aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library')
-rw-r--r--Src/Plugins/Library/ml_autotag/main.cpp220
-rw-r--r--Src/Plugins/Library/ml_autotag/main.h35
-rw-r--r--Src/Plugins/Library/ml_autotag/metadata.cpp25
-rw-r--r--Src/Plugins/Library/ml_autotag/ml_autotag.rc212
-rw-r--r--Src/Plugins/Library/ml_autotag/ml_autotag.sln30
-rw-r--r--Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj306
-rw-r--r--Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj.filters71
-rw-r--r--Src/Plugins/Library/ml_autotag/resource.h88
-rw-r--r--Src/Plugins/Library/ml_autotag/tagger.cpp831
-rw-r--r--Src/Plugins/Library/ml_autotag/tagger.h68
-rw-r--r--Src/Plugins/Library/ml_autotag/version.rc239
-rw-r--r--Src/Plugins/Library/ml_bookmarks/api__ml_bookmarks.h15
-rw-r--r--Src/Plugins/Library/ml_bookmarks/bookmark.cpp32
-rw-r--r--Src/Plugins/Library/ml_bookmarks/bookmark.h18
-rw-r--r--Src/Plugins/Library/ml_bookmarks/listview.cpp128
-rw-r--r--Src/Plugins/Library/ml_bookmarks/listview.h144
-rw-r--r--Src/Plugins/Library/ml_bookmarks/main.cpp122
-rw-r--r--Src/Plugins/Library/ml_bookmarks/main.h28
-rw-r--r--Src/Plugins/Library/ml_bookmarks/ml_bookmarks.rc186
-rw-r--r--Src/Plugins/Library/ml_bookmarks/ml_bookmarks.sln30
-rw-r--r--Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj317
-rw-r--r--Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj.filters100
-rw-r--r--Src/Plugins/Library/ml_bookmarks/resource.h46
-rw-r--r--Src/Plugins/Library/ml_bookmarks/resources/ti_bookmarks_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_bookmarks/version.rc239
-rw-r--r--Src/Plugins/Library/ml_bookmarks/view.cpp1512
-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
-rw-r--r--Src/Plugins/Library/ml_disc/M3UWriter.cpp91
-rw-r--r--Src/Plugins/Library/ml_disc/M3UWriter.h25
-rw-r--r--Src/Plugins/Library/ml_disc/PLSWriter.cpp65
-rw-r--r--Src/Plugins/Library/ml_disc/PLSWriter.h23
-rw-r--r--Src/Plugins/Library/ml_disc/ReplayGain.cpp95
-rw-r--r--Src/Plugins/Library/ml_disc/ReplayGain.h16
-rw-r--r--Src/Plugins/Library/ml_disc/api__ml_disc.h15
-rw-r--r--Src/Plugins/Library/ml_disc/banner.cpp222
-rw-r--r--Src/Plugins/Library/ml_disc/banner.h44
-rw-r--r--Src/Plugins/Library/ml_disc/cdburn.cpp2155
-rw-r--r--Src/Plugins/Library/ml_disc/cdrip.cpp1061
-rw-r--r--Src/Plugins/Library/ml_disc/cmdbar_data.cpp485
-rw-r--r--Src/Plugins/Library/ml_disc/commandbar.cpp119
-rw-r--r--Src/Plugins/Library/ml_disc/commandbar.h48
-rw-r--r--Src/Plugins/Library/ml_disc/config.cpp129
-rw-r--r--Src/Plugins/Library/ml_disc/config.h44
-rw-r--r--Src/Plugins/Library/ml_disc/copyfiles.cpp613
-rw-r--r--Src/Plugins/Library/ml_disc/copyfiles.h27
-rw-r--r--Src/Plugins/Library/ml_disc/copyfiles_post.cpp138
-rw-r--r--Src/Plugins/Library/ml_disc/copyinternal.h125
-rw-r--r--Src/Plugins/Library/ml_disc/copyprep.cpp426
-rw-r--r--Src/Plugins/Library/ml_disc/copyprogress.cpp360
-rw-r--r--Src/Plugins/Library/ml_disc/discInfo.cpp328
-rw-r--r--Src/Plugins/Library/ml_disc/discInfo.h61
-rw-r--r--Src/Plugins/Library/ml_disc/drive.cpp138
-rw-r--r--Src/Plugins/Library/ml_disc/drive.h16
-rw-r--r--Src/Plugins/Library/ml_disc/driveListBox.cpp255
-rw-r--r--Src/Plugins/Library/ml_disc/driveListBox.h50
-rw-r--r--Src/Plugins/Library/ml_disc/drivemngr.cpp1910
-rw-r--r--Src/Plugins/Library/ml_disc/drivemngr.h239
-rw-r--r--Src/Plugins/Library/ml_disc/drives.cpp243
-rw-r--r--Src/Plugins/Library/ml_disc/drives.h44
-rw-r--r--Src/Plugins/Library/ml_disc/formatfilename.cpp269
-rw-r--r--Src/Plugins/Library/ml_disc/helpwnd.cpp350
-rw-r--r--Src/Plugins/Library/ml_disc/infoBox.cpp145
-rw-r--r--Src/Plugins/Library/ml_disc/infoBox.h42
-rw-r--r--Src/Plugins/Library/ml_disc/main.cpp1273
-rw-r--r--Src/Plugins/Library/ml_disc/main.h222
-rw-r--r--Src/Plugins/Library/ml_disc/medium.cpp136
-rw-r--r--Src/Plugins/Library/ml_disc/medium.h17
-rw-r--r--Src/Plugins/Library/ml_disc/menu.cpp25
-rw-r--r--Src/Plugins/Library/ml_disc/menu.h13
-rw-r--r--Src/Plugins/Library/ml_disc/ml_disc.rc939
-rw-r--r--Src/Plugins/Library/ml_disc/ml_disc.sln30
-rw-r--r--Src/Plugins/Library/ml_disc/ml_disc.vcxproj382
-rw-r--r--Src/Plugins/Library/ml_disc/ml_disc.vcxproj.filters250
-rw-r--r--Src/Plugins/Library/ml_disc/png.rc45
-rw-r--r--Src/Plugins/Library/ml_disc/prefs.cpp351
-rw-r--r--Src/Plugins/Library/ml_disc/primosdk_helper.cpp109
-rw-r--r--Src/Plugins/Library/ml_disc/primosdk_helper.h31
-rw-r--r--Src/Plugins/Library/ml_disc/questionwnd.cpp279
-rw-r--r--Src/Plugins/Library/ml_disc/resource.h455
-rw-r--r--Src/Plugins/Library/ml_disc/resources/cdrom.pngbin0 -> 202 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/cdrom_32x32_24.bmpbin0 -> 3126 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject1.pngbin0 -> 249 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject2.pngbin0 -> 261 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject3.pngbin0 -> 132 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject4.pngbin0 -> 113 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject_d.pngbin0 -> 345 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject_n.pngbin0 -> 361 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/eject_p.pngbin0 -> 325 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueue_d.pngbin0 -> 285 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueue_menu_16x16.pngbin0 -> 199 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueue_n.pngbin0 -> 270 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueue_p.pngbin0 -> 249 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueuem_d.pngbin0 -> 250 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueuem_n.pngbin0 -> 259 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/enqueuem_p.pngbin0 -> 257 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/filecopy.pngbin0 -> 2903 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/listbox_back_2x68x24.bmpbin0 -> 598 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/play_d.pngbin0 -> 340 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/play_menu_16x16.pngbin0 -> 348 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/play_n.pngbin0 -> 352 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/play_p.pngbin0 -> 352 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/playm_d.pngbin0 -> 373 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/playm_n.pngbin0 -> 380 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/playm_p.pngbin0 -> 386 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/rip&burn_logo_228x25x16.bmpbin0 -> 11456 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/resources/sonic_powered_44x22.bmpbin0 -> 694 bytes
-rw-r--r--Src/Plugins/Library/ml_disc/settings.cpp573
-rw-r--r--Src/Plugins/Library/ml_disc/settings.h108
-rw-r--r--Src/Plugins/Library/ml_disc/spti.cpp239
-rw-r--r--Src/Plugins/Library/ml_disc/spti.h35
-rw-r--r--Src/Plugins/Library/ml_disc/version.rc239
-rw-r--r--Src/Plugins/Library/ml_disc/view_cdrom.cpp1014
-rw-r--r--Src/Plugins/Library/ml_disc/view_container.cpp573
-rw-r--r--Src/Plugins/Library/ml_disc/view_data.cpp675
-rw-r--r--Src/Plugins/Library/ml_disc/view_info.cpp127
-rw-r--r--Src/Plugins/Library/ml_disc/view_ripburn.cpp567
-rw-r--r--Src/Plugins/Library/ml_disc/view_wait.cpp140
-rw-r--r--Src/Plugins/Library/ml_downloads/AtomParse.h51
-rw-r--r--Src/Plugins/Library/ml_downloads/DESIGN.txt12
-rw-r--r--Src/Plugins/Library/ml_downloads/Defaults.cpp43
-rw-r--r--Src/Plugins/Library/ml_downloads/Defaults.h24
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadManager.cpp1
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadStatus.cpp158
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadStatus.h41
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadThread.cpp65
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadThread.h27
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp125
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadViewCallback.h37
-rw-r--r--Src/Plugins/Library/ml_downloads/Downloaded.cpp141
-rw-r--r--Src/Plugins/Library/ml_downloads/Downloaded.h86
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp1657
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsDialog.h11
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsParse.cpp179
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsParse.h13
-rw-r--r--Src/Plugins/Library/ml_downloads/Main.cpp387
-rw-r--r--Src/Plugins/Library/ml_downloads/Main.h43
-rw-r--r--Src/Plugins/Library/ml_downloads/MessageProcessor.cpp7
-rw-r--r--Src/Plugins/Library/ml_downloads/MessageProcessor.h35
-rw-r--r--Src/Plugins/Library/ml_downloads/ParseUtil.cpp43
-rw-r--r--Src/Plugins/Library/ml_downloads/ParseUtil.h8
-rw-r--r--Src/Plugins/Library/ml_downloads/Preferences.cpp77
-rw-r--r--Src/Plugins/Library/ml_downloads/Preferences.h6
-rw-r--r--Src/Plugins/Library/ml_downloads/RFCDate.cpp216
-rw-r--r--Src/Plugins/Library/ml_downloads/RFCDate.h7
-rw-r--r--Src/Plugins/Library/ml_downloads/TODO.txt51
-rw-r--r--Src/Plugins/Library/ml_downloads/Util.h64
-rw-r--r--Src/Plugins/Library/ml_downloads/XMLWriter.cpp125
-rw-r--r--Src/Plugins/Library/ml_downloads/XMLWriter.h6
-rw-r--r--Src/Plugins/Library/ml_downloads/api__ml_downloads.h11
-rw-r--r--Src/Plugins/Library/ml_downloads/date.c1
-rw-r--r--Src/Plugins/Library/ml_downloads/date.h20
-rw-r--r--Src/Plugins/Library/ml_downloads/db.cpp150
-rw-r--r--Src/Plugins/Library/ml_downloads/errors.h15
-rw-r--r--Src/Plugins/Library/ml_downloads/layout.cpp215
-rw-r--r--Src/Plugins/Library/ml_downloads/layout.h43
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.rc249
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.sln93
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj370
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters215
-rw-r--r--Src/Plugins/Library/ml_downloads/png.rc63
-rw-r--r--Src/Plugins/Library/ml_downloads/resource.h120
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon.pngbin0 -> 367 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/util.cpp255
-rw-r--r--Src/Plugins/Library/ml_downloads/version.rc239
-rw-r--r--Src/Plugins/Library/ml_fanzone.7zbin0 -> 9490 bytes
-rw-r--r--Src/Plugins/Library/ml_fanzone/CMakeLists.txt77
-rw-r--r--Src/Plugins/Library/ml_fanzone/api__ml_fanzone.h15
-rw-r--r--Src/Plugins/Library/ml_fanzone/compatibility.manifest20
-rw-r--r--Src/Plugins/Library/ml_fanzone/listview.cpp128
-rw-r--r--Src/Plugins/Library/ml_fanzone/listview.h144
-rw-r--r--Src/Plugins/Library/ml_fanzone/main.cpp110
-rw-r--r--Src/Plugins/Library/ml_fanzone/main.h24
-rw-r--r--Src/Plugins/Library/ml_fanzone/ml_fanzone.dll.manifest20
-rw-r--r--Src/Plugins/Library/ml_fanzone/ml_fanzone.rc142
-rw-r--r--Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj354
-rw-r--r--Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj.filters97
-rw-r--r--Src/Plugins/Library/ml_fanzone/myCefApp.cpp49
-rw-r--r--Src/Plugins/Library/ml_fanzone/resource.h23
-rw-r--r--Src/Plugins/Library/ml_fanzone/resources/FANZONE_16x16.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_fanzone/resources/cefsimple.icobin0 -> 23558 bytes
-rw-r--r--Src/Plugins/Library/ml_fanzone/resources/small.icobin0 -> 23558 bytes
-rw-r--r--Src/Plugins/Library/ml_fanzone/version.rc239
-rw-r--r--Src/Plugins/Library/ml_fanzone/view.cpp444
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPI.cpp93
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPI.h12
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp62
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPIFactory.h21
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp95
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_Creator.h31
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp123
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h27
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp260
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h46
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp198
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h37
-rw-r--r--Src/Plugins/Library/ml_history/Main.cpp349
-rw-r--r--Src/Plugins/Library/ml_history/Main.h34
-rw-r--r--Src/Plugins/Library/ml_history/api__ml_history.h18
-rw-r--r--Src/Plugins/Library/ml_history/api_history.cpp1
-rw-r--r--Src/Plugins/Library/ml_history/api_history.h34
-rw-r--r--Src/Plugins/Library/ml_history/db_error.txt6
-rw-r--r--Src/Plugins/Library/ml_history/history.h35
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.cpp289
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.h64
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.rc264
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.sln94
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.vcxproj341
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.vcxproj.filters164
-rw-r--r--Src/Plugins/Library/ml_history/prefs.cpp178
-rw-r--r--Src/Plugins/Library/ml_history/resource.h92
-rw-r--r--Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_history/version.rc239
-rw-r--r--Src/Plugins/Library/ml_history/view_history.cpp2349
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/api__ml_hotmixradio.h15
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/listview.cpp128
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/listview.h144
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/main.cpp111
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/main.h24
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.rc128
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj313
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj.filters88
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/resource.h20
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/resources/HOTMIXRADIO_16x16.bmpbin0 -> 1078 bytes
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/version.rc239
-rw-r--r--Src/Plugins/Library/ml_hotmixradio/view.cpp388
-rw-r--r--Src/Plugins/Library/ml_impex/ImportPlaylists.cpp404
-rw-r--r--Src/Plugins/Library/ml_impex/ImporterAPI.cpp299
-rw-r--r--Src/Plugins/Library/ml_impex/ImporterAPI.h13
-rw-r--r--Src/Plugins/Library/ml_impex/api__ml_impex.h20
-rw-r--r--Src/Plugins/Library/ml_impex/api_importer.h52
-rw-r--r--Src/Plugins/Library/ml_impex/impex.cpp625
-rw-r--r--Src/Plugins/Library/ml_impex/importer.cpp327
-rw-r--r--Src/Plugins/Library/ml_impex/importer.h66
-rw-r--r--Src/Plugins/Library/ml_impex/itunesxmlwrite.cpp152
-rw-r--r--Src/Plugins/Library/ml_impex/itunesxmlwrite.h33
-rw-r--r--Src/Plugins/Library/ml_impex/ml_impex.rc144
-rw-r--r--Src/Plugins/Library/ml_impex/ml_impex.sln50
-rw-r--r--Src/Plugins/Library/ml_impex/ml_impex.vcxproj317
-rw-r--r--Src/Plugins/Library/ml_impex/ml_impex.vcxproj.filters62
-rw-r--r--Src/Plugins/Library/ml_impex/resource.h37
-rw-r--r--Src/Plugins/Library/ml_impex/version.rc239
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtCache.cpp508
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtCache.h14
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtContainer.cpp80
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtContainer.h43
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtFilter.cpp1258
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtFilter.h64
-rw-r--r--Src/Plugins/Library/ml_local/AlbumFilter.cpp496
-rw-r--r--Src/Plugins/Library/ml_local/AlbumFilter.h49
-rw-r--r--Src/Plugins/Library/ml_local/DBitemrecord.cpp173
-rw-r--r--Src/Plugins/Library/ml_local/FolderBrowseEx.cpp239
-rw-r--r--Src/Plugins/Library/ml_local/FolderBrowseEx.h97
-rw-r--r--Src/Plugins/Library/ml_local/LocalMediaCOM.cpp307
-rw-r--r--Src/Plugins/Library/ml_local/LocalMediaCOM.h21
-rw-r--r--Src/Plugins/Library/ml_local/MD5.cpp294
-rw-r--r--Src/Plugins/Library/ml_local/MD5.h18
-rw-r--r--Src/Plugins/Library/ml_local/MLDBCallback.h76
-rw-r--r--Src/Plugins/Library/ml_local/MLString.cpp116
-rw-r--r--Src/Plugins/Library/ml_local/MLString.h54
-rw-r--r--Src/Plugins/Library/ml_local/Main.cpp586
-rw-r--r--Src/Plugins/Library/ml_local/Main.h71
-rw-r--r--Src/Plugins/Library/ml_local/ReIndexUI.cpp333
-rw-r--r--Src/Plugins/Library/ml_local/SaveQuery.cpp406
-rw-r--r--Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp475
-rw-r--r--Src/Plugins/Library/ml_local/ScanFolderBrowser.h53
-rw-r--r--Src/Plugins/Library/ml_local/SimpleFilter.cpp278
-rw-r--r--Src/Plugins/Library/ml_local/SimpleFilter.h169
-rw-r--r--Src/Plugins/Library/ml_local/TitleInfo.cpp321
-rw-r--r--Src/Plugins/Library/ml_local/ViewFilter.cpp431
-rw-r--r--Src/Plugins/Library/ml_local/ViewFilter.h89
-rw-r--r--Src/Plugins/Library/ml_local/add.cpp478
-rw-r--r--Src/Plugins/Library/ml_local/api__ml_local.h15
-rw-r--r--Src/Plugins/Library/ml_local/api_mldb.cpp3
-rw-r--r--Src/Plugins/Library/ml_local/api_mldb.h166
-rw-r--r--Src/Plugins/Library/ml_local/bgscan.cpp940
-rw-r--r--Src/Plugins/Library/ml_local/contnr.cpp979
-rw-r--r--Src/Plugins/Library/ml_local/contnr.h153
-rw-r--r--Src/Plugins/Library/ml_local/db.h64
-rw-r--r--Src/Plugins/Library/ml_local/db_error.txt6
-rw-r--r--Src/Plugins/Library/ml_local/editinfo.cpp816
-rw-r--r--Src/Plugins/Library/ml_local/editquery.cpp1013
-rw-r--r--Src/Plugins/Library/ml_local/editquery.h7
-rw-r--r--Src/Plugins/Library/ml_local/evntsink.cpp239
-rw-r--r--Src/Plugins/Library/ml_local/evntsink.h25
-rw-r--r--Src/Plugins/Library/ml_local/guess.cpp122
-rw-r--r--Src/Plugins/Library/ml_local/handleMessage.cpp906
-rw-r--r--Src/Plugins/Library/ml_local/local_menu.cpp272
-rw-r--r--Src/Plugins/Library/ml_local/local_menu.h15
-rw-r--r--Src/Plugins/Library/ml_local/metaRecord.h44
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.cpp1394
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.h403
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.rc1244
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.sln128
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.vcxproj416
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.vcxproj.filters328
-rw-r--r--Src/Plugins/Library/ml_local/ml_subclass.cpp80
-rw-r--r--Src/Plugins/Library/ml_local/mldbApi.cpp399
-rw-r--r--Src/Plugins/Library/ml_local/mldbApi.h34
-rw-r--r--Src/Plugins/Library/ml_local/mldbApiFactory.cpp61
-rw-r--r--Src/Plugins/Library/ml_local/mldbApiFactory.h23
-rw-r--r--Src/Plugins/Library/ml_local/nde_error.txt4
-rw-r--r--Src/Plugins/Library/ml_local/nde_itemRecord.cpp976
-rw-r--r--Src/Plugins/Library/ml_local/pe_subclass.cpp76
-rw-r--r--Src/Plugins/Library/ml_local/prefs.cpp936
-rw-r--r--Src/Plugins/Library/ml_local/queries.cpp1639
-rw-r--r--Src/Plugins/Library/ml_local/queries.txt77
-rw-r--r--Src/Plugins/Library/ml_local/remove.cpp31
-rw-r--r--Src/Plugins/Library/ml_local/resource.h606
-rw-r--r--Src/Plugins/Library/ml_local/resources/icn_alb_art.bmpbin0 -> 1210 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/icn_columns.bmpbin0 -> 1342 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/icn_view_mode.bmpbin0 -> 1298 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_simple.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_threefilters.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_twofilters.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/notfound.pngbin0 -> 3645 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmpbin0 -> 578 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/util.cpp77
-rw-r--r--Src/Plugins/Library/ml_local/version.rc239
-rw-r--r--Src/Plugins/Library/ml_local/view_audio.cpp2403
-rw-r--r--Src/Plugins/Library/ml_local/view_errorinfo.cpp119
-rw-r--r--Src/Plugins/Library/ml_local/view_media.cpp4051
-rw-r--r--Src/Plugins/Library/ml_local/view_miniinfo.cpp201
-rw-r--r--Src/Plugins/Library/ml_local/wa_subclass.cpp314
-rw-r--r--Src/Plugins/Library/ml_nft/api__ml_nft.h15
-rw-r--r--Src/Plugins/Library/ml_nft/listview.cpp128
-rw-r--r--Src/Plugins/Library/ml_nft/listview.h144
-rw-r--r--Src/Plugins/Library/ml_nft/main.cpp111
-rw-r--r--Src/Plugins/Library/ml_nft/main.h24
-rw-r--r--Src/Plugins/Library/ml_nft/ml_nft.rc128
-rw-r--r--Src/Plugins/Library/ml_nft/ml_nft.vcxproj313
-rw-r--r--Src/Plugins/Library/ml_nft/ml_nft.vcxproj.filters88
-rw-r--r--Src/Plugins/Library/ml_nft/resource.h20
-rw-r--r--Src/Plugins/Library/ml_nft/resources/NFT_16x16.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_nft/resources/_NFT_16x16.pngbin0 -> 456 bytes
-rw-r--r--Src/Plugins/Library/ml_nft/version.rc239
-rw-r--r--Src/Plugins/Library/ml_nft/view.cpp388
-rw-r--r--Src/Plugins/Library/ml_nowplaying/common.cpp147
-rw-r--r--Src/Plugins/Library/ml_nowplaying/common.h58
-rw-r--r--Src/Plugins/Library/ml_nowplaying/external.cpp110
-rw-r--r--Src/Plugins/Library/ml_nowplaying/external.h44
-rw-r--r--Src/Plugins/Library/ml_nowplaying/handler.cpp47
-rw-r--r--Src/Plugins/Library/ml_nowplaying/handler.h19
-rw-r--r--Src/Plugins/Library/ml_nowplaying/local_menu.cpp68
-rw-r--r--Src/Plugins/Library/ml_nowplaying/local_menu.h15
-rw-r--r--Src/Plugins/Library/ml_nowplaying/main.cpp160
-rw-r--r--Src/Plugins/Library/ml_nowplaying/main.h22
-rw-r--r--Src/Plugins/Library/ml_nowplaying/ml_nowplaying.rc99
-rw-r--r--Src/Plugins/Library/ml_nowplaying/ml_nowplaying.sln30
-rw-r--r--Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj280
-rw-r--r--Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj.filters107
-rw-r--r--Src/Plugins/Library/ml_nowplaying/navigation.cpp684
-rw-r--r--Src/Plugins/Library/ml_nowplaying/navigation.h27
-rw-r--r--Src/Plugins/Library/ml_nowplaying/png.rc7
-rw-r--r--Src/Plugins/Library/ml_nowplaying/resource.h23
-rw-r--r--Src/Plugins/Library/ml_nowplaying/resources/serviceIcon.pngbin0 -> 290 bytes
-rw-r--r--Src/Plugins/Library/ml_nowplaying/service.cpp171
-rw-r--r--Src/Plugins/Library/ml_nowplaying/service.h53
-rw-r--r--Src/Plugins/Library/ml_nowplaying/version.rc239
-rw-r--r--Src/Plugins/Library/ml_nowplaying/wasabi.cpp82
-rw-r--r--Src/Plugins/Library/ml_nowplaying/wasabi.h42
-rw-r--r--Src/Plugins/Library/ml_nowplaying/wasabiCallback.cpp90
-rw-r--r--Src/Plugins/Library/ml_nowplaying/wasabiCallback.h42
-rw-r--r--Src/Plugins/Library/ml_online/BufferCache.h13
-rw-r--r--Src/Plugins/Library/ml_online/JSAPI2_Creator.cpp93
-rw-r--r--Src/Plugins/Library/ml_online/JSAPI2_Creator.h31
-rw-r--r--Src/Plugins/Library/ml_online/JnetCOM.cpp656
-rw-r--r--Src/Plugins/Library/ml_online/JnetCOM.h91
-rw-r--r--Src/Plugins/Library/ml_online/Main.cpp599
-rw-r--r--Src/Plugins/Library/ml_online/Main.h79
-rw-r--r--Src/Plugins/Library/ml_online/OMCOM.cpp821
-rw-r--r--Src/Plugins/Library/ml_online/OMCOM.h43
-rw-r--r--Src/Plugins/Library/ml_online/Preferences.cpp110
-rw-r--r--Src/Plugins/Library/ml_online/Preferences.h16
-rw-r--r--Src/Plugins/Library/ml_online/Setup/SetupGroupFilter.h91
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setup.cpp71
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupDetails.cpp80
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupDetails.h30
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupDetailsGroup.cpp284
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupDetailsService.cpp596
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupGroup.cpp929
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupGroup.h131
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupGroupFilter.cpp226
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupGroupList.cpp190
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupGroupList.h52
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupImage.cpp282
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupImage.h50
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupListbox.cpp878
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupListbox.h84
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupListboxLabel.cpp228
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupListboxLabel.h57
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupLog.cpp395
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupLog.h54
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupPage.cpp648
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupPage.h85
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupPageWnd.cpp145
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupRecord.cpp567
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupRecord.h84
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupServicePanel.cpp797
-rw-r--r--Src/Plugins/Library/ml_online/Setup/setupServicePanel.h79
-rw-r--r--Src/Plugins/Library/ml_online/api__ml_online.h62
-rw-r--r--Src/Plugins/Library/ml_online/browserEvent.cpp100
-rw-r--r--Src/Plugins/Library/ml_online/browserEvent.h41
-rw-r--r--Src/Plugins/Library/ml_online/commands.cpp418
-rw-r--r--Src/Plugins/Library/ml_online/commands.h29
-rw-r--r--Src/Plugins/Library/ml_online/common.cpp374
-rw-r--r--Src/Plugins/Library/ml_online/common.h81
-rw-r--r--Src/Plugins/Library/ml_online/config.cpp304
-rw-r--r--Src/Plugins/Library/ml_online/config.h54
-rw-r--r--Src/Plugins/Library/ml_online/external.cpp273
-rw-r--r--Src/Plugins/Library/ml_online/external.h49
-rw-r--r--Src/Plugins/Library/ml_online/forceUrl.cpp51
-rw-r--r--Src/Plugins/Library/ml_online/forceUrl.h27
-rw-r--r--Src/Plugins/Library/ml_online/handler.cpp168
-rw-r--r--Src/Plugins/Library/ml_online/handler.h20
-rw-r--r--Src/Plugins/Library/ml_online/import.h18
-rw-r--r--Src/Plugins/Library/ml_online/importFile.cpp265
-rw-r--r--Src/Plugins/Library/ml_online/importUrl.cpp321
-rw-r--r--Src/Plugins/Library/ml_online/jsapi2_omcom.cpp136
-rw-r--r--Src/Plugins/Library/ml_online/jsapi2_omcom.h27
-rw-r--r--Src/Plugins/Library/ml_online/local_menu.cpp379
-rw-r--r--Src/Plugins/Library/ml_online/local_menu.h40
-rw-r--r--Src/Plugins/Library/ml_online/messageBox.cpp193
-rw-r--r--Src/Plugins/Library/ml_online/messageBox.h13
-rw-r--r--Src/Plugins/Library/ml_online/ml_online.rc453
-rw-r--r--Src/Plugins/Library/ml_online/ml_online.sln30
-rw-r--r--Src/Plugins/Library/ml_online/ml_online.vcxproj395
-rw-r--r--Src/Plugins/Library/ml_online/ml_online.vcxproj.filters301
-rw-r--r--Src/Plugins/Library/ml_online/navigation.cpp1472
-rw-r--r--Src/Plugins/Library/ml_online/navigation.h96
-rw-r--r--Src/Plugins/Library/ml_online/png.rc27
-rw-r--r--Src/Plugins/Library/ml_online/resource.h206
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconAol.pngbin0 -> 253 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconAolGames.pngbin0 -> 206 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconDefault.pngbin0 -> 245 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconIn2Tv.pngbin0 -> 247 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconMusicNow.pngbin0 -> 294 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconShoutcastRadio.pngbin0 -> 277 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconShoutcastTv.pngbin0 -> 197 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconSingingfish.pngbin0 -> 249 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconWaMusic.pngbin0 -> 300 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/iconWaRemote.pngbin0 -> 264 bytes
-rw-r--r--Src/Plugins/Library/ml_online/resources/pages/serviceEditor.htm30
-rw-r--r--Src/Plugins/Library/ml_online/resources/pages/webdev.js95
-rw-r--r--Src/Plugins/Library/ml_online/resources/service64x64.pngbin0 -> 1667 bytes
-rw-r--r--Src/Plugins/Library/ml_online/serviceHelper.cpp1232
-rw-r--r--Src/Plugins/Library/ml_online/serviceHelper.h57
-rw-r--r--Src/Plugins/Library/ml_online/serviceHost.cpp394
-rw-r--r--Src/Plugins/Library/ml_online/serviceHost.h75
-rw-r--r--Src/Plugins/Library/ml_online/testPages.rc7
-rw-r--r--Src/Plugins/Library/ml_online/version.rc239
-rw-r--r--Src/Plugins/Library/ml_online/wasabi.cpp144
-rw-r--r--Src/Plugins/Library/ml_playlists/AMNWatcher.cpp241
-rw-r--r--Src/Plugins/Library/ml_playlists/AddPlaylist.cpp124
-rw-r--r--Src/Plugins/Library/ml_playlists/CurrentPlaylist.cpp313
-rw-r--r--Src/Plugins/Library/ml_playlists/CurrentPlaylist.h12
-rw-r--r--Src/Plugins/Library/ml_playlists/DESIGN.TXT7
-rw-r--r--Src/Plugins/Library/ml_playlists/Playlist.cpp331
-rw-r--r--Src/Plugins/Library/ml_playlists/Playlist.h66
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.cpp41
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.h15
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistInfo.cpp233
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistInfo.h58
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistView.cpp300
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistView.h32
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistsCB.cpp120
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistsCB.h16
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistsCOM.cpp178
-rw-r--r--Src/Plugins/Library/ml_playlists/PlaylistsCOM.h20
-rw-r--r--Src/Plugins/Library/ml_playlists/RenamePlaylist.cpp72
-rw-r--r--Src/Plugins/Library/ml_playlists/SendTo.cpp697
-rw-r--r--Src/Plugins/Library/ml_playlists/SendTo.h100
-rw-r--r--Src/Plugins/Library/ml_playlists/api__ml_playlists.h27
-rw-r--r--Src/Plugins/Library/ml_playlists/main.h148
-rw-r--r--Src/Plugins/Library/ml_playlists/ml_playlists.cpp237
-rw-r--r--Src/Plugins/Library/ml_playlists/ml_playlists.rc498
-rw-r--r--Src/Plugins/Library/ml_playlists/ml_playlists.sln81
-rw-r--r--Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj359
-rw-r--r--Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj.filters199
-rw-r--r--Src/Plugins/Library/ml_playlists/ml_subclass.cpp271
-rw-r--r--Src/Plugins/Library/ml_playlists/pe_subclass.cpp63
-rw-r--r--Src/Plugins/Library/ml_playlists/playlists.cpp185
-rw-r--r--Src/Plugins/Library/ml_playlists/playlists.h6
-rw-r--r--Src/Plugins/Library/ml_playlists/playlistsXML.cpp88
-rw-r--r--Src/Plugins/Library/ml_playlists/playlistsXML.h27
-rw-r--r--Src/Plugins/Library/ml_playlists/pluginproc.cpp527
-rw-r--r--Src/Plugins/Library/ml_playlists/resource.h177
-rw-r--r--Src/Plugins/Library/ml_playlists/resources/ti_cloud_playlist_16x16x16.bmpbin0 -> 578 bytes
-rw-r--r--Src/Plugins/Library/ml_playlists/resources/ti_playlist_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_playlists/version.rc239
-rw-r--r--Src/Plugins/Library/ml_playlists/view_pl.cpp2885
-rw-r--r--Src/Plugins/Library/ml_playlists/view_playlists.cpp1953
-rw-r--r--Src/Plugins/Library/ml_playlists/wa_subclass.cpp90
-rw-r--r--Src/Plugins/Library/ml_plg/AddPlaylist.cpp139
-rw-r--r--Src/Plugins/Library/ml_plg/AlbumID.cpp45
-rw-r--r--Src/Plugins/Library/ml_plg/IDScanner.cpp698
-rw-r--r--Src/Plugins/Library/ml_plg/IDScanner.h97
-rw-r--r--Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp44
-rw-r--r--Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h14
-rw-r--r--Src/Plugins/Library/ml_plg/api__ml_plg.h50
-rw-r--r--Src/Plugins/Library/ml_plg/api_playlist_generator.h36
-rw-r--r--Src/Plugins/Library/ml_plg/atltransactionmanager.h697
-rw-r--r--Src/Plugins/Library/ml_plg/generate.cpp766
-rw-r--r--Src/Plugins/Library/ml_plg/gn_logo_88x83.bmpbin0 -> 21966 bytes
-rw-r--r--Src/Plugins/Library/ml_plg/impl_playlist.cpp328
-rw-r--r--Src/Plugins/Library/ml_plg/impl_playlist.h65
-rw-r--r--Src/Plugins/Library/ml_plg/main.h87
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.cpp719
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.rc312
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.sln30
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.vcxproj331
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.vcxproj.filters130
-rw-r--r--Src/Plugins/Library/ml_plg/pass1.cpp101
-rw-r--r--Src/Plugins/Library/ml_plg/pass2.cpp72
-rw-r--r--Src/Plugins/Library/ml_plg/playlist.cpp635
-rw-r--r--Src/Plugins/Library/ml_plg/playlist.h40
-rw-r--r--Src/Plugins/Library/ml_plg/prefs.cpp596
-rw-r--r--Src/Plugins/Library/ml_plg/resource.h120
-rw-r--r--Src/Plugins/Library/ml_plg/util.cpp36
-rw-r--r--Src/Plugins/Library/ml_plg/version.rc239
-rw-r--r--Src/Plugins/Library/ml_plg/view.cpp368
-rw-r--r--Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp848
-rw-r--r--Src/Plugins/Library/ml_pmp/AlbumArtListView.h44
-rw-r--r--Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp1173
-rw-r--r--Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h64
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceCommands.cpp192
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceCommands.h65
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceView.cpp2714
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceView.h257
-rw-r--r--Src/Plugins/Library/ml_pmp/Filters.cpp829
-rw-r--r--Src/Plugins/Library/ml_pmp/Filters.h58
-rw-r--r--Src/Plugins/Library/ml_pmp/IconStore.cpp237
-rw-r--r--Src/Plugins/Library/ml_pmp/IconStore.h39
-rw-r--r--Src/Plugins/Library/ml_pmp/LinkedQueue.cpp125
-rw-r--r--Src/Plugins/Library/ml_pmp/LinkedQueue.h41
-rw-r--r--Src/Plugins/Library/ml_pmp/PmpDevice.cpp557
-rw-r--r--Src/Plugins/Library/ml_pmp/PmpDevice.h1
-rw-r--r--Src/Plugins/Library/ml_pmp/SkinnedListView.cpp856
-rw-r--r--Src/Plugins/Library/ml_pmp/SkinnedListView.h86
-rw-r--r--Src/Plugins/Library/ml_pmp/api__ml_pmp.h56
-rw-r--r--Src/Plugins/Library/ml_pmp/autofill.cpp432
-rw-r--r--Src/Plugins/Library/ml_pmp/banner.cpp216
-rw-r--r--Src/Plugins/Library/ml_pmp/banner.h44
-rw-r--r--Src/Plugins/Library/ml_pmp/config.cpp50
-rw-r--r--Src/Plugins/Library/ml_pmp/config.h21
-rw-r--r--Src/Plugins/Library/ml_pmp/editinfo.cpp790
-rw-r--r--Src/Plugins/Library/ml_pmp/graphics.cpp316
-rw-r--r--Src/Plugins/Library/ml_pmp/graphics.h71
-rw-r--r--Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp313
-rw-r--r--Src/Plugins/Library/ml_pmp/local_menu.cpp189
-rw-r--r--Src/Plugins/Library/ml_pmp/local_menu.h15
-rw-r--r--Src/Plugins/Library/ml_pmp/main.cpp1454
-rw-r--r--Src/Plugins/Library/ml_pmp/main.h91
-rw-r--r--Src/Plugins/Library/ml_pmp/metadata_utils.cpp516
-rw-r--r--Src/Plugins/Library/ml_pmp/metadata_utils.h48
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.rc1228
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.sln141
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj444
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters298
-rw-r--r--Src/Plugins/Library/ml_pmp/mt19937ar.cpp3
-rw-r--r--Src/Plugins/Library/ml_pmp/mt19937ar.h6
-rw-r--r--Src/Plugins/Library/ml_pmp/pluginloader.cpp187
-rw-r--r--Src/Plugins/Library/ml_pmp/pluginloader.h14
-rw-r--r--Src/Plugins/Library/ml_pmp/pmp.h321
-rw-r--r--Src/Plugins/Library/ml_pmp/prefs.cpp1541
-rw-r--r--Src/Plugins/Library/ml_pmp/replaceVars.cpp213
-rw-r--r--Src/Plugins/Library/ml_pmp/resource1.h475
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/albumArt.pngbin0 -> 192 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/columns.pngbin0 -> 133 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/deviceIcon.pngbin0 -> 222 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/notfound.pngbin0 -> 3645 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/playlistIcon.pngbin0 -> 194 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/sync-command-small.pngbin0 -> 532 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.pngbin0 -> 215 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.pngbin0 -> 217 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.pngbin0 -> 211 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.pngbin0 -> 216 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/usb.pngbin0 -> 441 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/videoIcon.pngbin0 -> 197 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/viewMode.pngbin0 -> 146 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp675
-rw-r--r--Src/Plugins/Library/ml_pmp/syncCloudDialog.h17
-rw-r--r--Src/Plugins/Library/ml_pmp/syncDialog.cpp443
-rw-r--r--Src/Plugins/Library/ml_pmp/syncDialog.h20
-rw-r--r--Src/Plugins/Library/ml_pmp/transcoder.h38
-rw-r--r--Src/Plugins/Library/ml_pmp/transcoder_imp.cpp539
-rw-r--r--Src/Plugins/Library/ml_pmp/transcoder_imp.h122
-rw-r--r--Src/Plugins/Library/ml_pmp/transfer_thread.cpp503
-rw-r--r--Src/Plugins/Library/ml_pmp/transfer_thread.h94
-rw-r--r--Src/Plugins/Library/ml_pmp/version.rc239
-rw-r--r--Src/Plugins/Library/ml_pmp/view_pmp_devices.cpp764
-rw-r--r--Src/Plugins/Library/ml_pmp/view_pmp_media.cpp3205
-rw-r--r--Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp1181
-rw-r--r--Src/Plugins/Library/ml_rg/Process.cpp53
-rw-r--r--Src/Plugins/Library/ml_rg/Process.h25
-rw-r--r--Src/Plugins/Library/ml_rg/Progress.cpp261
-rw-r--r--Src/Plugins/Library/ml_rg/RGFactory.cpp67
-rw-r--r--Src/Plugins/Library/ml_rg/RGFactory.h24
-rw-r--r--Src/Plugins/Library/ml_rg/Results.cpp172
-rw-r--r--Src/Plugins/Library/ml_rg/api__ml_rg.h24
-rw-r--r--Src/Plugins/Library/ml_rg/config.cpp52
-rw-r--r--Src/Plugins/Library/ml_rg/main.h146
-rw-r--r--Src/Plugins/Library/ml_rg/metadata.cpp28
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.cpp409
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.rc152
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.sln57
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.vcxproj310
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters68
-rw-r--r--Src/Plugins/Library/ml_rg/obj_replaygain.h62
-rw-r--r--Src/Plugins/Library/ml_rg/replaygain.cpp370
-rw-r--r--Src/Plugins/Library/ml_rg/resource.h40
-rw-r--r--Src/Plugins/Library/ml_rg/version.rc239
-rw-r--r--Src/Plugins/Library/ml_transcode/LinkedQueue.cpp139
-rw-r--r--Src/Plugins/Library/ml_transcode/LinkedQueue.h41
-rw-r--r--Src/Plugins/Library/ml_transcode/api__ml_transcode.h26
-rw-r--r--Src/Plugins/Library/ml_transcode/main.cpp1100
-rw-r--r--Src/Plugins/Library/ml_transcode/ml_transcode.rc162
-rw-r--r--Src/Plugins/Library/ml_transcode/ml_transcode.sln30
-rw-r--r--Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj333
-rw-r--r--Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj.filters65
-rw-r--r--Src/Plugins/Library/ml_transcode/replaceVars.cpp317
-rw-r--r--Src/Plugins/Library/ml_transcode/resource.h45
-rw-r--r--Src/Plugins/Library/ml_transcode/reversesync.h9
-rw-r--r--Src/Plugins/Library/ml_transcode/version.rc239
-rw-r--r--Src/Plugins/Library/ml_webdev/commands.cpp328
-rw-r--r--Src/Plugins/Library/ml_webdev/commands.h29
-rw-r--r--Src/Plugins/Library/ml_webdev/common.cpp238
-rw-r--r--Src/Plugins/Library/ml_webdev/common.h68
-rw-r--r--Src/Plugins/Library/ml_webdev/config.cpp74
-rw-r--r--Src/Plugins/Library/ml_webdev/config.h18
-rw-r--r--Src/Plugins/Library/ml_webdev/external.cpp272
-rw-r--r--Src/Plugins/Library/ml_webdev/external.h49
-rw-r--r--Src/Plugins/Library/ml_webdev/forceUrl.cpp77
-rw-r--r--Src/Plugins/Library/ml_webdev/forceUrl.h15
-rw-r--r--Src/Plugins/Library/ml_webdev/import.h18
-rw-r--r--Src/Plugins/Library/ml_webdev/importFile.cpp266
-rw-r--r--Src/Plugins/Library/ml_webdev/importUrl.cpp320
-rw-r--r--Src/Plugins/Library/ml_webdev/local_menu.cpp222
-rw-r--r--Src/Plugins/Library/ml_webdev/local_menu.h17
-rw-r--r--Src/Plugins/Library/ml_webdev/main.cpp167
-rw-r--r--Src/Plugins/Library/ml_webdev/main.h25
-rw-r--r--Src/Plugins/Library/ml_webdev/ml_webdev.rc182
-rw-r--r--Src/Plugins/Library/ml_webdev/ml_webdev.sln30
-rw-r--r--Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj382
-rw-r--r--Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj.filters187
-rw-r--r--Src/Plugins/Library/ml_webdev/navigation.cpp1261
-rw-r--r--Src/Plugins/Library/ml_webdev/navigation.h83
-rw-r--r--Src/Plugins/Library/ml_webdev/png.rc9
-rw-r--r--Src/Plugins/Library/ml_webdev/resource.h60
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/gear.pngbin0 -> 265 bytes
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/help.pngbin0 -> 288 bytes
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/api2.js624
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/applicationApi.htm32
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/asyncDownloaderApi.htm42
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/bookmarkApi.htm17
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/configApi.htm28
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/historyApi.htm18
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/mediaCoreApi.htm37
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/playQueueApi.htm74
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/playlistApi.htm118
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/podcastApi.htm15
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/securityApi.htm17
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/serviceEditor.htm31
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/skinApi.htm40
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/transportApi.htm64
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/transportEventsApi.htm25
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/webdev.htm29
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/webdev.js97
-rw-r--r--Src/Plugins/Library/ml_webdev/resources/pages/welcome.htm22
-rw-r--r--Src/Plugins/Library/ml_webdev/serviceHelper.cpp232
-rw-r--r--Src/Plugins/Library/ml_webdev/serviceHelper.h28
-rw-r--r--Src/Plugins/Library/ml_webdev/serviceHost.cpp243
-rw-r--r--Src/Plugins/Library/ml_webdev/serviceHost.h55
-rw-r--r--Src/Plugins/Library/ml_webdev/testPages.rc22
-rw-r--r--Src/Plugins/Library/ml_webdev/version.rc239
-rw-r--r--Src/Plugins/Library/ml_webdev/wasabi.cpp87
-rw-r--r--Src/Plugins/Library/ml_webdev/wasabi.h45
-rw-r--r--Src/Plugins/Library/ml_wire/AtomParse.h51
-rw-r--r--Src/Plugins/Library/ml_wire/BackgroundDownloader.cpp338
-rw-r--r--Src/Plugins/Library/ml_wire/BackgroundDownloader.h18
-rw-r--r--Src/Plugins/Library/ml_wire/ChannelCheck.h14
-rw-r--r--Src/Plugins/Library/ml_wire/ChannelRefresher.cpp33
-rw-r--r--Src/Plugins/Library/ml_wire/ChannelRefresher.h14
-rw-r--r--Src/Plugins/Library/ml_wire/ChannelSync.h31
-rw-r--r--Src/Plugins/Library/ml_wire/Cloud.cpp235
-rw-r--r--Src/Plugins/Library/ml_wire/Cloud.h35
-rw-r--r--Src/Plugins/Library/ml_wire/DESIGN.txt12
-rw-r--r--Src/Plugins/Library/ml_wire/Defaults.cpp40
-rw-r--r--Src/Plugins/Library/ml_wire/Defaults.h34
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadManager.cpp1
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadStatus.cpp153
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadStatus.h41
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadThread.cpp222
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadThread.h28
-rw-r--r--Src/Plugins/Library/ml_wire/Downloaded.cpp121
-rw-r--r--Src/Plugins/Library/ml_wire/Downloaded.h79
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadsDialog.cpp1453
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadsDialog.h13
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadsParse.cpp204
-rw-r--r--Src/Plugins/Library/ml_wire/DownloadsParse.h13
-rw-r--r--Src/Plugins/Library/ml_wire/ExternalCOM.cpp85
-rw-r--r--Src/Plugins/Library/ml_wire/ExternalCOM.h40
-rw-r--r--Src/Plugins/Library/ml_wire/Factory.cpp64
-rw-r--r--Src/Plugins/Library/ml_wire/Factory.h22
-rw-r--r--Src/Plugins/Library/ml_wire/FeedParse.cpp38
-rw-r--r--Src/Plugins/Library/ml_wire/FeedParse.h24
-rw-r--r--Src/Plugins/Library/ml_wire/FeedUtil.cpp37
-rw-r--r--Src/Plugins/Library/ml_wire/FeedUtil.h8
-rw-r--r--Src/Plugins/Library/ml_wire/Feeds.cpp298
-rw-r--r--Src/Plugins/Library/ml_wire/Feeds.h50
-rw-r--r--Src/Plugins/Library/ml_wire/FeedsDialog.h5
-rw-r--r--Src/Plugins/Library/ml_wire/Item.cpp174
-rw-r--r--Src/Plugins/Library/ml_wire/Item.h50
-rw-r--r--Src/Plugins/Library/ml_wire/JSAPI2_Creator.cpp94
-rw-r--r--Src/Plugins/Library/ml_wire/JSAPI2_Creator.h31
-rw-r--r--Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.cpp109
-rw-r--r--Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.h30
-rw-r--r--Src/Plugins/Library/ml_wire/Loader.cpp100
-rw-r--r--Src/Plugins/Library/ml_wire/Loader.h6
-rw-r--r--Src/Plugins/Library/ml_wire/Main.cpp469
-rw-r--r--Src/Plugins/Library/ml_wire/Main.h69
-rw-r--r--Src/Plugins/Library/ml_wire/MessageProcessor.cpp7
-rw-r--r--Src/Plugins/Library/ml_wire/MessageProcessor.h35
-rw-r--r--Src/Plugins/Library/ml_wire/OPMLParse.cpp33
-rw-r--r--Src/Plugins/Library/ml_wire/OPMLParse.h28
-rw-r--r--Src/Plugins/Library/ml_wire/PCastFactory.cpp60
-rw-r--r--Src/Plugins/Library/ml_wire/PCastFactory.h24
-rw-r--r--Src/Plugins/Library/ml_wire/PCastURIHandler.cpp258
-rw-r--r--Src/Plugins/Library/ml_wire/PCastURIHandler.h21
-rw-r--r--Src/Plugins/Library/ml_wire/ParseUtil.cpp43
-rw-r--r--Src/Plugins/Library/ml_wire/ParseUtil.h8
-rw-r--r--Src/Plugins/Library/ml_wire/Preferences.cpp160
-rw-r--r--Src/Plugins/Library/ml_wire/Preferences.h6
-rw-r--r--Src/Plugins/Library/ml_wire/RFCDate.cpp216
-rw-r--r--Src/Plugins/Library/ml_wire/RFCDate.h7
-rw-r--r--Src/Plugins/Library/ml_wire/RSSCOM.cpp232
-rw-r--r--Src/Plugins/Library/ml_wire/RSSCOM.h42
-rw-r--r--Src/Plugins/Library/ml_wire/RSSParse.cpp181
-rw-r--r--Src/Plugins/Library/ml_wire/RSSParse.h9
-rw-r--r--Src/Plugins/Library/ml_wire/TODO.txt51
-rw-r--r--Src/Plugins/Library/ml_wire/UpdateAutoDownload.cpp57
-rw-r--r--Src/Plugins/Library/ml_wire/UpdateAutoDownload.h28
-rw-r--r--Src/Plugins/Library/ml_wire/UpdateTime.cpp62
-rw-r--r--Src/Plugins/Library/ml_wire/UpdateTime.h29
-rw-r--r--Src/Plugins/Library/ml_wire/Util.h69
-rw-r--r--Src/Plugins/Library/ml_wire/WantsDownloadStatus.h14
-rw-r--r--Src/Plugins/Library/ml_wire/Wire.cpp99
-rw-r--r--Src/Plugins/Library/ml_wire/Wire.h62
-rw-r--r--Src/Plugins/Library/ml_wire/XMLWriter.cpp272
-rw-r--r--Src/Plugins/Library/ml_wire/XMLWriter.h8
-rw-r--r--Src/Plugins/Library/ml_wire/api__ml_wire.h27
-rw-r--r--Src/Plugins/Library/ml_wire/api_podcasts.h46
-rw-r--r--Src/Plugins/Library/ml_wire/channelEditor.cpp687
-rw-r--r--Src/Plugins/Library/ml_wire/channelEditor.h15
-rw-r--r--Src/Plugins/Library/ml_wire/date.c1
-rw-r--r--Src/Plugins/Library/ml_wire/date.h20
-rw-r--r--Src/Plugins/Library/ml_wire/db.cpp171
-rw-r--r--Src/Plugins/Library/ml_wire/errors.h17
-rw-r--r--Src/Plugins/Library/ml_wire/ifc_article.h15
-rw-r--r--Src/Plugins/Library/ml_wire/ifc_podcast.h39
-rw-r--r--Src/Plugins/Library/ml_wire/layout.cpp215
-rw-r--r--Src/Plugins/Library/ml_wire/layout.h43
-rw-r--r--Src/Plugins/Library/ml_wire/ml_podcast.rc396
-rw-r--r--Src/Plugins/Library/ml_wire/ml_wire.sln87
-rw-r--r--Src/Plugins/Library/ml_wire/ml_wire.vcxproj423
-rw-r--r--Src/Plugins/Library/ml_wire/ml_wire.vcxproj.filters345
-rw-r--r--Src/Plugins/Library/ml_wire/navigation.cpp420
-rw-r--r--Src/Plugins/Library/ml_wire/navigation.h24
-rw-r--r--Src/Plugins/Library/ml_wire/png.rc15
-rw-r--r--Src/Plugins/Library/ml_wire/resource.h220
-rw-r--r--Src/Plugins/Library/ml_wire/resources/discoverIcon.pngbin0 -> 223 bytes
-rw-r--r--Src/Plugins/Library/ml_wire/resources/downloadIcon.pngbin0 -> 220 bytes
-rw-r--r--Src/Plugins/Library/ml_wire/resources/mediaIcon.pngbin0 -> 168 bytes
-rw-r--r--Src/Plugins/Library/ml_wire/resources/subscriptionIcon.pngbin0 -> 244 bytes
-rw-r--r--Src/Plugins/Library/ml_wire/resources/textIcon.pngbin0 -> 152 bytes
-rw-r--r--Src/Plugins/Library/ml_wire/service.cpp273
-rw-r--r--Src/Plugins/Library/ml_wire/service.h69
-rw-r--r--Src/Plugins/Library/ml_wire/subscriptionView.cpp2691
-rw-r--r--Src/Plugins/Library/ml_wire/subscriptionView.h29
-rw-r--r--Src/Plugins/Library/ml_wire/util.cpp271
-rw-r--r--Src/Plugins/Library/ml_wire/version.rc239
964 files changed, 193327 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_autotag/main.cpp b/Src/Plugins/Library/ml_autotag/main.cpp
new file mode 100644
index 00000000..cb796d84
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/main.cpp
@@ -0,0 +1,220 @@
+#define PLUGIN_VER L"2.08"
+
+#include "main.h"
+#include "tagger.h"
+#include "../nu/MediaLibraryInterface.h"
+
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+api_gracenote *AGAVE_API_GRACENOTE = 0;
+api_decodefile *AGAVE_API_DECODE = 0;
+api_mldb *AGAVE_API_MLDB = 0;
+
+static int Init();
+static void Quit();
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_autotag.dll)",
+ Init,
+ Quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+const char *INI_FILE=0;
+
+int Init()
+{
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ INI_FILE = mediaLibrary.GetWinampIni();
+
+ ServiceBuild( AGAVE_API_GRACENOTE, gracenoteApiGUID );
+ ServiceBuild( AGAVE_API_DECODE, decodeFileGUID );
+ ServiceBuild( WASABI_API_LNG, languageApiGUID );
+ ServiceBuild( AGAVE_API_MLDB, mldbApiGuid );
+
+ if ( !AGAVE_API_GRACENOTE || !AGAVE_API_DECODE || !WASABI_API_LNG )
+ return ML_INIT_FAILURE; // error!
+
+ WASABI_API_START_LANG( plugin.hDllInstance, MlAutoTagLangGUID );
+
+ static wchar_t szDescription[256];
+ swprintf( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_PLUGINNAME ), PLUGIN_VER );
+ plugin.description = (char*)szDescription;
+
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+}
+
+void LookupTracks(std::vector<TagItem*> &list, HWND parent=0)
+{
+ if(!AGAVE_API_MLDB)
+ {
+ ServiceBuild(AGAVE_API_MLDB, mldbApiGuid);
+ if(!AGAVE_API_MLDB) return;
+ }
+
+ if(!parent) parent = plugin.hwndLibraryParent;
+ ICDDBMusicIDManager3 *musicid = AGAVE_API_GRACENOTE?AGAVE_API_GRACENOTE->GetMusicID():NULL;
+ if(!musicid)
+ {
+ wchar_t title[32] = {0};
+ MessageBoxW(parent,WASABI_API_LNGSTRINGW(IDS_GRACENOTE_TOOLS_NOT_INSTALLED),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title, 32),0);
+ return;
+ }
+
+ //parent = 0; // non-blocking...
+ // as we're not parenting directly, then we have a look at the aot state and fiddle it
+ // -> not ideal but at least the dialog will show on top so it can be seen initially
+ HWND dialog = WASABI_API_CREATEDIALOGPARAMW(IDD_AUTOTAGGER,parent,autotagger_dlgproc,(LPARAM)new Tagger(list, musicid));
+ if(GetWindowLongPtr(plugin.hwndWinampParent,GWL_EXSTYLE) & WS_EX_TOPMOST)
+ SetWindowPos(dialog, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE);
+}
+
+static bool IsInternetAvailable()
+{
+ return !!SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_INETAVAILABLE);
+}
+
+INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_NO_CONFIG)
+ {
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if ((param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_FILENAMES ||
+ param1 == ML_TYPE_STREAMNAMES || param1 == ML_TYPE_ITEMRECORDLISTW ||
+ param1 == ML_TYPE_FILENAMESW || param1 == ML_TYPE_STREAMNAMESW) &&
+ IsInternetAvailable())
+ {
+ mediaLibrary.AddToSendTo(WASABI_API_LNGSTRINGW(IDS_AUTOTAG), param2, (INT_PTR)PluginMessageProc);
+ }
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)PluginMessageProc) return 0;
+
+ INT_PTR type = param1;
+ INT_PTR data = param2;
+
+ if (data)
+ {
+ if (type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ std::vector<TagItem*> list;
+ itemRecordListW *p = (itemRecordListW*)data;
+ for (int x = 0; x < p->Size; x ++)
+ {
+ list.push_back(new TagItem(p->Items[x].filename));
+ }
+ LookupTracks(list);
+
+ //list.deleteAll();
+ for (auto obj : list)
+ {
+ delete obj;
+ }
+ list.clear();
+ return 1;
+ }
+ else if (type == ML_TYPE_ITEMRECORDLIST)
+ {
+ std::vector<TagItem*> list;
+ itemRecordList *p = (itemRecordList*)data;
+ for (int x = 0; x < p->Size; x ++)
+ {
+ list.push_back(new TagItem(p->Items[x].filename));
+ }
+ LookupTracks(list);
+
+ //list.deleteAll();
+ for (auto obj : list)
+ {
+ delete obj;
+ }
+ list.clear();
+
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW)
+ {
+ std::vector<TagItem*> list;
+ wchar_t *p = (wchar_t*)data;
+ while (p && *p)
+ {
+ list.push_back(new TagItem(p));
+ p += wcslen(p) + 1;
+ }
+ LookupTracks(list);
+
+ //list.deleteAll();
+ for (auto obj : list)
+ {
+ delete obj;
+ }
+ list.clear();
+
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES)
+ {
+ std::vector<TagItem*> list;
+ char *p = (char*)data;
+ while (p && *p)
+ {
+ list.push_back(new TagItem(p));
+ p += strlen(p) + 1;
+ }
+ LookupTracks(list);
+
+ //list.deleteAll();
+ for (auto obj : list)
+ {
+ delete obj;
+ }
+ list.clear();
+
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+extern "C"
+{
+ __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_autotag/main.h b/Src/Plugins/Library/ml_autotag/main.h
new file mode 100644
index 00000000..996368ac
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/main.h
@@ -0,0 +1,35 @@
+#ifndef NULLSOFT_ML_AUTOTAG_MAIN_H
+#define NULLSOFT_ML_AUTOTAG_MAIN_H
+
+#include <windows.h>
+#include <shlwapi.h>
+#include "../../General/gen_ml/ml.h"
+#include "../nu/AutoWide.h"
+#include "../nu/listview.h"
+
+#include "resource.h"
+
+#include <api/service/waServiceFactory.h>
+
+#include "../gracenote/api_gracenote.h"
+extern api_gracenote * gracenoteApi;
+#define AGAVE_API_GRACENOTE gracenoteApi
+
+#include "../Winamp/api_decodefile.h"
+extern api_decodefile *decodeApi;
+#define AGAVE_API_DECODE decodeApi
+
+#include "../ml_local/api_mldb.h"
+extern api_mldb *mldbApi;
+#define AGAVE_API_MLDB mldbApi
+
+#include "../Agave/Language/api_language.h"
+
+extern winampMediaLibraryPlugin plugin;
+INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+LRESULT SetFileInfo(const wchar_t *filename, const wchar_t *metadata, const wchar_t *data);
+void WriteFileInfo(const wchar_t *file);
+int GetFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_autotag/metadata.cpp b/Src/Plugins/Library/ml_autotag/metadata.cpp
new file mode 100644
index 00000000..19cd2249
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/metadata.cpp
@@ -0,0 +1,25 @@
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+
+LRESULT SetFileInfo(const wchar_t *filename, const wchar_t *metadata, const wchar_t *data)
+{
+ extendedFileInfoStructW efis = {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? (size_t)lstrlenW(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+void WriteFileInfo(const wchar_t *file)
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)file, IPC_FILE_TAG_MAY_HAVE_UPDATEDW);
+}
+
+int GetFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len)
+{
+ extendedFileInfoStructW efis = { filename, metadata, dest, (size_t)len, };
+ return (int)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE); //will return 1 if wa2 supports this IPC call
+}
diff --git a/Src/Plugins/Library/ml_autotag/ml_autotag.rc b/Src/Plugins/Library/ml_autotag/ml_autotag.rc
new file mode 100644
index 00000000..1bd7e7de
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/ml_autotag.rc
@@ -0,0 +1,212 @@
+// 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_AUTOTAGGER DIALOGEX 0, 0, 420, 318
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Winamp Auto Tagger"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Apply Changes",IDOK,296,297,62,14
+ PUSHBUTTON "Cancel",IDCANCEL,363,297,50,14
+ CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,7,406,157
+ GROUPBOX "Old Info",IDC_STATIC_OLDINFO,7,175,201,93
+ LTEXT "Title:",IDC_STATIC_TITLE_NEW,216,185,30,8
+ LTEXT "Track #:",IDC_STATIC_TRACK_NEW,216,194,30,8
+ LTEXT "Artist:",IDC_STATIC_ARTIST_NEW,216,203,30,8
+ LTEXT "Album:",IDC_STATIC_ALBUM_NEW,216,212,30,8
+ LTEXT "Genre:",IDC_STATIC_GENRE_NEW,216,221,30,8
+ LTEXT "Year:",IDC_STATIC_YEAR_NEW,216,230,30,8
+ LTEXT "",IDC_TITLE_NEW,246,185,162,8,SS_NOPREFIX
+ LTEXT "",IDC_TRACK_NEW,246,194,66,8
+ LTEXT "",IDC_ARTIST_NEW,246,203,162,8,SS_NOPREFIX
+ LTEXT "",IDC_ALBUM_NEW,246,212,162,8,SS_NOPREFIX
+ LTEXT "",IDC_GENRE_NEW,246,221,162,8,SS_NOPREFIX
+ LTEXT "",IDC_YEAR_NEW,246,230,66,8
+ GROUPBOX "New Info",IDC_STATIC_NEWINFO,212,175,201,93
+ LTEXT "Selected:",IDC_STATIC_FILENAME,7,165,31,8
+ LTEXT "filename",IDC_FILENAME,41,165,275,8,SS_PATHELLIPSIS
+ LTEXT "Disc #:",IDC_STATIC_DISC_NEW,312,194,30,8
+ LTEXT "",IDC_DISC_NEW,342,194,66,8
+ LTEXT "Title:",IDC_STATIC_TITLE_OLD,11,185,30,8
+ LTEXT "Track #:",IDC_STATIC_TRACK_OLD,11,194,30,8
+ LTEXT "Artist:",IDC_STATIC_ARTIST_OLD,11,203,30,8
+ LTEXT "Album:",IDC_STATIC_ALBUM_OLD,11,212,30,8
+ LTEXT "Genre:",IDC_STATIC_GENRE_OLD,11,221,30,8
+ LTEXT "Year:",IDC_STATIC_YEAR_OLD,11,230,30,8
+ LTEXT "",IDC_TITLE_OLD,41,185,162,8,SS_NOPREFIX
+ LTEXT "",IDC_TRACK_OLD,41,194,66,8
+ LTEXT "",IDC_ARTIST_OLD,41,203,162,8,SS_NOPREFIX
+ LTEXT "",IDC_ALBUM_OLD,41,212,162,8,SS_NOPREFIX
+ LTEXT "",IDC_GENRE_OLD,41,221,162,8,SS_NOPREFIX
+ LTEXT "",IDC_YEAR_OLD,41,230,66,8
+ LTEXT "Disc #:",IDC_STATIC_DISC_OLD,107,194,30,8
+ LTEXT "",IDC_DISC_OLD,137,194,66,8
+ PUSHBUTTON "Check All",IDC_CHECK_ALL,319,165,46,11
+ PUSHBUTTON "Check None",IDC_CHECK_NONE,367,165,46,11
+ LTEXT "Accessing Gracenote Media Database...",IDC_BRANDTXT,7,303,129,8,NOT WS_VISIBLE
+ LTEXT "Album Artist:",IDC_STATIC_ALBUMARTIST_OLD,11,239,42,8
+ LTEXT "Publisher:",IDC_STATIC_PUBLISHER_OLD,11,248,32,8
+ LTEXT "",IDC_PUBLISHER_OLD,49,248,154,8,SS_NOPREFIX
+ LTEXT "",IDC_ALBUMARTIST_OLD,55,239,148,8,SS_NOPREFIX
+ LTEXT "Album Artist:",IDC_STATIC_ALBUMARTIST_NEW,216,239,42,8
+ LTEXT "Publisher:",IDC_STATIC_PUBLISHER_NEW,216,248,32,8
+ LTEXT "",IDC_PUBLISHER_NEW,254,248,154,8,SS_NOPREFIX
+ LTEXT "",IDC_ALBUMARTIST_NEW,260,239,148,8,SS_NOPREFIX
+ LTEXT "Composer:",IDC_STATIC_COMPOSER_OLD,11,257,36,8
+ LTEXT "",IDC_COMPOSER_OLD,49,257,154,8,SS_NOPREFIX
+ LTEXT "Composer:",IDC_STATIC_COMPOSER_NEW,216,257,36,8
+ LTEXT "",IDC_COMPOSER_NEW,254,257,154,8,SS_NOPREFIX
+ LTEXT "BPM:",IDC_STATIC_BPM_OLD,108,230,17,8
+ LTEXT "",IDC_BPM_OLD,138,230,66,8
+ LTEXT "BPM:",IDC_STATIC_BPM_NEW,312,230,17,8
+ LTEXT "",IDC_BPM_NEW,342,230,66,8
+END
+
+IDD_SAVING DIALOGEX 0, 0, 186, 45
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Updating Files..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,7,172,14
+ DEFPUSHBUTTON "Cancel",IDCANCEL,68,24,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_AUTOTAGGER, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 413
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 311
+ END
+
+ IDD_SAVING, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 38
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.K.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGINNAME "Nullsoft Auto-Tagger v%s"
+ 65535 "{D8DBA660-90BD-431d-8F4E-189D6ACB407E}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_GRACENOTE_TOOLS_NOT_INSTALLED "Gracenote tools are not installed"
+ IDS_ERROR "Error"
+ IDS_FILENAME "Filename"
+ IDS_STATUS "Status"
+ IDS_PROCESSING "Processing..."
+ IDS_LOOKINGUP "Looking up..."
+ IDS_QUERYING "Querying..."
+ IDS_QUERIED "Queried"
+ IDS_PROCESSED "Done"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ANALYZING "Analyzing..."
+ IDS_WAITING "Waiting"
+ IDS_NOMATCH "No Match"
+ IDS_AUTOTAG "Auto-Tag"
+ IDS_FUZZY "Unsure"
+END
+
+#endif // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_autotag/ml_autotag.sln b/Src/Plugins/Library/ml_autotag/ml_autotag.sln
new file mode 100644
index 00000000..bba91969
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/ml_autotag.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_autotag", "ml_autotag.vcxproj", "{11598486-4F4A-4D6A-BE12-A66B71ED8730}"
+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
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Debug|Win32.ActiveCfg = Debug|Win32
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Debug|Win32.Build.0 = Debug|Win32
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Debug|x64.ActiveCfg = Debug|x64
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Debug|x64.Build.0 = Debug|x64
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Release|Win32.ActiveCfg = Release|Win32
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Release|Win32.Build.0 = Release|Win32
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Release|x64.ActiveCfg = Release|x64
+ {11598486-4F4A-4D6A-BE12-A66B71ED8730}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {21C6579D-AAA7-4D78-A13A-7B5536E8F0E2}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj b/Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj
new file mode 100644
index 00000000..a9a3c213
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj
@@ -0,0 +1,306 @@
+<?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>{11598486-4F4A-4D6A-BE12-A66B71ED8730}</ProjectGuid>
+ <RootNamespace>ml_autotag</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)'=='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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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)'=='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|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;../;..\..\..\,./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_AUTOTAG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;../;..\..\..\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_AUTOTAG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;../;./;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_AUTOTAG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>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>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>
+ <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;../;./;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_AUTOTAG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>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>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>
+ </Link>
+ <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\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="metadata.cpp" />
+ <ClCompile Include="tagger.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="tagger.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_autotag.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_autotag/ml_autotag.vcxproj.filters b/Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj.filters
new file mode 100644
index 00000000..d529ab18
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/ml_autotag.vcxproj.filters
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="metadata.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="tagger.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\PtrList.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="tagger.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\PtrList.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{eb7beaf8-5c4e-4515-9669-3cc08c4dec13}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{e6fc722a-5751-4702-83f9-30ecc7e9576d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{f0f150ed-27bc-470e-a3c6-76379e7aac57}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{0d3a7eae-c815-4473-bb08-27e49076c914}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{3c9bf4d3-acf3-4085-97c0-7ea53dfcc512}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{5ae344c0-3796-4ce7-ba60-46ce8c1d188a}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_autotag.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_autotag/resource.h b/Src/Plugins/Library/ml_autotag/resource.h
new file mode 100644
index 00000000..087f6044
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/resource.h
@@ -0,0 +1,88 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_autotag.rc
+//
+#define IDD_AUTOTAGGER 101
+#define IDS_GRACENOTE_TOOLS_NOT_INSTALLED 103
+#define IDS_ERROR 104
+#define IDS_FILENAME 105
+#define IDS_STATUS 106
+#define IDS_PROCESSING 107
+#define IDS_LOOKINGUP 108
+#define IDS_QUERYING 109
+#define IDS_QUERIED 110
+#define IDS_PROCESSED 111
+#define IDS_ANALYZING 112
+#define IDS_WAITING 113
+#define IDS_NOMATCH 114
+#define IDD_SAVING 114
+#define IDS_AUTOTAG 116
+#define IDS_FUZZY 117
+#define IDC_LIST1 1001
+#define IDC_LIST 1001
+#define IDC_BUTTON1 1002
+#define IDC_CHECK_ALL 1002
+#define IDC_BUTTON2 1003
+#define IDC_CHECK_NONE 1003
+#define IDC_TITLE_OLD 1004
+#define IDC_TRACK_OLD 1005
+#define IDC_ARTIST_OLD 1006
+#define IDC_ALBUM_OLD 1007
+#define IDC_GENRE_OLD 1008
+#define IDC_YEAR_OLD 1009
+#define IDC_DISC_OLD 1010
+#define IDC_STATIC_TITLE_OLD 1011
+#define IDC_STATIC_TRACK_OLD 1012
+#define IDC_STATIC_ARTIST_OLD 1013
+#define IDC_STATIC_ALBUM_OLD 1014
+#define IDC_STATIC_GENRE_OLD 1015
+#define IDC_STATIC_YEAR_OLD 1016
+#define IDC_STATIC_DISC_OLD 1017
+#define IDC_TITLE_NEW 1018
+#define IDC_TRACK_NEW 1019
+#define IDC_DISC_NEW 1020
+#define IDC_ARTIST_NEW 1021
+#define IDC_ALBUM_NEW 1022
+#define IDC_GENRE_NEW 1023
+#define IDC_YEAR_NEW 1024
+#define IDC_STATIC_DISC_NEW 1025
+#define IDC_STATIC_TITLE_NEW 1026
+#define IDC_STATIC_TRACK_NEW 1027
+#define IDC_STATIC_ARTIST_NEW 1028
+#define IDC_STATIC_ALBUM_NEW 1029
+#define IDC_STATIC_GENRE_NEW 1030
+#define IDC_STATIC_YEAR_NEW 1031
+#define IDC_FILENAME 1032
+#define IDC_STATIC_FILENAME 1033
+#define IDC_STATIC_OLDINFO 1034
+#define IDC_STATIC_NEWINFO 1035
+#define IDC_PROGRESS 1036
+#define IDC_PUBLISHER_OLD 1036
+#define IDC_BRANDTXT 1037
+#define IDC_ALBUMARTIST_OLD 1038
+#define IDC_STATIC_ALBUMARTIST_OLD 1039
+#define IDC_STATIC_PUBLISHER_OLD 1040
+#define IDC_STATIC_ALBUMARTIST_NEW 1041
+#define IDC_STATIC_PUBLISHER_NEW 1042
+#define IDC_PUBLISHER_NEW 1043
+#define IDC_ALBUMARTIST_NEW 1044
+#define IDC_STATIC_COMPOSER_OLD 1045
+#define IDC_COMPOSER_OLD 1046
+#define IDC_STATIC_COMPOSER_NEW 1047
+#define IDC_COMPOSER_NEW 1048
+#define IDC_STATIC_BPM_OLD 1049
+#define IDC_BPM_OLD 1050
+#define IDC_STATIC_BPM_NEW 1051
+#define IDC_BPM_NEW 1052
+#define IDS_PLUGINNAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 118
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1041
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_autotag/tagger.cpp b/Src/Plugins/Library/ml_autotag/tagger.cpp
new file mode 100644
index 00000000..c0441500
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/tagger.cpp
@@ -0,0 +1,831 @@
+#include "main.h"
+#include "tagger.h"
+
+#define COL_FILENAME 0
+#define COL_STATUS 1
+
+static INT_PTR CALLBACK autotagger_saving_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+static const int ids[] =
+{
+ IDC_TITLE_OLD,
+ IDC_TRACK_OLD,
+ IDC_ARTIST_OLD,
+ IDC_ALBUM_OLD,
+ IDC_GENRE_OLD,
+ IDC_YEAR_OLD,
+ IDC_DISC_OLD,
+ IDC_PUBLISHER_OLD,
+ IDC_ALBUMARTIST_OLD,
+ IDC_COMPOSER_OLD,
+ IDC_BPM_OLD,
+ IDC_TITLE_NEW,
+ IDC_TRACK_NEW,
+ IDC_ARTIST_NEW,
+ IDC_ALBUM_NEW,
+ IDC_GENRE_NEW,
+ IDC_YEAR_NEW,
+ IDC_DISC_NEW,
+ IDC_PUBLISHER_NEW,
+ IDC_ALBUMARTIST_NEW,
+ IDC_COMPOSER_NEW,
+ IDC_BPM_NEW,
+ IDC_FILENAME,
+};
+static const int ids_static[] =
+{
+ IDC_STATIC_OLDINFO,
+ IDC_STATIC_TITLE_OLD,
+ IDC_STATIC_TRACK_OLD,
+ IDC_STATIC_ARTIST_OLD,
+ IDC_STATIC_ALBUM_OLD,
+ IDC_STATIC_GENRE_OLD,
+ IDC_STATIC_YEAR_OLD,
+ IDC_STATIC_DISC_OLD,
+ IDC_STATIC_ALBUMARTIST_OLD,
+ IDC_STATIC_PUBLISHER_OLD,
+ IDC_STATIC_COMPOSER_OLD,
+ IDC_STATIC_BPM_OLD,
+ IDC_STATIC_NEWINFO,
+ IDC_STATIC_DISC_NEW,
+ IDC_STATIC_TITLE_NEW,
+ IDC_STATIC_TRACK_NEW,
+ IDC_STATIC_ARTIST_NEW,
+ IDC_STATIC_ALBUM_NEW,
+ IDC_STATIC_GENRE_NEW,
+ IDC_STATIC_YEAR_NEW,
+ IDC_STATIC_ALBUMARTIST_NEW,
+ IDC_STATIC_PUBLISHER_NEW,
+ IDC_STATIC_COMPOSER_NEW,
+ IDC_STATIC_BPM_NEW,
+ IDC_STATIC_FILENAME,
+};
+
+static bool GetRole(ICddbDisc *disc, BSTR roleId, BSTR *str)
+{
+ if (!roleId || !*roleId)
+ return false;
+
+ if (!disc)
+ return false;
+
+ ICddbCreditsPtr credits;
+ disc->get_Credits(&credits);
+ if (credits)
+ {
+ long creditCount;
+ credits->get_Count(&creditCount);
+ for (long c = 0;c < creditCount;c++)
+ {
+ ICddbCreditPtr credit;
+ credits->GetCredit(c + 1, &credit);
+ if (credit)
+ {
+ BSTR thisRole;
+ credit->get_Id(&thisRole);
+ if (!wcscmp(thisRole, roleId))
+ {
+ credit->get_Name(str);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static void EnableBottomView(HWND hwndDlg, int enable)
+{
+ for (int i=0; i<(sizeof(ids_static)/sizeof(int)); i++)
+ EnableWindow(GetDlgItem(hwndDlg,ids_static[i]),enable);
+
+ for (int i=0; i<(sizeof(ids)/sizeof(int)); i++)
+ SetDlgItemText(hwndDlg,ids[i],L"");
+}
+
+INT_PTR CALLBACK autotagger_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowLongPtr( hwndDlg, GWLP_USERDATA, (LONG)(LONG_PTR)lParam );
+ Tagger *t = (Tagger *)lParam;
+ t->hwndDlg = hwndDlg;
+ HWND hlist = GetDlgItem( hwndDlg, IDC_LIST );
+ t->listview.setwnd( hlist );
+
+ EnableBottomView( hwndDlg, FALSE );
+
+ ICddbFileInfoListPtr infolist;
+ infolist.CreateInstance( CLSID_CddbFileInfoList );
+
+ ListView_SetExtendedListViewStyle( GetDlgItem( hwndDlg, IDC_LIST ), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP );
+
+ t->listview.AddCol( WASABI_API_LNGSTRINGW( IDS_FILENAME ), 475 );
+ t->listview.AddCol( WASABI_API_LNGSTRINGW( IDS_STATUS ), 100 );
+
+ for ( size_t i = 0; i < t->list.size(); i++ )
+ {
+ ICddbFileInfoPtr info;
+ info.CreateInstance( CLSID_CddbFileInfo );
+ info->put_Filename( (wchar_t *)t->list[ i ]->filename );
+ infolist->AddFileInfo( info );
+ t->list[ i ]->num = (int)i;
+ t->listview.InsertItem( (int)i, t->list[ i ]->filename, (LPARAM)t->list[ i ] );
+ t->listview.SetItemText( (int)i, COL_STATUS, WASABI_API_LNGSTRINGW( IDS_WAITING ) );
+ ListView_SetCheckState( hlist, (int)i, FALSE );
+ }
+
+ t->musicid->LibraryID( infolist, MUSICID_LOOKUP_ASYNC | MUSICID_RETURN_SINGLE | MUSICID_GET_TAG_FROM_APP | MUSICID_GET_FP_FROM_APP | MUSICID_PREFER_WF_MATCHES );
+ }
+ break;
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->code)
+ {
+ case LVN_ITEMCHANGED:
+ {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if ((lv->uNewState ^ lv->uOldState) & LVIS_SELECTED)
+ SendMessage(hwndDlg,WM_USER,0,0);
+ }
+ break;
+ }
+ break;
+ case WM_USER:
+ {
+ int f = ListView_GetNextItem(GetDlgItem(hwndDlg,IDC_LIST),-1,LVIS_FOCUSED);
+ if (f < 0)
+ {
+ EnableBottomView(hwndDlg,FALSE);
+ return -1;
+ }
+ EnableBottomView(hwndDlg,TRUE);
+ Tagger * t = (Tagger *)(LONG_PTR)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ TagItem * ti = t->list[f];
+ SetDlgItemText(hwndDlg,IDC_FILENAME,ti->filename);
+
+ BSTR bstr = 0;
+ if (ti->oldTag)
+ {
+ ICddbFileTag2_5Ptr tag2_5 = NULL;
+ ti->oldTag->QueryInterface(&tag2_5);
+
+#define PUTINFO(x,y) ti->oldTag->get_ ## x ##(&bstr); if(bstr && *bstr) SetDlgItemText(hwndDlg,y,bstr); if(bstr) SysFreeString(bstr); bstr=0;
+#define PUTINFO2(x,y) if (tag2_5) { tag2_5->get_ ## x ##(&bstr); if(bstr && *bstr) SetDlgItemText(hwndDlg,y,bstr); if(bstr) SysFreeString(bstr); bstr=0; }
+ PUTINFO(Title, IDC_TITLE_OLD);
+ PUTINFO(TrackPosition, IDC_TRACK_OLD);
+ PUTINFO(LeadArtist, IDC_ARTIST_OLD);
+ PUTINFO(Album, IDC_ALBUM_OLD);
+ PUTINFO(Genre, IDC_GENRE_OLD);
+ PUTINFO(Year, IDC_YEAR_OLD);
+ PUTINFO(PartOfSet, IDC_DISC_OLD);
+ PUTINFO(Label, IDC_PUBLISHER_OLD);
+ PUTINFO2(DiscArtist, IDC_ALBUMARTIST_OLD);
+ PUTINFO2(Composer, IDC_COMPOSER_OLD);
+ PUTINFO(BeatsPerMinute, IDC_BPM_OLD);
+#undef PUTINFO
+#undef PUTINFO2
+ }
+ if (ti->newTag)
+ {
+ ICddbFileTag2_5Ptr tag2_5 = NULL;
+ ICddbDisc2_5Ptr pDisc2_5 = NULL;
+
+ ti->newTag->QueryInterface(&tag2_5);
+ if (ti->disc)
+ ti->disc->QueryInterface(&pDisc2_5);
+
+#define PUTINFO(x,y) ti->newTag->get_ ## x ##(&bstr); if(bstr && *bstr) SetDlgItemText(hwndDlg,y,bstr); if(bstr) SysFreeString(bstr); bstr=0;
+#define PUTINFO2(x,y) if (tag2_5) { tag2_5->get_ ## x ##(&bstr); if(bstr && *bstr) SetDlgItemText(hwndDlg,y,bstr); if(bstr) SysFreeString(bstr); bstr=0; }
+#define PUTROLE(x,y) if (GetRole(ti->disc, x, &bstr) && bstr && *bstr) SetDlgItemText(hwndDlg,y,bstr); if(bstr) SysFreeString(bstr); bstr=0;
+ PUTINFO(Title, IDC_TITLE_NEW);
+ PUTINFO(TrackPosition, IDC_TRACK_NEW);
+ PUTINFO(LeadArtist, IDC_ARTIST_NEW);
+ PUTINFO(Album, IDC_ALBUM_NEW);
+
+ if (pDisc2_5 == NULL
+ || (FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(3, &bstr))
+ && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(2, &bstr))
+ && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(1, &bstr))
+ && FAILED(pDisc2_5->get_V2GenreStringPrimary(&bstr)))
+ )
+ {
+ PUTINFO(Genre, IDC_GENRE_NEW);
+ }
+ if (bstr && *bstr)
+ SetDlgItemText(hwndDlg,IDC_GENRE_NEW,bstr);
+ if (bstr) SysFreeString(bstr); bstr=0;
+
+ PUTINFO(Year, IDC_YEAR_NEW);
+ PUTINFO(PartOfSet, IDC_DISC_NEW);
+ PUTINFO(Label, IDC_PUBLISHER_NEW);
+ PUTINFO2(DiscArtist, IDC_ALBUMARTIST_NEW);
+ PUTINFO2(Composer, IDC_COMPOSER_NEW);
+ PUTINFO(BeatsPerMinute, IDC_BPM_NEW);
+#undef PUTINFO
+#undef PUTINFO2
+#undef PUTROLE
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_CHECK_NONE:
+ case IDC_CHECK_ALL:
+ {
+ BOOL check = (LOWORD(wParam) == IDC_CHECK_ALL);
+ HWND hlist = GetDlgItem(hwndDlg,IDC_LIST);
+ size_t l = ListView_GetItemCount(hlist);
+ for (size_t i=0; i < l; i++)
+ ListView_SetCheckState(hlist,(int)i,check);
+ }
+ break;
+ case IDOK:
+ if (WASABI_API_DIALOGBOXW(IDD_SAVING,hwndDlg,autotagger_saving_dlgproc))
+ break;
+ case IDCANCEL:
+ {
+ Tagger * t = (Tagger *)(LONG_PTR)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ t->abort=1;
+ t->musicid->LibraryIDStop(0);
+ //t->musicid->LibraryIDClear();
+ }
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ Tagger * t = (Tagger *)(LONG_PTR)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ if (t->icp) t->icp->Unadvise(t->m_dwCookie);
+ t->musicid->Shutdown();
+ t->musicid->Release();
+ delete t;
+ }
+ break;
+ case WM_CLOSE:
+ return SendMessage(hwndDlg,WM_COMMAND,IDCANCEL,0);
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK autotagger_saving_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static int i;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ i=0;
+ SetTimer(hwndDlg,0,20,NULL);
+ Tagger * t = (Tagger *)(LONG_PTR)GetWindowLongPtr(GetParent(hwndDlg),GWLP_USERDATA);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETRANGE,0,MAKELPARAM(0, t->list.size()));
+ }
+ break;
+ case WM_TIMER:
+ if (wParam == 0)
+ {
+ KillTimer(hwndDlg,0);
+ HWND hlist = GetDlgItem(GetParent(hwndDlg),IDC_LIST);
+ Tagger * t = (Tagger *)(LONG_PTR)GetWindowLongPtr(GetParent(hwndDlg),GWLP_USERDATA);
+ int n = (int)t->list.size();
+ for (;;)
+ {
+ if (i >= n)
+ {
+ EndDialog(hwndDlg,0);
+ return 0;
+ }
+ if (ListView_GetCheckState(hlist,i))
+ {
+ TagItem *ti = t->list[i];
+ const wchar_t *fn = ti->filename;
+ BSTR bstr=0;
+ if (ti->newTag)
+ {
+ ICddbFileTag2_5Ptr tag2_5 = NULL;
+ ICddbDisc2_5Ptr pDisc2_5 = NULL;
+
+ ti->newTag->QueryInterface(&tag2_5);
+ if (ti->disc)
+ ti->disc->QueryInterface(&pDisc2_5);
+
+#define PUTINFO(x,y) ti->newTag->get_ ## x ## (&bstr); if(bstr && *bstr) SetFileInfo(fn,y,bstr); if(bstr) SysFreeString(bstr); bstr=0;
+#define PUTINFO2(x,y) if (tag2_5) { tag2_5->get_ ## x ## (&bstr); if(bstr && *bstr) SetFileInfo(fn,y,bstr); if(bstr) SysFreeString(bstr); bstr=0; }
+#define PUTROLE(x,y) if (GetRole(ti->disc, x, &bstr) && bstr && *bstr) SetFileInfo(fn,y,bstr); if(bstr) SysFreeString(bstr); bstr=0;
+ PUTINFO(LeadArtist, L"artist");
+ PUTINFO(Album, L"album");
+ PUTINFO(Title, L"title");
+ if (pDisc2_5 == NULL
+ || (FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(3, &bstr))
+ && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(2, &bstr))
+ && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(1, &bstr))
+ && FAILED(pDisc2_5->get_V2GenreStringPrimary(&bstr)))
+ )
+ {
+ PUTINFO(Genre, L"genre");
+ }
+ if (bstr && *bstr)
+ SetFileInfo(fn,L"genre",bstr);
+ if (bstr) SysFreeString(bstr); bstr=0;
+ // benski> CUT: PUTINFO(Genre, L"genre");
+ PUTINFO(Year, L"year");
+ PUTINFO(Label, L"publisher");
+ PUTINFO(BeatsPerMinute, L"bpm");
+ PUTINFO(TrackPosition, L"track");
+ PUTINFO(PartOfSet, L"disc");
+ PUTINFO2(Composer, L"composer");
+ PUTINFO2(DiscArtist, L"albumartist");
+ PUTINFO(ISRC, L"ISRC");
+ //PUTROLE(L"147", L"remixing");
+ PUTINFO(FileId, L"GracenoteFileID");
+ PUTINFO2(ExtDataSerialized, L"GracenoteExtData");
+
+ WriteFileInfo(fn);
+#undef PUTINFO
+#undef PUTINFO2
+#undef PUTROLE
+ }
+ i++;
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETPOS,i,0);
+ SetTimer(hwndDlg,0,20,NULL);
+ return 0;
+ }
+ else i++;
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETPOS,i,0);
+ }
+ }
+ break;
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDCANCEL) EndDialog(hwndDlg,1);
+ break;
+ case WM_CLOSE:
+ EndDialog(hwndDlg,1);
+ break;
+ }
+ return 0;
+}
+
+TagItem::TagItem(const char * filename0) : freefn(true), num(0)
+{
+ filename = AutoWideDup(filename0);
+ init();
+}
+
+TagItem::TagItem(const wchar_t * filename0, bool copy) : freefn(copy), num(0)
+{
+ filename = copy?_wcsdup(filename0):filename0;
+ init();
+}
+
+TagItem::~TagItem()
+{
+ if (freefn) free((void*)filename);
+}
+
+void TagItem::init()
+{
+ last_match = MUSICID_MATCH_NONE;
+ oldTag.CreateInstance(CLSID_CddbID3Tag);
+}
+
+void TagItem::SetStatus(CddbMusicIDStatus status, HWND hwndDlg)
+{
+ int s[] = {0, IDS_ERROR, IDS_PROCESSING, IDS_LOOKINGUP, IDS_LOOKINGUP, 0, IDS_QUERYING, IDS_QUERIED, IDS_PROCESSING, IDS_PROCESSED, IDS_ANALYZING, IDS_QUERYING};
+ if (status < sizeof(s)/sizeof(int))
+ {
+ wchar_t buf[100] = {0};
+ SetStatus(WASABI_API_LNGSTRINGW_BUF(s[status],buf,100),hwndDlg);
+ }
+}
+
+void TagItem::SetStatus(const wchar_t *text, HWND hwndDlg)
+{
+ LVITEM lvi = {0, };
+ lvi.iItem = num;
+ lvi.iSubItem = COL_STATUS;
+ lvi.mask = LVIF_TEXT;
+ lvi.pszText = (LPTSTR)text;
+ lvi.cchTextMax = lstrlenW(text);
+ SendMessageW(GetDlgItem(hwndDlg,IDC_LIST), LVM_SETITEMW, 0, (LPARAM)&lvi);
+}
+
+void TagItem::Check(HWND hwndDlg, BOOL check)
+{
+ HWND hlist = GetDlgItem(hwndDlg,IDC_LIST);
+ ListView_SetCheckState(hlist,(WPARAM)num,(LPARAM)check);
+
+}
+
+void TagItem::TagUpdate(HWND hwndDlg)
+{
+ int f = ListView_GetNextItem(GetDlgItem(hwndDlg,IDC_LIST),-1,LVIS_FOCUSED);
+ if (f == num)
+ SendMessage(hwndDlg,WM_USER,0,0);
+}
+
+static IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid);
+
+Tagger::Tagger(std::vector<TagItem*> &_list, ICDDBMusicIDManager3 *musicid) : hwndDlg(0), abort(0), musicid(musicid), icp(0), m_dwCookie(0)
+{
+ //list.own(_list);
+ for (auto obj : list)
+ {
+ delete obj;
+ }
+ list.clear();
+ list.assign(_list.begin(), _list.end());
+ _list.clear();
+
+
+ icp = GetConnectionPoint(musicid, DIID__ICDDBMusicIDManagerEvents);
+ if (icp)
+ {
+ icp->Advise(static_cast<IDispatch *>(this), &m_dwCookie);
+ icp->Release();
+ }
+}
+
+Tagger::~Tagger()
+{
+}
+
+TagItem *Tagger::FindTagItem( const wchar_t *filename )
+{
+ if ( !filename )
+ return NULL;
+
+ for ( TagItem *l_tag_item : list )
+ if ( !_wcsicmp( l_tag_item->filename, filename ) )
+ return l_tag_item;
+
+ return NULL;
+}
+
+HRESULT Tagger::OnTrackIDStatusUpdate(CddbMusicIDStatus Status, BSTR filename, long* Abort)
+{
+ TagItem *t = FindTagItem(filename);
+ if (t) t->SetStatus(Status,hwndDlg);
+ *Abort = abort;
+ return S_OK;
+}
+
+HRESULT Tagger::OnTrackIDComplete(CddbMusicIDMatchCode match_code, ICddbFileInfo* pInfoIn, ICddbFileInfoList* pListOut)
+{
+ if (!pInfoIn || !pListOut)
+ return S_OK;
+
+ BSTR filename;
+ pInfoIn->get_Filename(&filename);
+
+ TagItem *t = FindTagItem(filename);
+ if (!t) return S_OK;
+
+ if (match_code == MUSICID_MATCH_FUZZY)
+ {
+ if (t->last_match == MUSICID_MATCH_EXACT)
+ return S_OK;
+ t->Check(hwndDlg, FALSE);
+ }
+ else if (match_code == MUSICID_MATCH_EXACT)
+ {
+ t->Check(hwndDlg, TRUE);
+ }
+ else if (match_code == MUSICID_MATCH_NONE)
+ {
+ if (t->last_match == MUSICID_MATCH_EXACT)
+ return S_OK;
+ }
+
+ t->last_match = match_code;
+
+ wchar_t buf[100] = {0};
+
+ switch(match_code)
+ {
+ case MUSICID_MATCH_NONE:
+ t->SetStatus(WASABI_API_LNGSTRINGW_BUF(IDS_NOMATCH,buf,100),hwndDlg);
+ return S_OK;
+ case MUSICID_MATCH_FUZZY:
+ t->SetStatus(WASABI_API_LNGSTRINGW_BUF(IDS_FUZZY,buf,100),hwndDlg);
+ break;
+ case MUSICID_MATCH_ERROR:
+ t->SetStatus(WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,buf,100),hwndDlg);
+ return S_OK;
+ default:
+ t->SetStatus(WASABI_API_LNGSTRINGW_BUF(IDS_PROCESSED,buf,100),hwndDlg);
+ break;
+ }
+ /*
+ if (match_code <= 1)
+ {
+ t->SetStatus(WASABI_API_LNGSTRINGW_BUF(IDS_NOMATCH,buf,100),hwndDlg); return S_OK;
+ }
+ else t->SetStatus(WASABI_API_LNGSTRINGW_BUF(IDS_PROCESSED,buf,100),hwndDlg);
+*/
+ long num;
+ pListOut->get_Count(&num);
+ if (!num) return S_OK;
+
+ ICddbFileInfoPtr infotag;
+ pListOut->GetFileInfo(1,&infotag);
+ if (infotag)
+ {
+ infotag->get_Disc(&t->disc);
+ infotag->get_Tag(&t->newTag);
+ t->TagUpdate(hwndDlg);
+ }
+
+ return S_OK;
+}
+
+HRESULT Tagger::OnAlbumIDStatusUpdate(CddbMusicIDStatus Status, BSTR filename, long current_file, long total_files, long* Abort)
+{
+ TagItem *t = FindTagItem(filename);
+ if (t) t->SetStatus(Status,hwndDlg);
+ *Abort = abort;
+ return S_OK;
+}
+
+HRESULT Tagger::OnAlbumIDComplete(LONG match_code, ICddbFileInfoList* pListIn, ICddbFileInfoLists* pListsOut)
+{
+ if (!pListIn || !pListsOut)
+ return 0;
+
+ long c1=0,c2=0;
+ pListIn->get_Count(&c1);
+ pListsOut->get_Count(&c2);
+
+ if (c2<=0) return 0;
+
+ for (int i=1; i<=c2; i++)
+ {
+ ICddbFileInfoListPtr list;
+ pListsOut->GetFileInfoList(i,&list);
+ list->get_Count(&c1);
+
+
+ for (int i=1; i<=c1; i++)
+ {
+ ICddbFileInfoPtr pInfoIn;
+ list->GetFileInfo(i,&pInfoIn);
+
+ ICddbFileInfoListPtr pListOut;
+ pListOut.CreateInstance(CLSID_CddbFileInfoList);
+
+ ICddbFileInfoPtr temp;
+ list->GetFileInfo(i,&temp);
+ pListOut->AddFileInfo(temp);
+ CddbMusicIDMatchCode match;
+ pInfoIn->get_MusicIDMatchCode(&match);
+ OnTrackIDComplete(match,pInfoIn,pListOut);
+ }
+ }
+ return S_OK;
+}
+
+HRESULT Tagger::OnLibraryIDListStarted(ICddbFileInfoList* pList, long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError, long *Abort)
+{
+ *Abort = abort;
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BRANDTXT),SW_SHOWNA);
+ return S_OK;
+}
+
+HRESULT Tagger::OnLibraryIDListComplete(ICddbFileInfoList* pList, long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError, long *Abort)
+{
+ *Abort = abort;
+ return S_OK;
+}
+
+HRESULT Tagger::OnLibraryIDComplete(long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError)
+{
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BRANDTXT),SW_HIDE);
+ return S_OK;
+}
+
+HRESULT Tagger::FillTag(ICddbFileInfo *info, BSTR filename)
+{
+ TagItem *t = FindTagItem(filename);
+ if (!t) return E_FAIL;
+ ICddbID3Tag * infotag = t->oldTag;
+
+ ICddbFileTag2_5Ptr tag2_5 = NULL;
+ infotag->QueryInterface(&tag2_5);
+ itemRecordW *record = AGAVE_API_MLDB->GetFile(filename);
+ if (record && infotag && tag2_5)
+ {
+ wchar_t itemp[64] = {0};
+#define PUTINFO(y, x) if(record-> ## x) infotag->put_ ## y ##(record-> ## x)
+#define PUTINFOI(y, x) if(record-> ## x > 0) infotag->put_ ## y ##(_itow(record-> ## x,itemp,10))
+#define PUTINFO2(y, x) if(record-> ## x) tag2_5->put_ ## y ##(record-> ## x)
+ PUTINFO(LeadArtist, artist);
+ PUTINFO(Album, album);
+ PUTINFO(Title, title);
+ PUTINFO(Genre, genre);
+ PUTINFOI(Year, year);
+ PUTINFO(Label, publisher);
+ PUTINFOI(BeatsPerMinute, bpm);
+ PUTINFOI(TrackPosition, track);
+ PUTINFOI(PartOfSet, disc);
+ PUTINFO2(Composer, composer);
+ PUTINFO2(DiscArtist, albumartist);
+#undef PUTINFO
+#undef PUTINFOI
+#undef PUTINFO2
+ AGAVE_API_MLDB->FreeRecord(record);
+ }
+ else
+ {
+ wchar_t buf[2048]=L"";
+#define PUTINFO(y, x) buf[0]=0; GetFileInfo(filename,x,buf,2048); if(buf[0]) if (infotag) infotag->put_ ## y ##(buf);
+#define PUTINFO2(y, x) buf[0]=0; GetFileInfo(filename,x,buf,2048); if(buf[0]) if (tag2_5) tag2_5->put_ ## y ##(buf);
+ PUTINFO(LeadArtist, L"artist");
+ PUTINFO(Album, L"album");
+ PUTINFO(Title, L"title");
+ PUTINFO(Genre, L"genre");
+ PUTINFO(Year, L"year");
+ PUTINFO(Label, L"publisher");
+ PUTINFO(BeatsPerMinute, L"bpm");
+ PUTINFO(TrackPosition, L"track");
+ PUTINFO(PartOfSet, L"disc");
+ PUTINFO2(Composer, L"composer");
+ PUTINFO2(DiscArtist, L"albumartist");
+#undef PUTINFO
+#undef PUTINFO2
+ }
+ t->TagUpdate(hwndDlg);
+ info->put_Tag(infotag);
+ return S_OK;
+}
+
+// com shit
+STDMETHODIMP STDMETHODCALLTYPE Tagger::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, __uuidof(_ICDDBMusicIDManagerEvents)))
+ *ppvObject = (_ICDDBMusicIDManagerEvents *)this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE Tagger::AddRef(void)
+{
+ return 1;
+}
+
+ULONG STDMETHODCALLTYPE Tagger::Release(void)
+{
+ return 0;
+}
+
+HRESULT STDMETHODCALLTYPE Tagger::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case 1: // OnTrackIDStatusUpdate, params: CddbMusicIDStatus Status, BSTR filename, long* Abort
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ BSTR filename = pdispparams->rgvarg[1].bstrVal;
+ if (!filename) return E_INVALIDARG;
+ CddbMusicIDStatus status = (CddbMusicIDStatus)pdispparams->rgvarg[2].lVal;
+ return OnTrackIDStatusUpdate(status,filename,abort);
+ }
+ case 2: // OnAlbumIDStatusUpdate, params: CddbMusicIDStatus Status, BSTR filename, long current_file, long total_files, long* Abort
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ long total_files = pdispparams->rgvarg[1].lVal;
+ long current_file= pdispparams->rgvarg[2].lVal;
+ CddbMusicIDStatus status = (CddbMusicIDStatus)pdispparams->rgvarg[4].lVal;
+ BSTR filename = pdispparams->rgvarg[3].bstrVal;
+ if (!filename) return E_INVALIDARG;
+ return OnAlbumIDStatusUpdate(status,filename,current_file,total_files,abort);
+ }
+ break;
+
+ case 3: // OnTrackIDComplete, params: LONG match_code, ICddbFileInfo* pInfoIn, ICddbFileInfoList* pListOut
+ {
+ IDispatch *disp1 =pdispparams->rgvarg[0].pdispVal;
+ IDispatch *disp2 =pdispparams->rgvarg[1].pdispVal;
+ if (!disp1 || !disp2) return E_INVALIDARG;
+ CddbMusicIDMatchCode match_code = (CddbMusicIDMatchCode)pdispparams->rgvarg[2].lVal;
+
+ ICddbFileInfoPtr pInfoIn;
+ ICddbFileInfoListPtr matchList;
+ disp1->QueryInterface(&matchList);
+ disp2->QueryInterface(&pInfoIn);
+
+ return OnTrackIDComplete(match_code,pInfoIn,matchList);
+ }
+ break;
+ case 4: // OnAlbumIDComplete, params: LONG match_code, ICddbFileInfoList* pListIn, ICddbFileInfoLists* pListsOut
+ {
+ IDispatch *disp1 =pdispparams->rgvarg[0].pdispVal;
+ IDispatch *disp2 =pdispparams->rgvarg[1].pdispVal;
+ if (!disp1 || !disp2) return E_INVALIDARG;
+ long match_code = pdispparams->rgvarg[2].lVal;
+
+ ICddbFileInfoListPtr pListIn;
+ ICddbFileInfoListsPtr pListsOut;
+ disp1->QueryInterface(&pListsOut);
+ disp2->QueryInterface(&pListIn);
+
+ return OnAlbumIDComplete(match_code,pListIn,pListsOut);
+ }
+ break;
+ case 7: // OnLibraryIDListStarted, params: ICddbFileInfoList* pList, long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError, long *Abort
+ {
+ IDispatch *disp1 =pdispparams->rgvarg[7].pdispVal;
+ if (!disp1) return E_INVALIDARG;
+ ICddbFileInfoListPtr pList;
+ disp1->QueryInterface(&pList);
+ return OnLibraryIDListStarted(pList,pdispparams->rgvarg[6].lVal,pdispparams->rgvarg[5].lVal,pdispparams->rgvarg[4].lVal,pdispparams->rgvarg[3].lVal,pdispparams->rgvarg[2].lVal,pdispparams->rgvarg[1].lVal,pdispparams->rgvarg[0].plVal);
+ }
+ break;
+ case 8: // OnLibraryIDListComplete, params: ICddbFileInfoList* pList, long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError, long *Abort
+ {
+ IDispatch *disp1 =pdispparams->rgvarg[7].pdispVal;
+ if (!disp1) return E_INVALIDARG;
+ ICddbFileInfoListPtr pList;
+ disp1->QueryInterface(&pList);
+ return OnLibraryIDListComplete(pList,pdispparams->rgvarg[6].lVal,pdispparams->rgvarg[5].lVal,pdispparams->rgvarg[4].lVal,pdispparams->rgvarg[3].lVal,pdispparams->rgvarg[2].lVal,pdispparams->rgvarg[1].lVal,pdispparams->rgvarg[0].plVal);
+ }
+ break;
+ case 9: // OnLibraryIDComplete, params: long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError
+ {
+ return OnLibraryIDComplete(pdispparams->rgvarg[5].lVal,pdispparams->rgvarg[4].lVal,pdispparams->rgvarg[3].lVal,pdispparams->rgvarg[2].lVal,pdispparams->rgvarg[1].lVal,pdispparams->rgvarg[0].lVal);
+ }
+ break;
+ case 10: // OnGetFingerprintInfo
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ IDispatch *disp = pdispparams->rgvarg[1].pdispVal;
+ BSTR filename = pdispparams->rgvarg[2].bstrVal;
+ if (!disp || !filename) return E_INVALIDARG;
+
+ ICddbFileInfo *info;
+ disp->QueryInterface(&info);
+ HRESULT hr = AGAVE_API_GRACENOTE->CreateFingerprint(musicid, AGAVE_API_DECODE, info, filename, abort);
+ info->Release();
+ return hr;
+ }
+ break;
+ case 11: // OnGetTagInfo
+ {
+ //long *Abort = pdispparams->rgvarg[0].plVal;
+ IDispatch *disp = pdispparams->rgvarg[1].pdispVal;
+ BSTR filename = pdispparams->rgvarg[2].bstrVal;
+ if (!disp || !filename) return E_INVALIDARG;
+ ICddbFileInfo *info;
+ disp->QueryInterface(&info);
+ HRESULT hr = FillTag(info, filename);
+ info->Release();
+ return hr;
+ }
+ break;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT STDMETHODCALLTYPE Tagger::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN; return DISP_E_UNKNOWNNAME;
+}
+HRESULT STDMETHODCALLTYPE Tagger::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+HRESULT STDMETHODCALLTYPE Tagger::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+static IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid)
+{
+ if (!punk)
+ return 0;
+
+ IConnectionPointContainer *pcpc=0;
+ IConnectionPoint *pcp = 0;
+
+ HRESULT hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **) & pcpc);
+ if (SUCCEEDED(hr))
+ {
+ pcpc->FindConnectionPoint(riid, &pcp);
+ pcpc->Release();
+ }
+ return pcp;
+}
diff --git a/Src/Plugins/Library/ml_autotag/tagger.h b/Src/Plugins/Library/ml_autotag/tagger.h
new file mode 100644
index 00000000..c479288d
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/tagger.h
@@ -0,0 +1,68 @@
+#ifndef _NULLSOFT_AUTOTAGGER_TAGGER_H_
+#define _NULLSOFT_AUTOTAGGER_TAGGER_H_
+#include <vector>
+
+class TagItem
+{
+public:
+ TagItem(const char * filename);
+ TagItem(const wchar_t * filename, bool copy=true);
+ ~TagItem();
+ void init();
+ void SetStatus(CddbMusicIDStatus status, HWND hwndDlg);
+ void SetStatus(const wchar_t *str, HWND hwndDlg);
+ void TagUpdate(HWND hwndDlg);
+ void Check(HWND, BOOL);
+
+ const wchar_t *filename;
+ bool freefn;
+ int num;
+
+ CddbMusicIDMatchCode last_match;
+
+ ICddbID3TagPtr oldTag;
+ ICddbFileTagPtr newTag;
+ ICddbDisc2Ptr disc;
+};
+
+class Tagger : public _ICDDBMusicIDManagerEvents
+{
+public:
+ Tagger(std::vector<TagItem*> &list, ICDDBMusicIDManager3 *musicid); // 0 = trackid, 1 = albumid, 2 = libraryid
+ ~Tagger();
+
+ TagItem* FindTagItem(const wchar_t * filename);
+
+protected:
+ HRESULT OnTrackIDStatusUpdate(CddbMusicIDStatus Status, BSTR filename, long* Abort);
+ HRESULT OnAlbumIDStatusUpdate(CddbMusicIDStatus Status, BSTR filename, long current_file, long total_files, long* Abort);
+ HRESULT OnTrackIDComplete(CddbMusicIDMatchCode match_code, ICddbFileInfo* pInfoIn, ICddbFileInfoList* pListOut);
+ HRESULT OnAlbumIDComplete(LONG match_code, ICddbFileInfoList* pListIn, ICddbFileInfoLists* pListsOut);
+ HRESULT OnLibraryIDListStarted(ICddbFileInfoList* pList, long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError, long *Abort);
+ HRESULT OnLibraryIDListComplete(ICddbFileInfoList* pList, long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError, long *Abort);
+ HRESULT OnLibraryIDComplete(long FilesComplete, long FilesTotal, long FilesExact, long FilesFuzzy, long FilesNoMatch, long FilesError);
+
+ HRESULT FillTag(ICddbFileInfo *info, BSTR filename);
+
+ // com shit
+ STDMETHODIMP STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObject);
+ ULONG STDMETHODCALLTYPE AddRef(void);
+ ULONG STDMETHODCALLTYPE Release(void);
+ HRESULT STDMETHODCALLTYPE Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+ HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR * pctinfo);
+
+public:
+ HWND hwndDlg;
+ std::vector<TagItem*> list;
+ ICDDBMusicIDManager3 *musicid;
+ long abort;
+ W_ListView listview;
+ IConnectionPoint *icp;
+ DWORD m_dwCookie;
+};
+
+extern INT_PTR CALLBACK autotagger_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+#endif // _NULLSOFT_AUTOTAGGER_TAGGER_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_autotag/version.rc2 b/Src/Plugins/Library/ml_autotag/version.rc2
new file mode 100644
index 00000000..bb228ccb
--- /dev/null
+++ b/Src/Plugins/Library/ml_autotag/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,0,8,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", "2,0,8,0"
+ VALUE "InternalName", "Nullsoft Auto-Tagger"
+ VALUE "LegalCopyright", "Copyright © 2007-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_autotag.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_bookmarks/api__ml_bookmarks.h b/Src/Plugins/Library/ml_bookmarks/api__ml_bookmarks.h
new file mode 100644
index 00000000..2f9ee071
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/api__ml_bookmarks.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_API_ML_BOOKMARKS_H
+#define NULLSOFT_API_ML_BOOKMARKS_H
+
+#include "api/service/waServiceFactory.h"
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include "api/application/api_application.h"
+#define WASABI_API_APP applicationApi
+
+#endif // !NULLSOFT_API_ML_BOOKMARKS_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_bookmarks/bookmark.cpp b/Src/Plugins/Library/ml_bookmarks/bookmark.cpp
new file mode 100644
index 00000000..c7ad05e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/bookmark.cpp
@@ -0,0 +1,32 @@
+/** (c) Nullsoft, Inc. C O N F I D E N T I A L
+ ** Filename:
+ ** Project:
+ ** Description:
+ ** Author:
+ ** Created:
+ **/
+#include "bookmark.h"
+
+BookmarkWriter::BookmarkWriter():fp(0)
+{
+}
+
+void BookmarkWriter::Open(const wchar_t *filename)
+{
+ fp=_wfopen(filename, L"a+t");
+}
+
+void BookmarkWriter::New(const wchar_t *filename)
+{
+ fp=_wfopen(filename, L"wt");
+}
+
+void BookmarkWriter::Write(const char *filename, const char *title)
+{
+ fprintf(fp,"%s\n%s\n",filename,title);
+}
+
+void BookmarkWriter::Close()
+{
+ if (fp) fclose(fp);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_bookmarks/bookmark.h b/Src/Plugins/Library/ml_bookmarks/bookmark.h
new file mode 100644
index 00000000..5921e82e
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/bookmark.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_BOOKMARKH
+#define NULLSOFT_BOOKMARKH
+
+#include <stdio.h>
+
+class BookmarkWriter
+{
+public:
+ BookmarkWriter();
+ void New(const wchar_t *filename);
+ void Open(const wchar_t *filename);
+ void Write(const char *filename, const char *title);
+ void Close();
+private:
+ FILE *fp;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_bookmarks/listview.cpp b/Src/Plugins/Library/ml_bookmarks/listview.cpp
new file mode 100644
index 00000000..a3ceb22c
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/listview.cpp
@@ -0,0 +1,128 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+
+#include <windows.h>
+#include <commctrl.h>
+#include "listview.h"
+
+#ifdef GEN_ML_EXPORTS
+#include "main.h" // for getting the font
+#include "config.h"
+#endif
+// bp Comment: all the calls beginning "ListView_" are
+// MACROs defined in commctrl.h
+
+void W_ListView :: AddCol (char *text, int w)
+{
+ LVCOLUMN lvc={0,};
+ lvc.mask = LVCF_TEXT|LVCF_WIDTH;
+ lvc.pszText = text;
+ if (w) lvc.cx=w;
+ ListView_InsertColumn (m_hwnd, m_col, &lvc);
+ m_col++;
+}
+
+int W_ListView::GetColumnWidth (int col)
+{
+ if (col < 0 || col >= m_col) return 0;
+ return ListView_GetColumnWidth (m_hwnd, col);
+}
+
+
+int W_ListView::GetParam (int p)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = p;
+ ListView_GetItem (m_hwnd, &lvi);
+ return lvi.lParam;
+}
+
+int W_ListView::InsertItem (int p, char *text, int param)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = p;
+ lvi.pszText = text;
+ lvi.cchTextMax=strlen (text);
+ lvi.lParam = param;
+ return ListView_InsertItem (m_hwnd, &lvi);
+}
+
+
+void W_ListView::SetItemText (int p, int si, char *text)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.iSubItem = si;
+ lvi.mask = LVIF_TEXT;
+ lvi.pszText = text;
+ lvi.cchTextMax = strlen (text);
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::SetItemParam (int p, int param)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.mask=LVIF_PARAM;
+ lvi.lParam=param;
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::refreshFont ()
+{
+ if (m_font)
+ {
+ DeleteFont (m_font);
+ SetWindowFont (m_hwnd, NULL, FALSE);
+ }
+ m_font = NULL;
+
+ HWND h;
+#ifdef GEN_ML_EXPORTS
+ h=g_hwnd;
+#else
+ h=m_libraryparent;
+#endif
+ if (h && m_allowfonts)
+ {
+ int a=SendMessage (h, WM_USER+0x1000 /*WM_ML_IPC*/,66, 0x0600 /*ML_IPC_SKIN_WADLG_GETFUNC*/);
+ if (a)
+ {
+ m_font= (HFONT)a;
+ SetWindowFont (m_hwnd, m_font, FALSE);
+ }
+ }
+ InvalidateRect (m_hwnd, NULL, TRUE);
+}
+
+void W_ListView::setallowfonts (int allow)
+{
+ m_allowfonts=allow;
+}
+
+void W_ListView::setwnd (HWND hwnd)
+{
+ m_hwnd = hwnd;
+ if (hwnd)
+ {
+ ListView_SetExtendedListViewStyle (hwnd, LVS_EX_FULLROWSELECT|LVS_EX_UNDERLINEHOT );
+ refreshFont ();
+ }
+}
diff --git a/Src/Plugins/Library/ml_bookmarks/listview.h b/Src/Plugins/Library/ml_bookmarks/listview.h
new file mode 100644
index 00000000..5938a4dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/listview.h
@@ -0,0 +1,144 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+#if 0
+#ifndef _LISTVIEW_H_
+#define _LISTVIEW_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+class W_ListView
+{
+public:
+ W_ListView()
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ }
+ W_ListView(HWND hwnd)
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ setwnd(hwnd);
+ }
+ ~W_ListView()
+ {
+ if (m_font) DeleteFont(m_font);
+ m_font=0;
+ }
+
+ void refreshFont();
+
+#ifndef GEN_ML_EXPORTS
+ void setLibraryParentWnd(HWND hwndParent)
+ {
+ m_libraryparent=hwndParent;
+ }// for Winamp Font getting stuff
+#endif
+ void setallowfonts(int allow=1);
+ void setwnd(HWND hwnd);
+ void AddCol(char *text, int w);
+ int GetCount(void)
+ {
+ return ListView_GetItemCount(m_hwnd);
+ }
+ int GetParam(int p);
+ void DeleteItem(int n)
+ {
+ ListView_DeleteItem(m_hwnd,n);
+ }
+ void Clear(void)
+ {
+ ListView_DeleteAllItems(m_hwnd);
+ }
+ int GetSelected(int x)
+ {
+ return(ListView_GetItemState(m_hwnd, x, LVIS_SELECTED) & LVIS_SELECTED)?1:0;
+ }
+
+ int GetSelectedCount()
+ {
+ return ListView_GetSelectedCount(m_hwnd);
+ }
+
+ int GetSelectionMark()
+ {
+ return ListView_GetSelectionMark(m_hwnd);
+ }
+ void SetSelected(int x)
+ {
+ ListView_SetItemState(m_hwnd,x,LVIS_SELECTED,LVIS_SELECTED);
+ }
+ int InsertItem(int p, char *text, int param);
+ void GetItemRect(int i, RECT *r)
+ {
+ ListView_GetItemRect(m_hwnd, i, r, LVIR_BOUNDS);
+ }
+ void SetItemText(int p, int si, char *text);
+ void SetItemParam(int p, int param);
+
+ void GetText(int p, int si, char *text, int maxlen)
+ {
+ ListView_GetItemText(m_hwnd, p, si, text, maxlen);
+ }
+ int FindItemByParam(int param)
+ {
+ LVFINDINFO fi={LVFI_PARAM,0,param};
+ return ListView_FindItem(m_hwnd,-1,&fi);
+ }
+ int FindItemByPoint(int x, int y)
+ {
+ int l=GetCount();
+ for (int i=0;i<l;i++)
+ {
+ RECT r;
+ GetItemRect(i, &r);
+ if (r.left<=x && r.right>=x && r.top<=y && r.bottom>=y) return i;
+ }
+ return -1;
+ }
+ int GetColumnWidth(int col);
+ HWND getwnd(void)
+ {
+ return m_hwnd;
+ }
+
+protected:
+ HWND m_hwnd;
+ HFONT m_font;
+ int m_col;
+ int m_allowfonts;
+#ifndef GEN_ML_EXPORTS
+ HWND m_libraryparent;
+#endif
+};
+
+#endif//_LISTVIEW_H_
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_bookmarks/main.cpp b/Src/Plugins/Library/ml_bookmarks/main.cpp
new file mode 100644
index 00000000..c85674ba
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/main.cpp
@@ -0,0 +1,122 @@
+//#define PLUGIN_NAME "Nullsoft Bookmarks"
+#define PLUGIN_VERSION L"1.27"
+
+#include "Main.h"
+#include "../nu/AutoWide.h"
+#include <strsafe.h>
+#include "../../General/gen_ml/menu.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+static int Init();
+static void Quit();
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_bookmarks.dll)",
+ Init,
+ Quit,
+ bm_pluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+int bookmark_treeItem = 0;
+HMENU g_context_menus, g_context_menus2;
+HCURSOR hDragNDropCursor;
+C_Config *g_config;
+WNDPROC waProc=0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_application *WASABI_API_APP = 0;
+
+static DWORD WINAPI wa_newWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_WA_IPC && lParam == IPC_ADDBOOKMARK && wParam && wParam != 666)
+ {
+ bookmark_notifyAdd(AutoWide((char*)wParam));
+ }
+ else if (msg == WM_WA_IPC && lParam == IPC_ADDBOOKMARKW && wParam && wParam != 666)
+ {
+ bookmark_notifyAdd((wchar_t*)wParam);
+ }
+ else if ((msg == WM_COMMAND || msg == WM_SYSCOMMAND) && LOWORD(wParam) == WINAMP_EDIT_BOOKMARKS)
+ {
+ mediaLibrary.ShowMediaLibrary();
+ mediaLibrary.SwitchToPluginView(bookmark_treeItem);
+ return 0;
+ }
+
+ if (waProc)
+ return (DWORD)CallWindowProcW(waProc, hwnd, msg, wParam, lParam);
+ else
+ return (DWORD)DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+int Init()
+{
+ waProc = (WNDPROC)SetWindowLongPtrW( plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc );
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( AnonymousStatsGUID );
+ if ( sf )
+ AGAVE_API_STATS = reinterpret_cast<api_stats *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application *>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlBookmarkLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_BOOKMARKS ), PLUGIN_VERSION );
+ plugin.description = (char *)szDescription;
+
+ wchar_t inifile[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins", inifile, MAX_PATH );
+ CreateDirectoryW( inifile, NULL );
+
+ mediaLibrary.BuildPath( L"Plugins\\gen_ml.ini", inifile, MAX_PATH );
+ g_config = new C_Config( inifile );
+
+ g_context_menus = WASABI_API_LOADMENU( IDR_MENU1 );
+ g_context_menus2 = WASABI_API_LOADMENU( IDR_MENU1 );
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.pszText = WASABI_API_LNGSTRINGW( IDS_BOOKMARKS );
+ nis.item.pszInvariant = L"Bookmarks";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.iSelectedImage = nis.item.iImage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_BOOKMARKS );
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = { sizeof( NAVITEM ),0,NIMF_ITEMID, };
+ nvItem.hItem = MLNavCtrl_InsertItem( plugin.hwndLibraryParent, &nis );
+ MLNavItem_GetInfo( plugin.hwndLibraryParent, &nvItem );
+ bookmark_treeItem = nvItem.id;
+
+ return 0;
+}
+
+void Quit()
+{
+ delete g_config;
+}
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_bookmarks/main.h b/Src/Plugins/Library/ml_bookmarks/main.h
new file mode 100644
index 00000000..d8c2f355
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/main.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_BOOKMARKS_MAIN_H
+#define NULLSOFT_BOOKMARKS_MAIN_H
+
+#include <windows.h>
+#include <windowsx.h>
+
+#include "api__ml_bookmarks.h"
+
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../nu/MediaLibraryInterface.h"
+
+#include "resource.h"
+
+#include "../winamp/wa_ipc.h"
+//#include "../Plugins/General/gen_ml/ml.h"
+#include "../Plugins/General/gen_ml/config.h"
+
+
+#define WINAMP_EDIT_BOOKMARKS 40320
+
+INT_PTR bm_pluginMessageProc( int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3 );
+
+extern winampMediaLibraryPlugin plugin;
+extern int bookmark_treeItem;
+
+void bookmark_notifyAdd( wchar_t *filenametitle );
+
+#endif // !NULLSOFT_BOOKMARKS_MAIN_H
diff --git a/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.rc b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.rc
new file mode 100644
index 00000000..f36fe7f8
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.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
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_MENU1 MENU
+BEGIN
+ POPUP "BMWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_BMWND_PLAYSELECTEDFILES
+ MENUITEM "Enqueue selection\tShift+Enter", ID_BMWND_ENQUEUESELECTEDFILES
+ MENUITEM "Send to:", ID_BMWND_SENDTO
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", ID_BMWND_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Remove selected bookmarks\tDel", ID_BMWND_REMOVESELECTEDBOOKMARKS
+ MENUITEM "Edit selected bookmarks\tCtrl+E", ID_BMWND_EDITSELECTEDBOOKMARKS
+ END
+ POPUP "BMWndIcon"
+ BEGIN
+ MENUITEM "Play all bookmarks\tEnter", ID_BMWND_PLAYSELECTEDFILES
+ MENUITEM "Enqueue all bookmarks\tShift+Enter", ID_BMWND_ENQUEUESELECTEDFILES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_BMWND_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_VIEW_BM DIALOGEX 0, 0, 186, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_TABSTOP,0,0,184,79
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,81,35,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,38,81,35,11
+ CONTROL "Edit",IDC_EDITBOOK,"Button",BS_OWNERDRAW | WS_TABSTOP,76,81,36,11
+ CONTROL "Delete",IDC_REMOVEBOOK,"Button",BS_OWNERDRAW | WS_TABSTOP,114,81,36,11
+END
+
+IDD_EDITBOOKMARK DIALOGEX 0, 0, 241, 65
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Edit Bookmark"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Title",IDC_STATIC,8,10,14,8,SS_CENTERIMAGE
+ EDITTEXT IDC_TITLE,28,7,206,14,ES_AUTOHSCROLL
+ LTEXT "File",IDC_STATIC,8,29,12,8,SS_CENTERIMAGE
+ EDITTEXT IDC_FILE,28,26,187,14,ES_AUTOHSCROLL
+ PUSHBUTTON "&...",IDC_EDIT_FN,218,25,16,15
+ DEFPUSHBUTTON "&OK",IDOK,6,44,50,14
+ PUSHBUTTON "&Cancel",IDCANCEL,60,44,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_EDITBOOKMARK, DIALOG
+ BEGIN
+ LEFTMARGIN, 6
+ RIGHTMARGIN, 234
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 58
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_BOOKMARKS BITMAP "resources\\ti_bookmarks_16x16x16.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_BM_ACCELERATORS ACCELERATORS
+BEGIN
+ "A", ID_BMWND_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "E", ID_BMWND_EDITSELECTEDBOOKMARKS, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_BMWND_REMOVESELECTEDBOOKMARKS, VIRTKEY, NOINVERT
+ VK_RETURN, ID_BMWND_PLAYSELECTEDFILES, VIRTKEY, NOINVERT
+ VK_RETURN, ID_BMWND_ENQUEUESELECTEDFILES, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_BOOKMARKS "Nullsoft Bookmarks v%s"
+ 65535 "{A3A1E7C0-761B-4391-A08A-F0D7AF38931C}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_BOOKMARKS "Bookmarks"
+ IDS_ADD_TO_BOOKMARKS "Add to bookmarks"
+ IDS_BOOKMARK_TITLE "Bookmark Title"
+ IDS_BOOKMARK_FN_URL "Bookmark Filename/URL"
+ IDS_BROWSE_FOR_BM_ENTRY "Browse for bookmark entry..."
+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_bookmarks/ml_bookmarks.sln b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.sln
new file mode 100644
index 00000000..746468be
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_bookmarks", "ml_bookmarks.vcxproj", "{24335E73-2704-466A-8265-4314DD99886A}"
+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
+ {24335E73-2704-466A-8265-4314DD99886A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {24335E73-2704-466A-8265-4314DD99886A}.Debug|Win32.Build.0 = Debug|Win32
+ {24335E73-2704-466A-8265-4314DD99886A}.Debug|x64.ActiveCfg = Debug|x64
+ {24335E73-2704-466A-8265-4314DD99886A}.Debug|x64.Build.0 = Debug|x64
+ {24335E73-2704-466A-8265-4314DD99886A}.Release|Win32.ActiveCfg = Release|Win32
+ {24335E73-2704-466A-8265-4314DD99886A}.Release|Win32.Build.0 = Release|Win32
+ {24335E73-2704-466A-8265-4314DD99886A}.Release|x64.ActiveCfg = Release|x64
+ {24335E73-2704-466A-8265-4314DD99886A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {91C2E933-BA43-4ADA-A8B4-B3A2E571BA11}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj
new file mode 100644
index 00000000..669a0893
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj
@@ -0,0 +1,317 @@
+<?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>{24335E73-2704-466A-8265-4314DD99886A}</ProjectGuid>
+ <RootNamespace>ml_bookmarks</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_BOOKMARKS_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_BOOKMARKS_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_BOOKMARKS_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_BOOKMARKS_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <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\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="bookmark.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="view.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="api__ml_bookmarks.h" />
+ <ClInclude Include="bookmark.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_bookmarks.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_bookmarks_16x16x16.bmp" />
+ </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_bookmarks/ml_bookmarks.vcxproj.filters b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj.filters
new file mode 100644
index 00000000..7207ea75
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/ml_bookmarks.vcxproj.filters
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="view.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="bookmark.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_bookmarks.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="bookmark.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{b4c6c983-ad68-4d2d-a542-7f1bf6b1c155}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{f072eba6-51ff-4c40-b136-bf3c79f3e120}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b04fff17-1af4-4d74-b609-169c1a32b097}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{56f5b459-ac0f-423d-b448-563135bf4bd8}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{f4c07660-d9f0-42b1-8745-0d6446aef60f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{d31d9efd-4434-46c5-8d2d-8a894aa24055}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{00b097a1-1383-414f-8c19-82f97c6d06a5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{8eaf67d8-c524-468a-a78b-334c69d1cf9e}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_bookmarks.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_bookmarks_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_bookmarks/resource.h b/Src/Plugins/Library/ml_bookmarks/resource.h
new file mode 100644
index 00000000..037b9841
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/resource.h
@@ -0,0 +1,46 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_bookmarks.rc
+//
+#define IDS_BOOKMARKS 1
+#define IDS_ADD_TO_BOOKMARKS 2
+#define IDS_BOOKMARK_TITLE 3
+#define IDS_BOOKMARK_FN_URL 4
+#define IDS_BROWSE_FOR_BM_ENTRY 5
+#define IDR_MENU1 101
+#define IDD_VIEW_BM 102
+#define IDD_EDITBOOKMARK 103
+#define IDC_CURSOR1 105
+#define IDB_BITMAP1 106
+#define IDR_VIEW_BM_ACCELERATORS 111
+#define IDC_BUTTON_CUSTOM 1000
+#define IDC_LIST 1001
+#define IDC_BUTTON_PLAY 1002
+#define IDC_BUTTON_ENQUEUE 1003
+#define IDB_TREEITEM_BOOKMARKS 1003
+#define IDC_EDITBOOK 1004
+#define IDC_REMOVEBOOK 1005
+#define IDC_TITLE 1006
+#define IDC_FILE 1008
+#define IDC_EDIT_FN 1009
+#define ID_BMWND_PLAYSELECTEDFILES 40001
+#define ID_BMWND_ENQUEUESELECTEDFILES 40002
+#define ID_BMWND_REMOVESELECTEDBOOKMARKS 40003
+#define ID_BMWND_EDITSELECTEDBOOKMARKS 40004
+#define ID_BMWND_SELECTALL 40005
+#define ID_Menu 40006
+#define ID_BMWNDICON_HELP 40007
+#define ID_BMWND_HELP 40008
+#define ID_BMWND_SENDTO 40012
+#define IDS_NULLSOFT_BOOKMARKS 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 114
+#define _APS_NEXT_COMMAND_VALUE 40013
+#define _APS_NEXT_CONTROL_VALUE 1010
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_bookmarks/resources/ti_bookmarks_16x16x16.bmp b/Src/Plugins/Library/ml_bookmarks/resources/ti_bookmarks_16x16x16.bmp
new file mode 100644
index 00000000..92f58e77
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/resources/ti_bookmarks_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_bookmarks/version.rc2 b/Src/Plugins/Library/ml_bookmarks/version.rc2
new file mode 100644
index 00000000..b5ce38cb
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,27,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,27,0,0"
+ VALUE "InternalName", "Nullsoft Bookmarks"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_bookmark.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_bookmarks/view.cpp b/Src/Plugins/Library/ml_bookmarks/view.cpp
new file mode 100644
index 00000000..f89196a0
--- /dev/null
+++ b/Src/Plugins/Library/ml_bookmarks/view.cpp
@@ -0,0 +1,1512 @@
+#include "main.h"
+#include "../nu/AutoCharFn.h"
+#include "../nu/DialogSkinner.h"
+#include "Bookmark.h"
+#include "../../General/gen_ml/ml_ipc.h"
+#include <string>
+#include "../nu/ListView.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/menushortcuts.h"
+#include <strsafe.h>
+#include "../../General/gen_ml/menu.h"
+
+INT_PTR CALLBACK view_bmDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static W_ListView m_bmlist;
+static HWND m_headerhwnd, m_hwnd;
+extern C_Config *g_config;
+static int customAllowed, groupBtn = 1, enqueuedef;
+static viewButtons view;
+
+// used for the send-to menu bits
+static INT_PTR IPC_LIBRARY_SENDTOMENU;
+static librarySendToMenuStruct s;
+BOOL myMenu = FALSE;
+
+extern HMENU g_context_menus, g_context_menus2;
+extern HCURSOR hDragNDropCursor;
+
+static int bookmark_contextMenu( INT_PTR param1, HWND parent, POINTS pts );
+static void bookmarks_contextMenu( HWND hwndDlg, HWND from, int x, int y );
+static void bookmark_onTreeEnterDblClk();
+
+static int pluginHandleIpcMessage(int msg, int param)
+{
+ return (int)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
+}
+
+void bookmark_notifyAdd(wchar_t *filenametitle)
+{
+ if (!m_hwnd || !filenametitle) return;
+ int cnt=m_bmlist.GetCount();
+ m_bmlist.InsertItem(cnt,filenametitle+lstrlenW(filenametitle)+1,0);
+ m_bmlist.SetItemText(cnt,1,filenametitle);
+}
+
+INT_PTR bm_pluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_NO_CONFIG)
+ {
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_TREE_ONCLICK)
+ {
+ switch(param2)
+ {
+ case ML_ACTION_ENTER:
+ case ML_ACTION_DBLCLICK:
+ if (param1 == bookmark_treeItem)
+ bookmark_onTreeEnterDblClk();
+ break;
+ }
+ }
+ else if (message_type == ML_MSG_TREE_ONCREATEVIEW && param1 == bookmark_treeItem)
+ {
+ return (INT_PTR)WASABI_API_CREATEDIALOGW(IDD_VIEW_BM, (HWND)param2, view_bmDialogProc);
+ }
+ else if (message_type == ML_MSG_NAVIGATION_CONTEXTMENU)
+ {
+ return bookmark_contextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+ }
+ else if (message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (!myMenu &&
+ (param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_ITEMRECORDLIST ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_STREAMNAMES ||
+ param1 == ML_TYPE_FILENAMESW || param1 == ML_TYPE_STREAMNAMESW ||
+ param1 == ML_TYPE_CDTRACKS))
+ mediaLibrary.AddToSendTo(WASABI_API_LNGSTRINGW(IDS_ADD_TO_BOOKMARKS),
+ param2, (INT_PTR)bm_pluginMessageProc);
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT || message_type == ML_MSG_TREE_ONDROPTARGET)
+ {
+ // set with droptarget defaults =)
+ UINT_PTR type = 0,data = 0;
+
+ if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)bm_pluginMessageProc) return 0;
+
+ type=(int)param1;
+ data = (int)param2;
+ }
+ else
+ {
+ if (param1 != bookmark_treeItem) return 0;
+
+ type=(int)param2;
+ data=(int)param3;
+
+ if (!data)
+ {
+ return (type == ML_TYPE_ITEMRECORDLISTW || type == ML_TYPE_ITEMRECORDLIST ||
+ type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES ||
+ type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW ||
+ type == ML_TYPE_CDTRACKS ||
+ type == ML_TYPE_PLAYLIST || type == ML_TYPE_PLAYLISTS) ? 1 : -1;
+ }
+ }
+
+ if (data)
+ {
+ if (type == ML_TYPE_ITEMRECORDLIST || type == ML_TYPE_CDTRACKS)
+ {
+ itemRecordList *p=(itemRecordList*)data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddBookmarkW(AutoWide(p->Items[x].filename));
+
+ return 1;
+ }
+ else if (type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *p=(itemRecordListW *)data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddBookmarkW(p->Items[x].filename);
+
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES)
+ {
+ char *p=(char*)data;
+ while (p && *p)
+ {
+ mediaLibrary.AddBookmark(p);
+ p+=lstrlenA(p)+1;
+ }
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW)
+ {
+ wchar_t *p=(wchar_t*)data;
+ while (p && *p)
+ {
+ mediaLibrary.AddBookmarkW(p);
+ p+=wcslen(p)+1;
+ }
+ return 1;
+ }
+ else if(type == ML_TYPE_PLAYLIST)
+ {
+ mediaLibrary.AddBookmarkW((wchar_t*)((mlPlaylist*)data)->filename);
+ return 1;
+ }
+ else if(type == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)data;
+ while (playlists && *playlists)
+ {
+ mlPlaylist *pl = *playlists;
+ mediaLibrary.AddBookmarkW((wchar_t*)pl->filename);
+ playlists++;
+ }
+ return 1;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE)
+ {
+ enqueuedef = (int)param1;
+ groupBtn = (int)param2;
+ PostMessage(m_hwnd, WM_APP + 104, param1, param2);
+ return 0;
+ }
+ return 0;
+}
+
+static void playFiles(int enqueue, int all)
+{
+ int cnt=0;
+ int l=m_bmlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if(all || m_bmlist.GetSelected(i))
+ {
+ if (!cnt)
+ {
+ if(!enqueue) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
+ cnt++;
+ }
+ //send the file to winamp
+ COPYDATASTRUCT cds = {0};
+ cds.dwData = IPC_ENQUEUEFILEW;
+ wchar_t buf[1024] = {0};
+ m_bmlist.GetText(i,1,buf,sizeof(buf)-1);
+ buf[1023]=0;
+ cds.lpData = (void *) buf;
+ cds.cbData = (lstrlenW((wchar_t*)cds.lpData)+1)*sizeof(wchar_t); // include space for null char
+ SendMessage(plugin.hwndWinampParent,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds);
+ }
+ }
+ if (cnt)
+ {
+ if(!enqueue) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_STARTPLAY);
+ }
+}
+
+static wchar_t *g_bmedit_fn, *g_bmedit_ft;
+
+static BOOL CALLBACK BookMarkEditProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ SetDlgItemTextW(hwndDlg,IDC_TITLE,g_bmedit_ft);
+ SetDlgItemTextW(hwndDlg,IDC_FILE,g_bmedit_fn);
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ GetDlgItemTextW(hwndDlg,IDC_TITLE,g_bmedit_ft,1024);
+ GetDlgItemTextW(hwndDlg,IDC_FILE,g_bmedit_fn,1024);
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ return 0;
+ case IDC_EDIT_FN:
+ {
+ wchar_t fn[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_FILE,fn,1024);
+ OPENFILENAMEW of = {0};
+ of.lStructSize = sizeof(OPENFILENAMEW);
+ of.hwndOwner = hwndDlg;
+ of.nMaxFileTitle = 32;
+ of.lpstrFilter = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,1,IPC_GET_EXTLISTW);
+ of.nMaxCustFilter = 1024;
+ of.lpstrFile = fn;
+ of.nMaxFile = 1024;
+ of.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_BROWSE_FOR_BM_ENTRY);
+ of.Flags = OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_EXPLORER|
+ OFN_PATHMUSTEXIST|OFN_ENABLESIZING;
+
+ if(GetOpenFileNameW(&of))
+ {
+ SetDlgItemTextW(hwndDlg,IDC_FILE,fn);
+ }
+ GlobalFree((void*)of.lpstrFilter);
+ }
+ break;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static void readbookmarks(int play1enqueue2=0)
+{
+ if (!play1enqueue2) m_bmlist.Clear();
+
+ int x=0;
+ FILE *fp=NULL;
+ wchar_t *fnp=(wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,666,IPC_ADDBOOKMARKW);
+ if ((unsigned int)fnp < 65536) return;
+
+ fp=_wfopen(fnp,L"rt");
+ if (fp)
+ {
+ while (1)
+ {
+ char ft[4096] = {0}, fn[4096] = {0};
+ fgets(fn,4096,fp);
+ if (feof(fp)) break;
+ fgets(ft,4096,fp);
+ if (feof(fp)) break;
+ if (ft[0] && fn[0])
+ {
+ if (fn[strlen(fn)-1]=='\n') fn[strlen(fn)-1]=0;
+ if (ft[strlen(ft)-1]=='\n') ft[strlen(ft)-1]=0;
+ if (ft[0] && fn[0])
+ {
+ if (!play1enqueue2)
+ {
+ m_bmlist.InsertItem(x,AutoWide(ft,CP_UTF8),0);
+ m_bmlist.SetItemText(x,1,AutoWide(fn,CP_UTF8));
+ }
+ else
+ {
+ if (!x)
+ {
+ if(play1enqueue2==1) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
+ }
+ //send the file to winamp
+ COPYDATASTRUCT cds = {0};
+ cds.dwData = IPC_ENQUEUEFILEW;
+ cds.lpData = (void *)AutoWideDup(fn,CP_UTF8);
+ cds.cbData = (lstrlenW((wchar_t *) cds.lpData)+1)*sizeof(wchar_t); // include space for null char
+ SendMessage(plugin.hwndWinampParent,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds);
+ }
+ x++;
+ }
+ }
+ }
+ fclose(fp);
+ }
+
+ if (!play1enqueue2 && AGAVE_API_STATS)
+ {
+ AGAVE_API_STATS->SetStat(api_stats::BOOKMARK_COUNT, m_bmlist.GetCount());
+ }
+
+ if (x && play1enqueue2 == 1)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_STARTPLAY);
+ }
+}
+
+static void writebookmarks()
+{
+ wchar_t *fnp = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 666, IPC_ADDBOOKMARKW );
+
+ if ( (unsigned int)fnp < 65536 )
+ return;
+
+ BookmarkWriter bookmarks;
+ bookmarks.New( fnp );
+
+ int l = m_bmlist.GetCount();
+ for ( int x = 0; x < l; x++ )
+ {
+ wchar_t ftW[ 4096 ] = { 0 }, fnW[ 4096 ] = { 0 };
+ m_bmlist.GetText( x, 0, ftW, ARRAYSIZE( ftW ) );
+ m_bmlist.GetText( x, 1, fnW, ARRAYSIZE( fnW ) );
+ bookmarks.Write( AutoChar( fnW, CP_UTF8 ), AutoChar( ftW, CP_UTF8 ) );
+ }
+
+ bookmarks.Close();
+
+ fnp = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_ADDBOOKMARKW );
+
+ if ( (unsigned int)fnp < 65536 )
+ return;
+
+ bookmarks.New( fnp );
+
+ for ( int x = 0; x < l; x++ )
+ {
+ char ft[ 4096 ] = { 0 }, fn[ 4096 ] = { 0 };
+
+ m_bmlist.GetText( x, 0, ft, sizeof( ft ) );
+ m_bmlist.GetText( x, 1, fn, sizeof( fn ) );
+
+ bookmarks.Write( fn, ft );
+ }
+
+ bookmarks.Close();
+}
+
+static void bookmark_onTreeEnterDblClk()
+{
+ int enq = ( ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ) ^ ( !!g_config->ReadInt( L"enqueuedef", 0 ) ) );
+ readbookmarks( enq ? 2 : 1 );
+}
+
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu)
+{
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(hwndDlg, szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendMenuShortcuts(menu, szAccel, c, MSF_REPLACE);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_BMWND_PLAYSELECTEDFILES)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ }
+ else if (id == ID_BMWND_ENQUEUESELECTEDFILES)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos= i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ }
+ }
+
+ playItem.wID = ID_BMWND_ENQUEUESELECTEDFILES;
+ enqueueItem.wID = ID_BMWND_PLAYSELECTEDFILES;
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+void UpdateMenuItems(HWND hwndDlg, HMENU menu)
+{
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ SyncMenuWithAccelerators(hwndDlg, menu);
+ if (swapPlayEnqueue) SwapPlayEnqueueInMenu(menu);
+}
+
+static int bookmark_contextMenu(INT_PTR param1, HWND hHost, POINTS pts)
+{
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, bookmark_treeItem);
+ if (hItem != myItem)
+ return FALSE;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU menu=GetSubMenu(g_context_menus,1);
+
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ if(!IsWindow(m_hwnd))
+ {
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_BM_ACCELERATORS);
+ int size = CopyAcceleratorTable(accel,0,0);
+ AppendMenuShortcuts(menu, &accel, size, MSF_REPLACE);
+ }
+ else
+ {
+ SyncMenuWithAccelerators(m_hwnd, menu);
+ }
+
+ if (swapPlayEnqueue)
+ SwapPlayEnqueueInMenu(menu);
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu,
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ pt.x, pt.y, hHost, NULL);
+ switch(r)
+ {
+ case ID_BMWND_PLAYSELECTEDFILES:
+ readbookmarks(1);
+ break;
+ case ID_BMWND_ENQUEUESELECTEDFILES:
+ readbookmarks(2);
+ break;
+ case ID_BMWND_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8105304048660-The-Winamp-Media-Library");
+ break;
+ }
+ Sleep(100);
+ MSG msg;
+ while(PeekMessage(&msg,NULL,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE)); //eat return
+ return TRUE;
+}
+
+static void bookmarks_contextMenu(HWND hwndDlg, HWND from, int x, int y)
+{
+ if (from != m_bmlist.getwnd())
+ return ;
+
+ POINT pt = {x,y};
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = m_bmlist.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ m_bmlist.GetItemRect(selected, &itemRect);
+ ClientToScreen(hwndDlg, (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(hwndDlg, &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(from, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(from, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return;
+ }
+
+ HMENU menu=GetSubMenu(g_context_menus,0);
+ UpdateMenuItems(hwndDlg, menu);
+ ZeroMemory(&s, sizeof(librarySendToMenuStruct));
+
+ IPC_LIBRARY_SENDTOMENU = (INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&"LibrarySendToMenu",IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)0,IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[1] = 1;
+ s.build_hMenu = CreatePopupMenu();
+
+ MENUITEMINFOW mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFOW);
+ mii.fMask = MIIM_SUBMENU;
+ mii.hSubMenu = s.build_hMenu;
+ SetMenuItemInfoW(menu, 2, TRUE, &mii);
+ }
+
+ UINT menuStatus;
+ if (m_bmlist.GetNextSelected(-1) == -1)
+ {
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+ EnableMenuItem(menu, 2, MF_BYPOSITION | MF_GRAYED);
+ }
+ else
+ {
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ EnableMenuItem(menu, 2, MF_BYPOSITION | MF_ENABLED);
+ }
+
+ EnableMenuItem(menu, ID_BMWND_PLAYSELECTEDFILES, menuStatus);
+ EnableMenuItem(menu, ID_BMWND_ENQUEUESELECTEDFILES, menuStatus);
+ EnableMenuItem(menu, ID_BMWND_REMOVESELECTEDBOOKMARKS, menuStatus);
+ EnableMenuItem(menu, ID_BMWND_EDITSELECTEDBOOKMARKS, menuStatus);
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu,
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON,
+ x, y, hwndDlg, NULL);
+ switch(LOWORD(r))
+ {
+ case ID_BMWND_PLAYSELECTEDFILES:
+ case ID_BMWND_ENQUEUESELECTEDFILES:
+ case ID_BMWND_REMOVESELECTEDBOOKMARKS:
+ case ID_BMWND_EDITSELECTEDBOOKMARKS:
+ case ID_BMWND_SELECTALL:
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(r,0),0);
+ break;
+ default:
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
+ {
+ s.mode=3;
+ s.data_type=ML_TYPE_FILENAMESW;
+
+ //std::vector<wchar_t> sendStr;
+ std::wstring sendStr;
+
+ int l=m_bmlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (m_bmlist.GetSelected(i))
+ {
+ wchar_t buf[1023] = {0};
+ m_bmlist.GetText(i,1,buf,ARRAYSIZE(buf)-1);
+ // HAKAN: why (len + 1) ?
+ //sendStr.append(buf, wcslen(buf)+1);
+ sendStr.append(buf, wcslen(buf));
+ }
+ }
+ // HAKAN: No need to add trailing zero
+ //sendStr.push_back(0);
+
+ s.data = (void*)sendStr.c_str();
+
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU)!=1)
+ {
+ s.mode=3;
+ s.data_type=ML_TYPE_FILENAMES;
+
+ //std::vector<char> sendStrA;
+ std::string sendStrA;
+
+ int l=m_bmlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (m_bmlist.GetSelected(i))
+ {
+ wchar_t buf[1023] = {0};
+ m_bmlist.GetText(i,1,buf,ARRAYSIZE(buf)-1);
+
+ // HAKAN: why (len + 1) ?
+ //sendStrA.append(AutoCharFn(buf), strlen(AutoCharFn(buf)) + 1);
+ sendStrA.append(AutoCharFn(buf), strlen(AutoCharFn(buf)));
+ }
+ }
+ // HAKAN: No need to add trailing zero
+ //sendStrA.push_back(0);
+
+ s.data = (void*)sendStrA.c_str();
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU);
+ }
+ }
+ }
+ break;
+ }
+
+ if (s.mode)
+ {
+ s.mode=4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+
+ if (NULL != s.build_hMenu)
+ {
+ DestroyMenu(s.build_hMenu);
+ s.build_hMenu = NULL;
+ }
+
+ Sleep(100);
+ MSG msg;
+ while(PeekMessage(&msg,NULL,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE)); //eat return
+}
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_EDITBOOK, IDC_REMOVEBOOK,
+ GROUP_MAIN, IDC_LIST
+ };
+
+ INT index;
+ RECT rc;
+ RECT rg;
+ RECT ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if ( rc.right == rc.left || rc.bottom == rc.top )
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect( hwnd, NULL, TRUE );
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if ( controls[ index ] >= GROUP_MIN && controls[ index ] <= GROUP_MAX ) // group id
+ {
+ skipgroup = FALSE;
+ switch ( controls[ index ] )
+ {
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[ 128 ] = { 0 };
+ WASABI_API_LNGSTRINGW_BUF( IDC_BUTTON_PLAY, buffer, ARRAYSIZE( buffer ) );
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( GetDlgItem( hwnd, IDC_BUTTON_PLAY ), buffer );
+
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ),
+ rc.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), rc.right, rc.bottom );
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY( 3 );
+ break;
+ }
+ case GROUP_MAIN:
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ), rc.top, rc.right, rc.bottom );
+ break;
+ }
+ continue;
+ }
+
+ if (skipgroup)
+ continue;
+
+ pl->id = controls[ index ];
+ pl->hwnd = GetDlgItem( hwnd, pl->id );
+ if (!pl->hwnd)
+ continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch ( pl->id )
+ {
+ case IDC_BUTTON_PLAY:
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_CUSTOM:
+ case IDC_EDITBOOK:
+ case IDC_REMOVEBOOK:
+ if ( IDC_BUTTON_CUSTOM != pl->id || customAllowed )
+ {
+ if ( groupBtn && pl->id == IDC_BUTTON_PLAY && enqueuedef == 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && pl->id == IDC_BUTTON_ENQUEUE && enqueuedef != 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && ( pl->id == IDC_BUTTON_PLAY || pl->id == IDC_BUTTON_ENQUEUE ) && customAllowed )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowTextW( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
+ LONG width = LOWORD( idealSize ) + WASABI_API_APP->getScaleX( 6 );
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+ pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if ( SWP_SHOWWINDOW & pl->flags ) rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_LIST:
+ pl->flags |= ( rg.top < rg.bottom ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ),
+ rg.right - rg.left + WASABI_API_APP->getScaleY( 1 ),
+ ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) &&
+ ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+ pl++;
+ }
+ else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
+ {
+ ValidateRect( hwnd, &ri );
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn ) rgn = CreateRectRgn( 0, 0, 0, 0 );
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if ( pl != layout )
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos( (INT)( pl - layout ) );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ hdwp = DeferWindowPos( hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags );
+ }
+
+ if ( hdwp )
+ EndDeferWindowPos( hdwp );
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ if ( fRedraw )
+ {
+ GetUpdateRgn( hwnd, rgn, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow( hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN );
+ }
+
+ if ( g_rgnUpdate )
+ {
+ GetUpdateRgn( hwnd, g_rgnUpdate, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR );
+ }
+ }
+ }
+
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ if ( pc->rgn )
+ DeleteObject( pc->rgn );
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn( hwnd, NULL );
+}
+
+static BOOL Bookmark_OnDisplayChange()
+{
+ ListView_SetTextColor(m_bmlist.getwnd(),dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(m_bmlist.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(m_bmlist.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+ m_bmlist.SetFont(dialogSkinner.GetFont());
+ LayoutWindows(m_hwnd, TRUE);
+ return 0;
+}
+
+static BOOL Bookmark_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
+{
+ if (GetCapture()==hwnd)
+ {
+ POINT p={x,y};
+ ClientToScreen(hwnd,&p);
+
+ if (WindowFromPoint(p) == m_bmlist.getwnd())
+ {
+ SetCursor(hDragNDropCursor);
+ }
+ else
+ {
+ mlDropItemStruct m={0};
+ m.type=ML_TYPE_FILENAMES;
+ m.p=p;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG,(int)&m);
+ }
+ }
+ return FALSE;
+}
+
+static BOOL Bookmark_OnLButtonUp(HWND hwnd, int x, int y, UINT flags)
+{
+ if (GetCapture()==hwnd)
+ {
+ ReleaseCapture();
+ POINT p={x,y};
+ ClientToScreen(hwnd,&p);
+
+ if (WindowFromPoint(p) == m_bmlist.getwnd())
+ {
+ LVHITTESTINFO lvi;
+ lvi.pt=p;
+ ScreenToClient(m_bmlist.getwnd(),&lvi.pt);
+ ListView_HitTest(m_bmlist.getwnd(),&lvi);
+
+ int num_items = m_bmlist.GetCount();
+
+ int destination_position=lvi.iItem;
+ if ((lvi.flags & (LVHT_ONITEM)));
+ else if (lvi.flags & LVHT_ABOVE) destination_position=0;
+ else if (lvi.flags & (LVHT_BELOW|LVHT_NOWHERE )) destination_position=num_items;
+ else return 0;
+
+ int x = 0, dirty=0;
+
+ for (; x < num_items; x ++)
+ {
+ m_bmlist.SetItemParam(x,0);
+ }
+
+ for (x = 0; x < num_items; x ++)
+ {
+ int sel=m_bmlist.GetSelected(x);
+ if (sel && x != destination_position)
+ {
+ wchar_t ft[1024] = {0}, fn[1024] = {0};
+ m_bmlist.GetText(x,0,ft,ARRAYSIZE(ft));
+ m_bmlist.GetText(x,1,fn,ARRAYSIZE(fn));
+ m_bmlist.DeleteItem(x);
+ if (x < destination_position)
+ {
+ x--;
+ }
+
+ if (destination_position >= num_items) destination_position--;
+
+ m_bmlist.InsertItem(destination_position,ft,1);
+ m_bmlist.SetItemText(destination_position,1,fn);
+ // have to do this otherwise first item isn't correctly flagged & reselected
+ m_bmlist.SetItemParam(destination_position,1);
+
+ destination_position++;
+
+ dirty=1;
+ }
+ else if (sel) destination_position++;
+ }
+
+ int w=0;
+ for (x = 0; x < num_items; x ++)
+ {
+ if (m_bmlist.GetParam(x))
+ {
+ if (!w)
+ {
+ w=1;
+ ListView_SetItemState(m_bmlist.getwnd(),x,LVIS_FOCUSED,LVIS_FOCUSED);
+ }
+ m_bmlist.SetSelected(x);
+ }
+ }
+
+ if (dirty) writebookmarks();
+ }
+ else
+ {
+ mlDropItemStruct m={0};
+ m.type=ML_TYPE_FILENAMESW;
+ m.p=p;
+ m.flags=ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG,(int)&m);
+
+ if (m.result>0)
+ {
+ wchar_t *buf=(wchar_t*)calloc(4096, sizeof(wchar_t));
+ size_t buf_size=4096;
+ size_t buf_pos=0;
+
+ int l=m_bmlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (m_bmlist.GetSelected(i))
+ {
+ wchar_t tbuf[1024]={0};
+ m_bmlist.GetText(i,1,tbuf,ARRAYSIZE(tbuf)-1);
+ tbuf[1023]=0;
+ size_t newsize=buf_pos + wcslen(tbuf) + 1;
+ if (newsize < buf_size)
+ {
+ size_t old_buf_size=buf_size;
+ buf_size=newsize+4096;
+
+ wchar_t *data = (wchar_t*)realloc(buf, (buf_size + 1) * sizeof(wchar_t));
+ if (data)
+ {
+ buf=data;
+ }
+ else
+ {
+ data=(wchar_t*)calloc((buf_size + 1), sizeof(wchar_t));
+ if (data)
+ {
+ memcpy(data, buf, sizeof(wchar_t)*old_buf_size);
+ free(buf);
+ buf=data;
+ }
+ else buf_size = old_buf_size;
+ }
+ }
+ lstrcpynW(buf+buf_pos,tbuf,(int)buf_size);
+ buf_pos=newsize;
+ }
+ }
+ if (buf_pos)
+ {
+ buf[buf_pos]=0;
+ m.flags=0;
+ m.result=0;
+ m.data=(void*)buf;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP,(int)&m);
+ }
+ free(buf);
+ }
+ }
+ }
+ return FALSE;
+}
+
+void Bookmark_SelectAll(void)
+{
+ LVITEM item;
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(m_bmlist.getwnd(), LVM_SETITEMSTATE, -1, (LPARAM)&item);
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL Bookmark_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags = 0 )
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem( hwndDlg, buttonId );
+ GetWindowRect( buttonHWND, &r );
+ UpdateMenuItems( hwndDlg, menu );
+ MLSkinnedButton_SetDropDownState( buttonHWND, TRUE );
+
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+
+ if ( !( flags & BPM_WM_COMMAND ) )
+ tpmFlags |= TPM_RETURNCMD;
+
+ int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, tpmFlags, r.left, r.top, hwndDlg, NULL );
+
+ if ( ( flags & BPM_ECHO_WM_COMMAND ) && x )
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( x, 0 ), 0 );
+
+ MLSkinnedButton_SetDropDownState( buttonHWND, FALSE );
+
+ return x;
+}
+
+static void Bookmark_Play(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(g_context_menus2, 0);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ Bookmark_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+static BOOL Bookmark_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
+{
+ switch(LOWORD(id))
+ {
+ case IDC_BUTTON_PLAY:
+ case ID_BMWND_PLAYSELECTEDFILES:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_BMWND_ENQUEUESELECTEDFILES:
+ case IDC_BUTTON_CUSTOM:
+ {
+ if (codeNotify == MLBN_DROPDOWN)
+ {
+ Bookmark_Play(hwnd, hwndCtl, id);
+ }
+ else
+ {
+ int action;
+ if (LOWORD(id) == IDC_BUTTON_PLAY || LOWORD(id) == ID_BMWND_PLAYSELECTEDFILES)
+ {
+ action = (codeNotify == 1) ? g_config->ReadInt(L"enqueuedef", 0) == 1 : 0;
+ }
+ else if (LOWORD(id) == IDC_BUTTON_ENQUEUE || LOWORD(id) == ID_BMWND_ENQUEUESELECTEDFILES)
+ {
+ action = (codeNotify == 1) ? g_config->ReadInt(L"enqueuedef", 0) != 1 : 1;
+ }
+ else
+ break;
+
+ playFiles(action, 0);
+ }
+ break;
+ }
+ case ID_BMWND_EDITSELECTEDBOOKMARKS:
+ case IDC_EDITBOOK:
+ {
+ int dirty=0;
+ int x=0,l=m_bmlist.GetCount();
+ while (x < l)
+ {
+ if (m_bmlist.GetSelected(x))
+ {
+ dirty=1;
+ wchar_t fn[1024] = {0}, ft[1024] = {0};
+ m_bmlist.GetText(x,0,ft,ARRAYSIZE(ft));
+ m_bmlist.GetText(x,1,fn,ARRAYSIZE(fn));
+ g_bmedit_fn=fn;
+ g_bmedit_ft=ft;
+ WASABI_API_DIALOGBOXW(IDD_EDITBOOKMARK,hwnd,BookMarkEditProc);
+ m_bmlist.SetItemText(x,0,ft);
+ m_bmlist.SetItemText(x,1,fn);
+ }
+ x++;
+ }
+ if (dirty) writebookmarks();
+ }
+ break;
+ case ID_BMWND_REMOVESELECTEDBOOKMARKS:
+ case IDC_REMOVEBOOK: // remove
+ {
+ int dirty=0;
+ int x=0,l=m_bmlist.GetCount();
+ while (x < l)
+ {
+ if (m_bmlist.GetSelected(x))
+ {
+ dirty=1;
+ m_bmlist.DeleteItem(x);
+ l--;
+ }
+ else x++;
+ }
+ if (dirty) writebookmarks();
+ }
+ break;
+ case ID_BMWND_SELECTALL:
+ Bookmark_SelectAll();
+ break;
+ }
+ return FALSE;
+}
+
+static BOOL Bookmark_OnDestroy(HWND hwnd)
+{
+ if (m_bmlist.getwnd())
+ {
+ g_config->WriteInt(L"bm_col_title",m_bmlist.GetColumnWidth(0));
+ g_config->WriteInt(L"bm_col_filename",m_bmlist.GetColumnWidth(1));
+ }
+
+ m_hwnd=0;
+ return FALSE;
+}
+
+static void Bookmark_ManageButtons(HWND hwndDlg)
+{
+ int has_selection = m_bmlist.GetSelectedCount();
+
+ const int buttonids[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_EDITBOOK, IDC_REMOVEBOOK};
+ for (size_t i = 0; i != sizeof(buttonids)/sizeof(buttonids[0]); i++)
+ {
+ HWND controlHWND = GetDlgItem(hwndDlg, buttonids[i]);
+ EnableWindow(controlHWND, has_selection);
+ }
+}
+
+static BOOL Bookmark_OnNotify( HWND hwnd, NMHDR *notification )
+{
+ if ( notification->idFrom == IDC_LIST )
+ {
+ if ( notification->code == NM_DBLCLK )
+ {
+ playFiles( ( !!( g_config->ReadInt( L"enqueuedef", 0 ) == 1 ) ) ^ ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ), 0 );
+ }
+ else if ( notification->code == LVN_BEGINDRAG )
+ {
+ SetCapture( hwnd );
+ }
+ else if ( notification->code == LVN_ITEMCHANGED )
+ {
+ Bookmark_ManageButtons( hwnd );
+ }
+ }
+ return FALSE;
+}
+
+void Bookmark_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+static BOOL Bookmark_OnInitDialog( HWND hwndDlg, HWND hwndFocus, LPARAM lParam )
+{
+ m_hwnd = hwndDlg;
+ m_bmlist.setwnd( GetDlgItem( hwndDlg, IDC_LIST ) );
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW( IDR_VIEW_BM_ACCELERATORS );
+ if ( accel )
+ WASABI_API_APP->app_addAccelerators( hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD );
+
+ if ( !view.play )
+ {
+ SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view );
+ }
+
+ // check the column widths and ensure that if <=0 it'll re-show
+ // (based on some forum reports where the width is set to zero somehow)
+ // -> shouldn't annoy anyone but you never know with the users, heh
+ int col_width1 = g_config->ReadInt( L"bm_col_title", 400 );
+ if ( col_width1 <= 0 )
+ col_width1 = 400;
+
+ int col_width2 = g_config->ReadInt( L"bm_col_filename", 240 );
+ if ( col_width2 <= 0 )
+ col_width2 = 240;
+
+ m_bmlist.AddCol( WASABI_API_LNGSTRINGW( IDS_BOOKMARK_TITLE ), col_width1 );
+ m_bmlist.AddCol( WASABI_API_LNGSTRINGW( IDS_BOOKMARK_FN_URL ), col_width2 );
+
+ groupBtn = g_config->ReadInt( L"groupbtn", 1 );
+ enqueuedef = ( g_config->ReadInt( L"enqueuedef", 0 ) == 1 );
+
+ Bookmark_OnDisplayChange();
+
+ m_headerhwnd = ListView_GetHeader( m_bmlist.getwnd() );
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = { ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG( IDC_BUTTON_CUSTOM, IDC_BUTTON_ENQUEUE ), (INT_PTR)L"ml_bookmark" };
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p );
+ if ( pszTextW && pszTextW[ 0 ] != 0 )
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW( hwndDlg, IDC_BUTTON_CUSTOM, pszTextW );
+ }
+ else
+ customAllowed = FALSE;
+
+ MLSKINWINDOW m = { 0 };
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ m.hwndToSkin = hwndDlg;
+ MLSkinWindow( plugin.hwndLibraryParent, &m );
+
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ m.hwndToSkin = m_bmlist.getwnd();
+ MLSkinWindow( mediaLibrary.library, &m );
+
+ m.skinType = SKINNEDWND_TYPE_BUTTON;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | ( groupBtn ? SWBS_SPLITBUTTON : 0 );
+ FLICKERFIX ff = { 0, FFM_ERASEINPAINT };
+ const int buttonids[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM };
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ ff.hwnd = m.hwndToSkin = GetDlgItem( hwndDlg, buttonids[ i ] );
+ if ( IsWindow( m.hwndToSkin ) )
+ {
+ MLSkinWindow( plugin.hwndLibraryParent, &m );
+ SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff );
+ }
+ }
+
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ const int buttonidz[] = { IDC_EDITBOOK, IDC_REMOVEBOOK };
+ for ( size_t i = 0; i != sizeof( buttonidz ) / sizeof( buttonidz[ 0 ] ); i++ )
+ {
+ ff.hwnd = m.hwndToSkin = GetDlgItem( hwndDlg, buttonidz[ i ] );
+ if ( IsWindow( m.hwndToSkin ) )
+ {
+ MLSkinWindow( plugin.hwndLibraryParent, &m );
+ SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff );
+ }
+ }
+
+ Bookmark_ManageButtons( hwndDlg );
+ Bookmark_UpdateButtonText( hwndDlg, enqueuedef == 1 );
+
+ SetTimer( hwndDlg, 100, 15, NULL );
+
+ return TRUE;
+}
+
+static void Bookmark_OnTimer(HWND hwnd, UINT id)
+{
+ if (id == 100)
+ {
+ KillTimer(hwnd,100);
+ // populate list
+ readbookmarks();
+ }
+}
+
+void Bookmark_DropFiles(HWND hwnd, HDROP hDrop)
+{
+ wchar_t temp[2048] = {0};
+ int y = DragQueryFileW(hDrop, 0xffffffff, temp, 2048);
+
+ for (int x = 0; x < y; x ++)
+ {
+ DragQueryFileW(hDrop, x, temp, 2048);
+ mediaLibrary.AddBookmarkW(temp);
+ }
+}
+
+static BOOL Bookmark_OnMLDropItem(mlDropItemStruct *dis)
+{
+ if (dis)
+ {
+ if (dis->type != ML_TYPE_ITEMRECORDLISTW && dis->type != ML_TYPE_ITEMRECORDLIST &&
+ dis->type != ML_TYPE_FILENAMES && dis->type != ML_TYPE_FILENAMESW &&
+ dis->type != ML_TYPE_STREAMNAMES && dis->type != ML_TYPE_STREAMNAMESW &&
+ dis->type != ML_TYPE_CDTRACKS &&
+ dis->type != ML_TYPE_PLAYLIST && dis->type != ML_TYPE_PLAYLISTS)
+ {
+ dis->result=-1;
+ }
+ else
+ {
+ dis->result=1;
+ if (dis->data)
+ {
+ if (dis->type == ML_TYPE_ITEMRECORDLIST || dis->type == ML_TYPE_CDTRACKS)
+ {
+ itemRecordList *p=(itemRecordList *)dis->data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddBookmark(p->Items[x].filename);
+ }
+ else if (dis->type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *p=(itemRecordListW *)dis->data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddBookmark(AutoChar(p->Items[x].filename));
+ }
+ else if (dis->type == ML_TYPE_FILENAMES || dis->type == ML_TYPE_STREAMNAMES) // playlist
+ {
+ char *p=(char*)dis->data;
+ while (p && *p)
+ {
+ mediaLibrary.AddBookmark(p);
+ p+=strlen(p)+1;
+ }
+ }
+ else if (dis->type == ML_TYPE_FILENAMESW || dis->type == ML_TYPE_STREAMNAMESW) // playlist
+ {
+ wchar_t *p=(wchar_t*)dis->data;
+ while (p && *p)
+ {
+ mediaLibrary.AddBookmarkW(p);
+ p+=wcslen(p)+1;
+ }
+ }
+ else if(dis->type == ML_TYPE_PLAYLIST)
+ {
+ mediaLibrary.AddBookmarkW((wchar_t*)((mlPlaylist*)dis->data)->filename);
+ }
+ else if(dis->type == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)dis->data;
+ while (playlists && *playlists)
+ {
+ mlPlaylist *pl = *playlists;
+ mediaLibrary.AddBookmarkW((wchar_t*)pl->filename);
+ playlists++;
+ }
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+#define HANDLE_ML_DROPITEM(func) case ML_CHILDIPC_DROPITEM: return Bookmark_OnMLDropItem((mlDropItemStruct *)wParam);
+INT_PTR CALLBACK view_bmDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam);
+ if (a) return a;
+
+ switch(uMsg)
+ {
+ HANDLE_MSG(hwndDlg, WM_INITDIALOG, Bookmark_OnInitDialog);
+ HANDLE_MSG(hwndDlg, WM_TIMER, Bookmark_OnTimer);
+ HANDLE_MSG(hwndDlg, WM_COMMAND, Bookmark_OnCommand);
+ HANDLE_MSG(hwndDlg, WM_DESTROY, Bookmark_OnDestroy);
+ HANDLE_MSG(hwndDlg, WM_MOUSEMOVE, Bookmark_OnMouseMove);
+ HANDLE_MSG(hwndDlg, WM_LBUTTONUP, Bookmark_OnLButtonUp);
+ HANDLE_MSG(hwndDlg, WM_DROPFILES, Bookmark_DropFiles);
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ myMenu = TRUE;
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ myMenu = FALSE;
+ }
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ return Bookmark_OnDisplayChange();
+
+ case WM_NOTIFY:
+ return Bookmark_OnNotify(hwndDlg, (LPNMHDR)lParam);
+
+ case WM_CONTEXTMENU:
+ bookmarks_contextMenu(hwndDlg, (HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ return 0;
+
+ case WM_APP + 104:
+ {
+ Bookmark_UpdateButtonText(hwndDlg, (int)wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+
+ case WM_ML_CHILDIPC:
+ if (lParam == ML_CHILDIPC_DROPITEM && wParam)
+ HANDLE_ML_DROPITEM(Bookmark_OnMLDropItem);
+ }
+ return FALSE;
+} \ No newline at end of file
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
diff --git a/Src/Plugins/Library/ml_disc/M3UWriter.cpp b/Src/Plugins/Library/ml_disc/M3UWriter.cpp
new file mode 100644
index 00000000..d4ec2a13
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/M3UWriter.cpp
@@ -0,0 +1,91 @@
+#include "M3UWriter.h"
+#include <shlwapi.h>
+
+static void MakeRelativePathName( const char *filename, char *outFile, const char *path )
+{
+ char outPath[ MAX_PATH ] = { 0 };
+
+ int common = PathCommonPrefixA( path, filename, outPath );
+ if ( common && common == lstrlenA( path ) )
+ {
+ PathAddBackslashA( outPath );
+ const char *p = filename + lstrlenA( outPath );
+ lstrcpyA( outFile, p );
+ }
+ else if ( !PathIsUNCA( filename ) && PathIsSameRootA( filename, path ) )
+ {
+ lstrcpyA( outFile, filename + 2 );
+ }
+}
+
+M3UWriter::M3UWriter()
+{
+ memset( basePath, 0, sizeof( basePath ) );
+}
+
+M3UWriter::~M3UWriter()
+{
+ Close();
+}
+
+int M3UWriter::Open( char *filename, int extendedMode )
+{
+ fp = fopen( filename, "wt" );
+ if ( !fp )
+ return 0;
+
+ if ( extendedMode )
+ fprintf( fp, "#EXTM3U\n" );
+
+ extended = extendedMode;
+
+ lstrcpynA( basePath, filename, MAX_PATH );
+ PathRemoveFileSpecA( basePath );
+
+ return 1;
+}
+
+int M3UWriter::Open( FILE *_fp, char *filename, int extendedMode )
+{
+ fp = _fp;
+ if ( !fp )
+ return 0;
+
+ if ( extendedMode )
+ fprintf( fp, "#EXTM3U\n" );
+
+ extended = extendedMode;
+
+ lstrcpynA( basePath, filename, MAX_PATH );
+ PathRemoveFileSpecA( basePath );
+
+ return 1;
+}
+
+void M3UWriter::SetFilename( char *filename )
+{
+ //char temp[ MAX_PATH ] = { 0 };
+ //MakeRelativePathName( filename, temp, basePath );
+ fprintf( fp, "%s\n", filename );
+}
+
+void M3UWriter::SetExtended( char *filename, char *title, int length )
+{
+ if ( !extended )
+ SetFilename( filename );
+ else
+ {
+ //char temp[ MAX_PATH ] = { 0 };
+ //MakeRelativePathName( filename, temp, basePath );
+ fprintf( fp, "#EXTINF:%d,%s\n%s\n", length, title, filename );
+ }
+}
+
+void M3UWriter::Close()
+{
+ if ( fp != NULL )
+ {
+ fclose( fp );
+ fp = NULL;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/M3UWriter.h b/Src/Plugins/Library/ml_disc/M3UWriter.h
new file mode 100644
index 00000000..2f4335d5
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/M3UWriter.h
@@ -0,0 +1,25 @@
+#ifndef NULLSOFT_M3UWRITERH
+#define NULLSOFT_M3UWRITERH
+
+#include <stdio.h>
+#include <windows.h>
+
+class M3UWriter
+{
+public:
+ M3UWriter();
+ virtual ~M3UWriter();
+
+ int Open(char *filename, int extendedMode);
+ int Open( FILE *_fp, char *filename, int extendedMode );
+ void SetFilename( char *filename );
+ void SetExtended( char *filename, char *title, int length );
+ void Close();
+
+private:
+ char basePath[MAX_PATH];
+ int extended = 0;
+ FILE *fp = NULL;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/PLSWriter.cpp b/Src/Plugins/Library/ml_disc/PLSWriter.cpp
new file mode 100644
index 00000000..8231d5b7
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/PLSWriter.cpp
@@ -0,0 +1,65 @@
+#include "PLSWriter.h"
+#include <windows.h>
+
+PLSWriter::PLSWriter() : numEntries(0), entryUsed(0)
+{
+ memset(plsFile, 0, sizeof(plsFile));
+}
+
+void PLSWriter::Open(char *filename)
+{
+ lstrcpynA(plsFile, filename, MAX_PATH);
+}
+
+void PLSWriter::SetFilename(char *filename)
+{
+ char fieldbuf[32] = {0};
+ BeforeSet();
+ wsprintfA(fieldbuf,"File%u",numEntries);
+ WritePrivateProfileStringA("playlist",fieldbuf,filename,plsFile);
+}
+
+void PLSWriter::SetTitle(char *title)
+{
+ char fieldbuf[32] = {0};
+ BeforeSet();
+ wsprintfA(fieldbuf,"Title%u",numEntries);
+ WritePrivateProfileStringA("playlist",fieldbuf,title,plsFile);
+}
+
+void PLSWriter::SetLength(int length)
+{
+ char fieldbuf[32] = {0};
+ char lenStr[32] = {0};
+ BeforeSet();
+ wsprintfA(fieldbuf,"Length%u",numEntries);
+ wsprintfA(lenStr,"%d", length);
+ WritePrivateProfileStringA("playlist",fieldbuf,lenStr,plsFile);
+}
+
+void PLSWriter::BeforeSet()
+{
+ if (!entryUsed)
+ {
+ entryUsed=1;
+ numEntries++;
+ }
+}
+
+void PLSWriter::Next()
+{
+ entryUsed=0;
+}
+
+void PLSWriter::Close()
+{
+ if (numEntries)
+ {
+ char temp[32] = {0};
+ wsprintfA(temp,"%u",numEntries);
+ WritePrivateProfileStringA("playlist","NumberOfEntries",temp,plsFile);
+ WritePrivateProfileStringA("playlist","Version","2",plsFile);
+ }
+ numEntries=0;
+ entryUsed=0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/PLSWriter.h b/Src/Plugins/Library/ml_disc/PLSWriter.h
new file mode 100644
index 00000000..d97ddd47
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/PLSWriter.h
@@ -0,0 +1,23 @@
+#ifndef NULLSOFT_PLSWRITERH
+#define NULLSOFT_PLSWRITERH
+
+#include <windows.h>
+
+class PLSWriter
+{
+public:
+ PLSWriter();
+ void Open(char *filename);
+ void SetFilename(char *filename);
+ void SetTitle(char *title);
+ void SetLength(int length);
+ void Next(); // tells the pls writer to start writing info for the next item
+ void Close();
+private:
+ void BeforeSet();
+ unsigned int numEntries;
+ int entryUsed;
+ char plsFile[MAX_PATH];
+
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/ReplayGain.cpp b/Src/Plugins/Library/ml_disc/ReplayGain.cpp
new file mode 100644
index 00000000..fdbd973c
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/ReplayGain.cpp
@@ -0,0 +1,95 @@
+#include "main.h"
+#include "ReplayGain.h"
+#include <api/service/waservicefactory.h>
+static obj_replaygain *replayGain = 0;
+HANDLE rgThread = 0;
+static HANDLE killSwitch = 0;
+extern HWND m_extract_wnd;
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+DWORD WINAPI RGProc(void *data)
+{
+ while (WaitForSingleObjectEx(killSwitch, INFINITE, TRUE) != WAIT_OBJECT_0);
+
+ return 0;
+}
+
+void CreateGain()
+{
+ killSwitch = CreateEvent(0, FALSE, FALSE, 0);
+ DWORD dummy;
+ rgThread = CreateThread(0, 0, RGProc, 0, 0, &dummy);
+}
+
+void CALLBACK StartGain(ULONG_PTR data)
+{
+ int mode = (int)data;
+
+ ServiceBuild(replayGain, RGGUID);
+ if (replayGain)
+ replayGain->Open(mode);
+}
+
+void CALLBACK WriteGain(ULONG_PTR data)
+{
+ if (replayGain)
+ replayGain->Write();
+
+ HANDLE notifyHandle =(HANDLE)data;
+ if (notifyHandle)
+ SetEvent(notifyHandle);
+
+ PostMessage(m_extract_wnd, WM_APP+4, 0, 0);
+}
+
+void CALLBACK CalculateGain(ULONG_PTR data)
+{
+ wchar_t *lastfn = (wchar_t *)data;
+ if (replayGain)
+ {
+ PostMessage(m_extract_wnd, WM_APP+2, 0, 0);
+ replayGain->ProcessTrack(lastfn);
+ }
+ free(lastfn);
+ PostMessage(m_extract_wnd, WM_APP+3, 0, 0);
+}
+
+void CALLBACK CloseGain(ULONG_PTR data)
+{
+ if (replayGain)
+ {
+ replayGain->Close();
+ ServiceRelease(replayGain, RGGUID);
+ replayGain = 0;
+ }
+}
+
+void CALLBACK QuitThread(ULONG_PTR data)
+{
+ if (rgThread)
+ {
+ SetEvent(killSwitch);
+ }
+}
diff --git a/Src/Plugins/Library/ml_disc/ReplayGain.h b/Src/Plugins/Library/ml_disc/ReplayGain.h
new file mode 100644
index 00000000..3e79e8c4
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/ReplayGain.h
@@ -0,0 +1,16 @@
+#ifndef NULLSOFT_ML_DISC_REPLAYGAIN_H
+#define NULLSOFT_ML_DISC_REPLAYGAIN_H
+
+#include <windows.h>
+#include "../ml_rg/obj_replaygain.h"
+
+void CALLBACK StartGain(ULONG_PTR data);
+void CALLBACK WriteGain(ULONG_PTR data);
+void CALLBACK CalculateGain(ULONG_PTR data);
+void CALLBACK CloseGain(ULONG_PTR data);
+void CALLBACK QuitThread(ULONG_PTR data);
+void CreateGain();
+
+extern HANDLE rgThread;
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/api__ml_disc.h b/Src/Plugins/Library/ml_disc/api__ml_disc.h
new file mode 100644
index 00000000..ac88b8ff
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/api__ml_disc.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_ML_DISC_API_H
+#define NULLSOFT_ML_DISC_API_H
+
+#include <api/service/waServiceFactory.h>
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#endif // !NULLSOFT_ML_DISC_API_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/banner.cpp b/Src/Plugins/Library/ml_disc/banner.cpp
new file mode 100644
index 00000000..5d0b1b15
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/banner.cpp
@@ -0,0 +1,222 @@
+#include "main.h"
+#include ".\banner.h"
+#include "..\gen_ml\graphics.h"
+
+
+MLBanner::MLBanner(void)
+{
+ bmpBck = NULL;
+ bmpLogo = NULL;
+ bmpLogoMask = NULL;
+ bmpBanner = NULL;
+ m_hwnd = NULL;
+ oldWndProc = NULL;
+
+ color1 = RGB(0,0,0);
+ color2 = RGB(255,255,255);
+
+ hInstance = NULL;
+ logoResId = 0;
+ bgndResId = 0;
+
+ SetRect(&rcBanner, 0,0,0,0);
+}
+MLBanner::~MLBanner(void)
+{
+ DestroyImages();
+ SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)oldWndProc);
+ oldWndProc = NULL;
+}
+
+void MLBanner::SetColors(int color1, int color2)
+{
+ this->color1 = color1;
+ this->color2 = color2;
+ ReloadImages();
+}
+
+void MLBanner::SetImages(HINSTANCE hInstance, int bgndResId, int logoResId)
+{
+ this->hInstance = hInstance;
+ this->logoResId = logoResId;
+ this->bgndResId = bgndResId;
+ ReloadImages();
+}
+
+void MLBanner::ReloadImages(void)
+{
+ DestroyImages();
+ if (hInstance)
+ {
+ if (bgndResId)
+ {
+ bmpBck = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(bgndResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpBck) bmpBck = PatchBitmapColors24(bmpBck, color1, color2, Filter1);
+ }
+ if (logoResId)
+ {
+ bmpLogo = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(logoResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpLogo)
+ {
+ bmpLogoMask = CreateBitmapMask(bmpLogo, 1,1);
+ }
+ }
+ }
+
+}
+
+void MLBanner::DestroyImages(void)
+{
+ if (bmpBck) DeleteObject(bmpBck);
+ bmpBck = NULL;
+
+ if (bmpLogo) DeleteObject(bmpLogo);
+ bmpLogo = NULL;
+
+ if (bmpLogoMask) DeleteObject(bmpLogoMask);
+ bmpLogoMask = NULL;
+
+ if (bmpBanner) DeleteObject(bmpBanner);
+ bmpBanner = NULL;
+}
+
+
+
+void MLBanner::UpdateBunnerBmp(void)
+{
+ if (bmpBanner) DeleteObject(bmpBanner);
+
+ HDC hdc = GetDC(m_hwnd);
+
+ bmpBanner = CreateCompatibleBitmap(hdc, rcBanner.right, rcBanner.bottom);
+ HDC memDstDC = CreateCompatibleDC (hdc);
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ HBITMAP obmp1 = (HBITMAP)SelectObject(memDstDC, bmpBanner);
+ HBITMAP obmp2 = (HBITMAP)SelectObject(memSrcDC, bmpBck);
+
+ for (int i = 0; i < rcBanner.right; i++)
+ {
+ BitBlt(memDstDC,
+ i,0,
+ 1, rcBanner.bottom,
+ memSrcDC,
+ 0,0,
+ SRCCOPY);
+
+ }
+
+ BITMAP bm;
+ GetObject(bmpLogo, sizeof(BITMAP), &bm);
+
+ SelectObject(memSrcDC, bmpLogoMask);
+ BitBlt(memDstDC,
+ 6,
+ max(2, (rcBanner.bottom - bm.bmHeight) / 2),
+ min(rcBanner.right - 4, bm.bmWidth),
+ min(rcBanner.bottom - 2, bm.bmHeight),
+ memSrcDC,
+ 0,0,
+ SRCAND);
+
+ SelectObject(memSrcDC, bmpLogo);
+ BitBlt(memDstDC,
+ 6,
+ max(2, (rcBanner.bottom - bm.bmHeight) / 2),
+ min(rcBanner.right - 4, bm.bmWidth),
+ min(rcBanner.bottom - 2, bm.bmHeight),
+ memSrcDC,
+ 0,0,
+ SRCPAINT);
+
+ ReleaseDC(m_hwnd, hdc);
+
+ SelectObject(memDstDC, obmp1);
+ SelectObject(memSrcDC, obmp2);
+
+ DeleteDC(memDstDC);
+ DeleteDC(memSrcDC);
+
+}
+
+void MLBanner::Init(HWND hwnd)
+{
+ m_hwnd = hwnd;
+ SetWindowLong(hwnd,GWL_USERDATA,(LONG)this);
+ oldWndProc= (WNDPROC) SetWindowLong(hwnd, GWL_WNDPROC, (LONG)newWndProc);
+ UpdateBunnerBmp();
+}
+
+BOOL CALLBACK MLBanner::newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ MLBanner *banner = (MLBanner*)GetWindowLong(hwndDlg, GWL_USERDATA);
+
+ switch(uMsg)
+ {
+ case WM_SIZE:
+ if (SIZE_MINIMIZED != wParam)
+ {
+ SetRect(&banner->rcBanner, 0,0,LOWORD(lParam),HIWORD(lParam));
+ banner->UpdateBunnerBmp();
+ }
+ break;
+ case WM_ERASEBKGND:
+ {
+ HDC hdc = GetDC(hwndDlg);
+ if (banner->bmpBanner)
+ {
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ HBITMAP obmp = (HBITMAP)SelectObject(memSrcDC, banner->bmpBanner);
+ StretchBlt( hdc,
+ banner->rcBanner.left,
+ banner->rcBanner.top,
+ banner->rcBanner.right - banner->rcBanner.left,
+ banner->rcBanner.bottom - banner->rcBanner.top,
+ memSrcDC,
+ banner->rcBanner.left,
+ banner->rcBanner.top,
+ banner->rcBanner.right - banner->rcBanner.left,
+ banner->rcBanner.bottom - banner->rcBanner.top,
+ SRCCOPY);
+ SelectObject(memSrcDC, obmp);
+ DeleteDC(memSrcDC);
+ }
+ ReleaseDC(hwndDlg, hdc);
+ }
+ return TRUE;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT pt;
+ HDC hdc = BeginPaint(hwndDlg, &pt);
+ if (!banner->bmpBanner)
+ {
+ SetRect(&banner->rcBanner, 0,0,pt.rcPaint.right - pt.rcPaint.left, pt.rcPaint.bottom - pt.rcPaint.top);
+ banner->UpdateBunnerBmp();
+ }
+ if (banner->bmpBanner)
+ {
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ HBITMAP obmp = (HBITMAP)SelectObject(memSrcDC, banner->bmpBanner);
+ StretchBlt( hdc,
+ pt.rcPaint.left,
+ pt.rcPaint.top,
+ pt.rcPaint.right - pt.rcPaint.left,
+ pt.rcPaint.bottom - pt.rcPaint.top,
+ memSrcDC,
+ pt.rcPaint.left,
+ pt.rcPaint.top,
+ pt.rcPaint.right - pt.rcPaint.left,
+ pt.rcPaint.bottom - pt.rcPaint.top,
+ SRCCOPY);
+ SelectObject(memSrcDC, obmp);
+ DeleteDC(memSrcDC);
+ ValidateRect(hwndDlg, &pt.rcPaint);
+ }
+ EndPaint(hwndDlg, &pt);
+ }
+ break;
+ }
+
+ return CallWindowProc(banner->oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/banner.h b/Src/Plugins/Library/ml_disc/banner.h
new file mode 100644
index 00000000..9b7409cf
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/banner.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOFT_ML_BANNER_HEADER
+#define NULLSOFT_ML_BANNER_HEADER
+
+#include <windows.h>
+
+class MLBanner
+{
+public:
+ MLBanner(void);
+ ~MLBanner(void);
+
+public:
+
+ void SetColors(int color1, int color2);
+ void SetImages(HINSTANCE hInstance, int bgndResId, int logoResId);
+ void Init(HWND hwnd);
+ void ReloadImages(void);
+
+protected:
+ void DestroyImages(void);
+ void UpdateBunnerBmp(void);
+ static BOOL CALLBACK newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+private:
+
+ HWND m_hwnd;
+ HBITMAP bmpBck;
+ HBITMAP bmpLogo;
+ HBITMAP bmpLogoMask;
+ HBITMAP bmpBanner;
+
+ WNDPROC oldWndProc;
+
+ HINSTANCE hInstance;
+ int logoResId;
+ int bgndResId;
+
+ int color1;
+ int color2;
+
+ RECT rcBanner;
+};
+
+#endif // NULLSOFT_ML_BANNER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/cdburn.cpp b/Src/Plugins/Library/ml_disc/cdburn.cpp
new file mode 100644
index 00000000..6f05d06a
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/cdburn.cpp
@@ -0,0 +1,2155 @@
+#include "main.h"
+#include <stdio.h>
+#include "../nu/ns_wc.h"
+#include "resource.h"
+#include "../nu/listview.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/ChildSizer.h"
+#include "config.h"
+#include "../../General/gen_ml/gaystring.h"
+#include "../Winamp/burn.h"
+#include "../Winamp/strutil.h"
+#include <std::vector>
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include <api/service/waServiceFactory.h>
+#include "../playlist/ifc_playlistloadercallback.h"
+#include "../playlist/api_playlistmanager.h"
+#include <imapi.h>
+#include <imapierror.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+
+//shit to finish:
+//-erase CDRWs
+//-cache the veritas handle
+//-recurse add folders
+//-check for available space on HD before burning
+//-the resampling in convert
+
+
+#define WM_EX_OPCOMPLETED (WM_USER + 0x100)
+
+class PLCallBack : ifc_playlistloadercallback
+{
+public:
+ PLCallBack(void) : fileList(0) {};
+ ~PLCallBack(void) {};
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ fileList->push_back(new GayString(AutoChar(filename)));
+ }
+ RECVS_DISPATCH;
+public:
+ std::vector<GayString*> *fileList;
+};
+
+#define CBCLASS PLCallBack
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+class PLCallBackW : ifc_playlistloadercallback
+{
+public:
+ PLCallBackW(void) : fileList(0) {};
+ ~PLCallBackW(void) {};
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ fileList->push_back(new GayStringW(filename));
+ }
+ RECVS_DISPATCH;
+public:
+ std::vector<GayStringW*> *fileList;
+};
+
+#define CBCLASS PLCallBackW
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+static INT_PTR WINAPI DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+#include "../burnlib/burnlib.h"
+
+static W_ListView m_statuslist;
+
+static HWND m_hwndstatus;
+static char m_cdrom;
+static int m_is_cdrw, m_availsecs;
+static int m_max_speed;
+static int m_dragging, m_drag_item;
+static HWND prevWnd = NULL;
+itemRecordListW itemCache[100] = {0};
+static int percentCompleted = 0;
+static DWORD pidBurner = 0;
+
+static HFONT hPLFont = NULL;
+
+static int LETTERTOINDEX(char c)
+{
+ c = (char)toupper(c);
+ if (c < 'A') c = 'A';
+ if (c > 'Z') c = 'Z';
+ return c -'A';
+}
+
+#include "../winamp/wa_ipc.h"
+
+
+#define TIMER_NOTIFYINFO_ID 1985
+#define TIMER_NOTIFYINFO_DELAY 200
+
+
+static ChildWndResizeItem burnwnd_rlist[] =
+{
+ {IDC_LIST2, 0x0011},
+ {IDC_CDINFO, 0x0010},
+ {IDC_ADD, 0x0101},
+ {IDC_BURN, 0x0101},
+ {IDC_CLEAR, 0x0101},
+ {IDC_BURN_OPTS, 0x0101},
+ {IDC_CANCEL_BURN, 0x0101},
+ {IDC_LOGO, 0x1010},
+ {IDC_BTN_SHOWINFO, 0x1111},
+};
+static _inline void code(long* v, long* k)
+{
+ unsigned long y = v[0], z = v[1], sum = 0, /* set up */
+ delta = 0x9e3779b9, n = 32 ; /* key schedule constant*/
+
+ while (n-- > 0)
+ { /* basic cycle start */
+ sum += delta;
+ y += ((z << 4) + k[0]) ^(z + sum) ^((z >> 5) + k[1]);
+ z += ((y << 4) + k[2]) ^(y + sum) ^((y >> 5) + k[3]); /* end cycle */
+ }
+ v[0] = y; v[1] = z;
+
+}
+
+static void startBurn(HWND hwndDlg, char driveletter)
+{
+ g_config->WriteInt(L"cdburnmaxspeed", m_max_speed);
+
+ //write the temp playlist to disk
+ FILE *fp;
+ char filename[MAX_PATH] = {0}, tp[MAX_PATH] = {0};
+
+ pidBurner = 0;
+
+ if (!GetTempPathA(sizeof(tp), tp)) lstrcpynA(tp, ".", MAX_PATH);
+ if (GetTempFileNameA(tp, "BRN", 0, filename))
+ {
+ unlink(filename);
+ StringCchCatA(filename, MAX_PATH, ".m3u8");
+ }
+ else lstrcpynA(filename, "brn_tmp.m3u8", MAX_PATH);
+
+ fp = fopen(filename, "wt");
+ if (!fp)
+ {
+ wchar_t title[16] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_ERROR_WRITING_TEMP_BURN_LIST),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,16), MB_OK);
+ return ;
+ }
+ int idx = LETTERTOINDEX(driveletter);
+ fprintf(fp, "#EXTM3U\n");
+ for (int i = 0;i < itemCache[idx].Size;i++)
+ {
+ fprintf(fp, "#EXTINF:%d,%s\n", itemCache[idx].Items[i].length, (char *)AutoChar(itemCache[idx].Items[i].title, CP_UTF8));
+ fprintf(fp, "%s\n", (char *)AutoChar(itemCache[idx].Items[i].filename, CP_UTF8));
+ }
+ fclose(fp);
+
+ burnCDStruct bcds =
+ {
+ m_cdrom,
+ filename,
+ hwndDlg,
+ "",
+ };
+ pidBurner = (DWORD)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & bcds, IPC_BURN_CD);
+ if (!pidBurner)
+ {
+ wchar_t title[16] = {0};
+ unlink(filename);
+ MessageBox(hwndDlg, AutoWide(bcds.error), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,16), MB_OK);
+ }
+}
+
+static void link_handledraw(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_DRAWITEM)
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ wchar_t wt[123] = {0};
+ int y;
+ RECT r;
+ HPEN hPen, hOldPen;
+ GetDlgItemText(hwndDlg, (int)wParam, wt, 123);
+
+ // draw text
+ SetTextColor(di->hDC, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ r = di->rcItem;
+ r.left += 2;
+ DrawText(di->hDC, wt, -1, &r, DT_VCENTER | DT_SINGLELINE);
+
+ memset(&r, 0, sizeof(r));
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_CALCRECT);
+
+ // draw underline
+ y = di->rcItem.bottom - ((di->rcItem.bottom - di->rcItem.top) - (r.bottom - r.top)) / 2 - 1;
+ hPen = CreatePen(PS_SOLID, 0, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ hOldPen = (HPEN) SelectObject(di->hDC, hPen);
+ MoveToEx(di->hDC, di->rcItem.left + 2, y, NULL);
+ LineTo(di->hDC, di->rcItem.right + 2 - ((di->rcItem.right - di->rcItem.left) - (r.right - r.left)), y);
+ SelectObject(di->hDC, hOldPen);
+ DeleteObject(hPen);
+
+ }
+ }
+}
+
+static void refreshList()
+{
+ if (!m_hwndstatus) return ;
+
+ ListView_SetItemCount(m_statuslist.getwnd(), 0);
+ int idx = LETTERTOINDEX(m_cdrom);
+ ListView_SetItemCount(m_statuslist.getwnd(), itemCache[idx].Size);
+ if (itemCache[idx].Size > 0) ListView_RedrawItems(m_statuslist.getwnd(), 0, itemCache[idx].Size - 1);
+}
+static int m_last_trackpos;
+
+
+typedef struct _MEDIAINFO
+{
+ CHAR cLetter;
+ BOOL bInserted;
+ BOOL bRecordable;
+ BOOL bRewritable;
+ BOOL bBlank;
+ ULONG nSectorsFree;
+ ULONG nSectorsUsed;
+} MEDIAINFO;
+
+static HRESULT GetMediaInfoFromSonic(MEDIAINFO *pmi)
+{
+ HRESULT hr;
+
+ hr = S_OK;
+
+ char name[]= "cda://X.cda";
+ char buf2[64] = "";
+ char buf3[64] = "";
+
+ name[6] = pmi->cLetter;
+
+ pmi->bInserted = FALSE;
+ pmi->bRewritable = FALSE;
+ pmi->nSectorsFree = 0;
+ pmi->nSectorsUsed = 0;
+
+ pmi->bRecordable = TRUE;
+ getFileInfo(name, "cdtype", buf3, sizeof(buf3));
+ if (buf3[0] && 0 == lstrcmpA(buf3, "CDRW")) pmi->bRewritable = TRUE;
+
+ getFileInfo(name, "cdlengths", buf2, sizeof(buf2));
+ if (buf2[0])
+ {
+ pmi->bInserted = TRUE;
+ pmi->nSectorsFree = atoi(buf2);
+ }
+
+ return hr;
+}
+
+static void CALLBACK FreeAsyncParam(DM_NOTIFY_PARAM *phdr)
+{
+ switch(phdr->opCode)
+ {
+ case DMOP_IMAPIINFO:
+ break;
+ }
+ free(phdr);
+}
+static void FinishSetStatus(HWND hwndDlg, MEDIAINFO *pmi)
+{
+ int freesecs;
+ if(pmi->bInserted)
+ {
+ freesecs = (pmi->nSectorsFree * 2048) / (150 * 1024); //150kb/s as its considered DATA CD at this point in veritas
+ }
+ else
+ {
+ freesecs = 74 * 60; //Default to 74mns CDR
+ }
+
+ m_availsecs = freesecs;
+
+ int idx = LETTERTOINDEX(m_cdrom);
+ int usedlen = 0;
+ int truncpos = 0;
+ for (int i = 0;i < itemCache[idx].Size;i++)
+ {
+ usedlen += itemCache[idx].Items[i].length;
+ if (usedlen > m_availsecs)
+ truncpos++;
+ }
+ m_availsecs -= usedlen;
+
+ wchar_t status[256] = {0};
+ if (!pmi->bInserted)
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_BLANK_CDR_IN_DRIVE,status,512);
+ else
+ {
+ StringCchPrintf(status, 512, WASABI_API_LNGSTRINGW(IDS_X_CAPACITY_DETAILS),
+ (pmi->bRewritable) ? L"CD-RW" : L"CD-R" , freesecs / 60, freesecs % 60);
+ }
+
+ wchar_t temp[16] = {0};
+ StringCchPrintf(status + wcslen(status), 256,
+ WASABI_API_LNGSTRINGW(IDS_USED_X_X_TRACKS),
+ usedlen / 60,
+ usedlen % 60,
+ itemCache[idx].Size,
+ WASABI_API_LNGSTRINGW_BUF(itemCache[idx].Size == 1 ? IDS_TRACK : IDS_TRACKS,temp,16));
+
+ if (freesecs && pmi->bInserted)
+ {
+ if (m_availsecs >= 0) StringCchPrintf(status + wcslen(status), 256, WASABI_API_LNGSTRINGW(IDS_AVAILABLE_X_X),
+ m_availsecs / 60, m_availsecs % 60);
+ else StringCchPrintf(status + wcslen(status), 256, WASABI_API_LNGSTRINGW(IDS_X_OVER_CAPACITY_REMOVE_X_TRACKS),
+ -m_availsecs / 60, -m_availsecs % 60, truncpos);
+ }
+ SetDlgItemText(hwndDlg, IDC_CDINFO, status);
+ m_last_trackpos = -1;
+
+ m_is_cdrw = pmi->bRewritable;
+ ListView_RedrawItems(m_statuslist.getwnd(), 0, itemCache[idx].Size - 1);
+}
+
+static void SetStatus(HWND hwndDlg, CHAR cLetter)
+{
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(cLetter) &&
+ NULL != (m_burning_other_wnd = cdburn_FindBurningHWND(cLetter)))
+ {
+ prevWnd = (HWND)SendMessage(m_burning_other_wnd, WM_BURNUPDATEOWNER, 0, (LPARAM)hwndDlg);
+ if (prevWnd == hwndDlg) prevWnd = NULL;
+ DWORD state = (DWORD)(INT_PTR)SendMessage(m_burning_other_wnd, WM_BURNGETSTATUS, BURNSTATUS_STATE, 0);
+ if (state)
+ {
+ SetDlgItemText(hwndDlg, IDC_CDINFO, WASABI_API_LNGSTRINGW(IDS_BURNING));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CLEAR), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_ADD), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BURN), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BURN_OPTS), SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BURN), SW_SHOWNA);
+ SetDlgItemText(hwndDlg, IDC_CANCEL_BURN, WASABI_API_LNGSTRINGW(IDS_CANCEL_BURN));
+ m_availsecs = 0;
+ m_last_trackpos = -1;
+ m_is_cdrw = 0;
+ percentCompleted = 0;
+ UpdateWindow(hwndDlg);
+ }
+
+ SendMessage(hwndDlg, WM_BURNNOTIFY, BURN_STATECHANGED, state);
+ ShowWindow(m_burning_other_wnd, g_config->ReadInt(L"cdburnstatuswnd", 1) ? SW_SHOWNA : SW_HIDE);
+ PostMessage(m_burning_other_wnd, WM_BURNCONFIGCHANGED, BURNCFG_HIDEVIEW, !g_config->ReadInt(L"cdburnstatuswnd", 1));
+ PostMessage(m_burning_other_wnd, WM_BURNCONFIGCHANGED, BURNCFG_AUTOEJECT, g_config->ReadInt(L"cdburnautoeject", 1));
+ PostMessage(m_burning_other_wnd, WM_BURNCONFIGCHANGED, BURNCFG_ADDTODB, g_config->ReadInt(L"cdburnautoadddb", 0));
+ PostMessage(m_burning_other_wnd, WM_BURNCONFIGCHANGED, BURNCFG_AUTOCLOSE, g_config->ReadInt(L"cdburnautoclose", 0));
+ }
+ else
+ {
+ BOOL br;
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CLEAR), SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_ADD), SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BURN), SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BURN_OPTS), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BURN), SW_HIDE);
+
+ SetDlgItemText(hwndDlg, IDC_CDINFO, WASABI_API_LNGSTRINGW(IDS_CALCULATING));
+
+ UpdateWindow(hwndDlg);
+
+ DM_IMAPI_PARAM *pIMAPI = (DM_IMAPI_PARAM*)calloc(1, sizeof(DM_IMAPI_PARAM));
+ if (pIMAPI)
+ {
+ pIMAPI->header.cLetter = cLetter;
+ pIMAPI->header.callback = (INT_PTR)hwndDlg;
+ pIMAPI->header.uMsg = WM_EX_OPCOMPLETED;
+ pIMAPI->header.fnFree = FreeAsyncParam;
+ pIMAPI->header.fFlags = DMF_QUERYMEDIATYPE | DMF_QUERYMEDIAINFO;
+ br = DriveManager_GetIMAPIInfo(pIMAPI);
+ }
+ else br = FALSE;
+ if (!br) SetDlgItemText(hwndDlg, IDC_CDINFO, WASABI_API_LNGSTRINGW(IDS_DISC_READ_ERROR));
+ }
+}
+
+static void deleteSelectedItems(HWND hwndDlg, CHAR cLetter)
+{
+ int idx = LETTERTOINDEX(cLetter);
+ for (int i = itemCache[idx].Size - 1;i >= 0;i--)
+ {
+ if (m_statuslist.GetSelected(i))
+ {
+ freeRecord(&itemCache[idx].Items[i]);
+ int l = itemCache[idx].Size - i - 1;
+ if (l > 0) memcpy(&itemCache[idx].Items[i], &itemCache[idx].Items[i + 1], sizeof(itemRecordW)*l);
+ itemCache[idx].Size--;
+ }
+ }
+ SetStatus(hwndDlg, cLetter);
+ refreshList();
+}
+
+static void selectAll()
+{
+ int l = m_statuslist.GetCount();
+ for (int i = 0;i < l;i++) m_statuslist.SetSelected(i);
+}
+
+static void playSelectedItems(HWND hwndDlg, int enqueue)
+{
+ int idx = LETTERTOINDEX(m_cdrom);
+ if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+
+ for (int i = 0;i < itemCache[idx].Size;i++)
+ {
+ if (!m_statuslist.GetSelected(i)) continue;
+
+ //send the file to winamp
+ COPYDATASTRUCT cds;
+ cds.dwData = IPC_PLAYFILEW;
+ cds.lpData = (void *)itemCache[idx].Items[i].filename;
+ cds.cbData = (DWORD)(sizeof(wchar_t *) * (wcslen(itemCache[idx].Items[i].filename) + 1)); // include space for null char
+ SendMessageW(plugin.hwndWinampParent, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);
+ }
+
+ if (!enqueue) SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+}
+BOOL CALLBACK CantBurnProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t *message = (wchar_t *)lParam;
+
+ // due to quirks with the more common resource editors, is easier to just store the string
+ // internally only with \n and post-process to be \r\n (as here) so it will appear correctly
+ // on new lines as is wanted (silly multiline edit controls)
+ wchar_t tmp2[1024] = {0}, *t2 = tmp2;
+ while(message && *message && (t2 - tmp2 < 1024))
+ {
+ if(*message == L'\n')
+ {
+ *t2 = L'\r';
+ t2 = CharNextW(t2);
+ }
+ *t2 = *message;
+ message = CharNextW(message);
+ t2 = CharNextW(t2);
+ }
+
+ SetDlgItemText(hwnd, IDC_MESSAGE2, tmp2);
+ }
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ EndDialog(hwnd, 0);
+ break;
+ case IDCANCEL:
+ EndDialog(hwnd, -1);
+ break;
+ }
+ }
+ return 0;
+}
+
+HRESULT ResolveShortCut(HWND hwnd, LPCSTR pszShortcutFile, LPSTR pszPath)
+{
+ HRESULT hres;
+ IShellLinkA* psl;
+ WIN32_FIND_DATAA wfd;
+
+ *pszPath = 0; // assume failure
+
+ hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkA, (void **) & psl);
+ if (SUCCEEDED(hres))
+ {
+ IPersistFile* ppf;
+
+ hres = psl->QueryInterface(IID_IPersistFile, (void **) & ppf); // OLE 2! Yay! --YO
+ if (SUCCEEDED(hres))
+ {
+ wchar_t wsz[MAX_PATH] = {0};
+ MultiByteToWideCharSZ(CP_ACP, 0, pszShortcutFile, -1, wsz, MAX_PATH);
+
+ hres = ppf->Load(wsz, STGM_READ);
+ if (SUCCEEDED(hres))
+ {
+ hres = psl->Resolve(hwnd, SLR_ANY_MATCH);
+ if (SUCCEEDED(hres))
+ {
+ char szGotPath[MAX_PATH] = {0};
+ lstrcpynA(szGotPath, pszShortcutFile, MAX_PATH);
+ hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATAA *) & wfd,
+ SLGP_SHORTPATH);
+ lstrcpynA(pszPath, szGotPath, MAX_PATH);
+ }
+ }
+ ppf->Release();
+ }
+ psl->Release();
+ }
+ return SUCCEEDED(hres);
+}
+
+HRESULT ResolveShortCut(HWND hwnd, LPCWSTR pszShortcutFile, LPWSTR pszPath)
+{
+ HRESULT hres;
+ IShellLinkW* psl;
+ WIN32_FIND_DATAW wfd;
+
+ *pszPath = 0; // assume failure
+
+ hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, (void **) & psl);
+ if (SUCCEEDED(hres))
+ {
+ IPersistFile* ppf;
+
+ hres = psl->QueryInterface(IID_IPersistFile, (void **) & ppf); // OLE 2! Yay! --YO
+ if (SUCCEEDED(hres))
+ {
+ /*wchar_t wsz[MAX_PATH] = {0};
+ MultiByteToWideCharSZ(CP_ACP, 0, pszShortcutFile, -1, wsz, MAX_PATH);*/
+
+ hres = ppf->Load(pszShortcutFile/*wsz*/, STGM_READ);
+ if (SUCCEEDED(hres))
+ {
+ hres = psl->Resolve(hwnd, SLR_ANY_MATCH);
+ if (SUCCEEDED(hres))
+ {
+ wchar_t szGotPath[MAX_PATH] = {0};
+ wcsncpy(szGotPath, pszShortcutFile, MAX_PATH);
+ hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATAW *) & wfd,
+ SLGP_SHORTPATH);
+ wcsncpy(pszPath, szGotPath, MAX_PATH);
+ }
+ }
+ ppf->Release();
+ }
+ psl->Release();
+ }
+ return SUCCEEDED(hres);
+}
+
+static int checkFile(const char *file)
+{
+ //check if the file is supported by winamp
+ const char *ext = extension(file);
+ if (!ext || !ext[0]) return 0;
+ if (strstr(file, "://") && !strstr(file, "cda://")) return 0;
+
+#if 0 // benski> this would be neat to have, but will fail with unicode filenames (which in_mp3 can open anyway)... TODO: make it workable later
+ HANDLE hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == hFile && GetLastError() != ERROR_FILE_NOT_FOUND)
+ {
+ wchar_t message[1024] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_NOT_FOUND), AutoWide(file), AutoWide(ext));
+ return WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, g_hwnd, CantBurnProc, (LPARAM)message);
+ }
+ CloseHandle(hFile);
+#endif
+
+ char *m_extlist = (char*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_EXTLIST);
+ {
+ int found = 0;
+ char *a = m_extlist;
+ while (a && *a)
+ {
+ if (!lstrcmpiA(a, ext))
+ {
+ found = 1;
+ break;
+ }
+ a += lstrlenA(a) + 1;
+ }
+ GlobalFree((HGLOBAL)m_extlist);
+ if (!found)
+ {
+ wchar_t message[1024] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_FILETYPE_NOT_REGISTERED), AutoWide(file), AutoWide(ext));
+ return (int)(INT_PTR)WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, plugin.hwndLibraryParent, CantBurnProc, (LPARAM)message);
+ }
+ }
+
+ //check for type
+ char tmp[64] = {0, };
+ getFileInfo(file, "type", tmp, sizeof(tmp) - 1);
+ if (tmp[0] && tmp[0] != '0')
+ {
+ wchar_t message[1024], temp[128] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_X),AutoWide(file),
+ WASABI_API_LNGSTRINGW_BUF((tmp[0] == '1' ? IDS_VIDEO_FILES_CANNOT_BE_BURNED : IDS_NOT_AN_AUDIO_FILE),temp,128));
+ return (int)(INT_PTR)WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, plugin.hwndLibraryParent, CantBurnProc, (LPARAM)message);
+
+ }
+
+ // note: this check is NOT meant as any sort of protection.. It simply saves the user the hassle of an error later
+ if (getFileInfo(file, "burnable", tmp, 64) // most plugins don't support this extended file info, so failure is OK
+ && tmp[0] == '0') // if it does support it, then we can check whether or not it's burnable
+ {
+ wchar_t message[1024] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_CANNOT_BE_BURNED), AutoWide(file));
+ if (getFileInfo(file, "noburnreason", tmp, 64))
+ {
+ StringCchPrintfW(message, 1024,
+ WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_X),
+ AutoWide(file), AutoWide(tmp));
+ }
+ return (int)(INT_PTR)WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, plugin.hwndLibraryParent, CantBurnProc, (LPARAM)message);
+ }
+
+ return 1;
+}
+
+
+static int checkFile(const wchar_t *file)
+{
+ //check if the file is supported by winamp
+ const wchar_t *ext = PathFindExtension(file);
+ if (!ext || !ext[0]) return 0;
+ ext++;
+ if (wcsstr(file, L"://") && !wcsstr(file, L"cda://")) return 0;
+
+#if 0 // benski> this would be neat to have, but will fail with unicode filenames (which in_mp3 can open anyway)... TODO: make it workable later
+ HANDLE hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == hFile && GetLastError() == ERROR_FILE_NOT_FOUND)
+ {
+ wchar_t message[1024] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_NOT_FOUND), file, ext);
+ return WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, g_hwnd, CantBurnProc, (LPARAM)message);
+ }
+ CloseHandle(hFile);
+#endif
+ wchar_t *m_extlist = (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_EXTLISTW);
+ {
+ int found = 0;
+ wchar_t *a = m_extlist;
+ while (a && *a)
+ {
+ if (!lstrcmpiW(a, ext))
+ {
+ found = 1;
+ break;
+ }
+ a += lstrlenW(a) + 1;
+ }
+ GlobalFree((HGLOBAL)m_extlist);
+ if (!found)
+ {
+ wchar_t message[1024] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_FILETYPE_NOT_REGISTERED), file, ext);
+ return (int)(INT_PTR)WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, plugin.hwndLibraryParent, CantBurnProc, (LPARAM)message);
+ }
+ }
+
+ //check for type
+ wchar_t tmp[64] = {0, };
+ getFileInfoW(file, L"type", tmp, 64);
+ if (tmp[0] && tmp[0] != '0')
+ {
+ wchar_t message[1024] = {0}, temp[128] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_X), file,
+ WASABI_API_LNGSTRINGW_BUF((tmp[0] == '1' ? IDS_VIDEO_FILES_CANNOT_BE_BURNED : IDS_NOT_AN_AUDIO_FILE),temp,128));
+ return (int)(INT_PTR)WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, plugin.hwndLibraryParent, CantBurnProc, (LPARAM)message);
+
+ }
+
+ // note: this check is NOT meant as any sort of protection.. It simply saves the user the hassle of an error later
+ if (getFileInfoW(file, L"burnable", tmp, 64) // most plugins don't support this extended file info, so failure is OK
+ && tmp[0] == '0') // if it does support it, then we can check whether or not it's burnable
+ {
+ wchar_t message[1024] = {0};
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_FILE_CANNOT_BE_BURNED), file);
+ if (getFileInfoW(file, L"noburnreason", tmp, 64))
+ {
+ StringCchPrintfW(message, 1024,
+ WASABI_API_LNGSTRINGW(IDS_FILE_X_CANNOT_BE_BURNED_REASON_X),
+ file, tmp);
+ }
+ return (int)(INT_PTR)WASABI_API_DIALOGBOXPARAM(IDD_NOBURN, plugin.hwndLibraryParent, CantBurnProc, (LPARAM)message);
+ }
+
+ return 1;
+}
+
+
+void cdburn_clearBurner(char driveletter)
+{
+ emptyRecordList(&itemCache[LETTERTOINDEX(driveletter)]);
+}
+void cdburn_addfile(char* file, std::vector<GayString*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB);
+void cdburn_addfile(wchar_t* file, std::vector<GayStringW*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB);
+void cdburn_addfolder(const char* folder, std::vector<GayString*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB);
+void cdburn_addfolder(const wchar_t* folder, std::vector<GayStringW*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB);
+
+void cdburn_appendFile(char *file, char cLetter)
+{
+ std::vector<GayString*> files;
+ waServiceFactory *plmFactory = plugin.service->service_getServiceByGuid(api_playlistmanagerGUID);
+ api_playlistmanager *plManager = (plmFactory) ? (api_playlistmanager*)plmFactory->getInterface() : NULL;
+
+ int idx = LETTERTOINDEX(cLetter);
+ int validFile = 1;
+
+ if (itemCache[idx].Size > 255) return;
+ itemRecordListW newItems = {0, 0, 0};
+ PLCallBack plCB;
+ plCB.fileList = &files;
+
+ cdburn_addfile(file, &files, (api_playlistmanager*)plManager, (ifc_playlistloadercallback*)&plCB);
+
+ size_t x;
+
+ for (x = 0; x < files.size(); x ++) // temp record . replace it !!!
+ {
+ char *fn = files.at(x)->Get();
+
+ validFile = checkFile(fn);
+ // can't use switch here cause break won't work
+ if (validFile == -1) // bad file and user cancelled
+ break;
+ if (validFile) // bad file, user skipped
+ {
+ allocRecordList(&newItems, newItems.Size + 1);
+ if (!newItems.Alloc) break;
+
+ char title[2048] = {0};
+ basicFileInfoStruct bfis = {fn, 0, 0, title, sizeof(title) - 1,};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfis, IPC_GET_BASIC_FILE_INFO);
+ if (bfis.length > 0)
+ {
+ memset((void *)&(newItems.Items[newItems.Size]), 0, sizeof(itemRecordW));
+ newItems.Items[newItems.Size].filename = AutoWideDup(fn);
+ newItems.Items[newItems.Size].title = AutoWideDup(title);
+ newItems.Items[newItems.Size].length = bfis.length;
+ newItems.Size++;
+ }
+ }
+ delete(files.at(x)->Get());
+ }
+
+ if (validFile != -1)
+ copyRecordList(&itemCache[idx], &newItems);
+ refreshList();
+ if (m_hwndstatus) SetStatus(m_hwndstatus, cLetter);
+ if (plManager) plmFactory->releaseInterface(plManager);
+}
+
+void cdburn_appendFile(wchar_t *file, char cLetter)
+{
+ std::vector<GayStringW*> files;
+ waServiceFactory *plmFactory = plugin.service->service_getServiceByGuid(api_playlistmanagerGUID);
+ api_playlistmanager *plManager = (plmFactory) ? (api_playlistmanager*)plmFactory->getInterface() : NULL;
+
+ int idx = LETTERTOINDEX(cLetter);
+ int validFile = 1;
+
+ if (itemCache[idx].Size > 255) return;
+ itemRecordListW newItems = {0, 0, 0};
+ PLCallBackW plCB;
+ plCB.fileList = &files;
+
+ cdburn_addfile(file, &files, (api_playlistmanager*)plManager, (ifc_playlistloadercallback*)&plCB);
+
+ size_t x;
+
+ for (x = 0; x < files.size(); x ++) // temp record . replace it !!!
+ {
+ const wchar_t *fn = files.at(x)->Get();
+
+ validFile = checkFile(fn);
+ // can't use switch here cause break won't work
+ if (validFile == -1) // bad file and user cancelled
+ break;
+ if (validFile) // bad file, user skipped
+ {
+ allocRecordList(&newItems, newItems.Size + 1);
+ if (!newItems.Alloc) break;
+
+ wchar_t title[2048] = {0};
+ basicFileInfoStructW bfis = {fn, 0, 0, title, ARRAYSIZE(title) - 1,};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfis, IPC_GET_BASIC_FILE_INFOW);
+ if (bfis.length > 0)
+ {
+ memset((void *)&(newItems.Items[newItems.Size]), 0, sizeof(itemRecordW));
+ newItems.Items[newItems.Size].filename = _wcsdup(fn);
+ newItems.Items[newItems.Size].title = _wcsdup(title);
+ newItems.Items[newItems.Size].length = bfis.length;
+ newItems.Size++;
+ }
+ }
+ delete(files.at(x)->Get());
+ }
+
+ if (validFile != -1)
+ copyRecordList(&itemCache[idx], &newItems);
+ refreshList();
+ if (m_hwndstatus) SetStatus(m_hwndstatus, cLetter);
+ if (plManager) plmFactory->releaseInterface(plManager);
+}
+void cdburn_addfile(char* file, std::vector<GayString*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB)
+{
+ if (!_stricmp(extension(file), "lnk"))
+ {
+ char temp2[MAX_PATH] = {0};
+ if (ResolveShortCut(plugin.hwndLibraryParent, file, temp2)) lstrcpynA(file, temp2, MAX_PATH);
+ else return;
+ }
+
+ if (!_strnicmp(file, "cda://", 6))
+ {
+ if (strlen(file) == 7)
+ {
+ int n = 0;
+ char buf2[32] = {0};
+ getFileInfo(file, "ntracks", buf2, sizeof(buf2));
+ n = atoi(buf2);
+ if (n > 0 && n < 256)
+ {
+ for (int x = 0; x < n; x ++)
+ {
+ char s[64] = {0};
+ StringCchPrintfA(s, 64, "%s,%d.cda", file, x + 1);
+ files->push_back(new GayString(s));
+ }
+ }
+ }
+ else files->push_back(new GayString(file));
+ }
+
+ else if (strstr(file, "://"))
+ {
+ if (plManager && PLAYLISTMANAGER_SUCCESS != plManager->Load(AutoWide(file), plCB))
+ {
+ files->push_back(new GayString(file));
+ }
+ }
+ else if (!lstrcmpA(file + 1, ":\\"))
+ {
+ cdburn_addfolder(file, files, plManager, plCB);
+ }
+ else
+ {
+ WIN32_FIND_DATAA d = {0};
+ HANDLE h = FindFirstFileA(file, &d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+ if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ cdburn_addfolder(file, files, plManager, plCB);
+ }
+ if (plManager && PLAYLISTMANAGER_SUCCESS != plManager->Load(AutoWide(file), plCB))
+ {
+ files->push_back(new GayString(file));
+ }
+ }
+ else files->push_back(new GayString(file));
+ }
+}
+void cdburn_addfile(wchar_t* file, std::vector<GayStringW*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB)
+{
+ if (!_wcsicmp(extensionW(file), L"lnk"))
+ {
+ wchar_t temp2[MAX_PATH] = {0};
+ if (ResolveShortCut(plugin.hwndLibraryParent, file, temp2)) lstrcpyn(file, temp2, MAX_PATH);
+ else return;
+ }
+
+ if (!_wcsnicmp(file, L"cda://", 6))
+ {
+ if (wcslen(file) == 7)
+ {
+ int n = 0;
+ wchar_t buf2[32] = {0};
+ getFileInfoW(file, L"ntracks", buf2, sizeof(buf2));
+ n = _wtoi(buf2);
+ if (n > 0 && n < 256)
+ {
+ for (int x = 0; x < n; x ++)
+ {
+ wchar_t s[64] = {0};
+ StringCchPrintfW(s, 64, L"%s,%d.cda", file, x + 1);
+ files->push_back(new GayStringW(s));
+ }
+ }
+ }
+ else files->push_back(new GayStringW(file));
+ }
+
+ else if (wcsstr(file, L"://"))
+ {
+ if (plManager && PLAYLISTMANAGER_SUCCESS != plManager->Load(file, plCB))
+ {
+ files->push_back(new GayStringW(file));
+ }
+ }
+ else if (!lstrcmpW(file + 1, L":\\"))
+ {
+ cdburn_addfolder(file, files, plManager, plCB);
+ }
+ else
+ {
+ WIN32_FIND_DATAW d = {0};
+ HANDLE h = FindFirstFileW(file, &d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+ if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ cdburn_addfolder(file, files, plManager, plCB);
+ }
+ if (plManager && PLAYLISTMANAGER_SUCCESS != plManager->Load(file, plCB))
+ {
+ files->push_back(new GayStringW(file));
+ }
+ }
+ else files->push_back(new GayStringW(file));
+ }
+}
+void cdburn_addfolder(const char* folder, std::vector<GayString*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB)
+{
+ WIN32_FIND_DATAA d = {0};
+ char path[MAX_PATH] = {0};
+ PathCombineA(path, folder, "*");
+
+ HANDLE h = FindFirstFileA(path, &d);
+ if (h == INVALID_HANDLE_VALUE) return;
+ do
+ {
+ if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ if (0 == lstrcmpA(d.cFileName, ".") || 0 == lstrcmpA(d.cFileName, "..")) continue;
+ GayString pathNew(folder);
+ pathNew.Append("\\");
+ pathNew.Append(d.cFileName);
+ cdburn_addfolder(pathNew.Get(), files, plManager, plCB);
+ }
+ else
+ {
+ GayString file(folder);
+ file.Append("\\");
+ file.Append(d.cFileName);
+ cdburn_addfile(file.Get(), files, plManager, plCB);
+ }
+ }
+ while (FindNextFileA(h, &d));
+ FindClose(h);
+}
+void cdburn_addfolder(const wchar_t* folder, std::vector<GayStringW*> *files, api_playlistmanager* plManager, ifc_playlistloadercallback *plCB)
+{
+ WIN32_FIND_DATAW d = {0};
+ wchar_t path[MAX_PATH] = {0};
+ PathCombineW(path, folder, L"*");
+
+ HANDLE h = FindFirstFileW(path, &d);
+ if (h == INVALID_HANDLE_VALUE) return;
+ do
+ {
+ if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ if (0 == lstrcmpW(d.cFileName, L".") || 0 == lstrcmpW(d.cFileName, L"..")) continue;
+ GayStringW pathNew(folder);
+ pathNew.Append(L"\\");
+ pathNew.Append(d.cFileName);
+ cdburn_addfolder(pathNew.Get(), files, plManager, plCB);
+ }
+ else
+ {
+ GayStringW file(folder);
+ file.Append(L"\\");
+ file.Append(d.cFileName);
+ cdburn_addfile((wchar_t*)file.Get(), files, plManager, plCB);
+ }
+ }
+ while (FindNextFileW(h, &d));
+ FindClose(h);
+}
+void cdburn_appendItemRecord(itemRecordList *obj, char cLetter)
+{
+ int idx = LETTERTOINDEX(cLetter);
+ int validFile = 1;
+ itemRecordListW newItems = {0, 0, 0};
+ BurnAddStatus_Create(obj->Size);
+
+ for (int i = 0;i < obj->Size;i++)
+ {
+ validFile = checkFile(obj->Items[i].filename);
+ if (validFile == -1)
+ break;
+ if (validFile)
+ {
+ if (newItems.Size > 255) break;
+
+ allocRecordList(&newItems, newItems.Size + 1);
+ if (!newItems.Alloc) return ;
+
+ memset((void *)&(newItems.Items[newItems.Size]), 0, sizeof(itemRecordW));
+ newItems.Items[newItems.Size].filename = AutoWideDup(obj->Items[i].filename);
+
+ GayString title;
+ if (obj->Items[i].artist) title.Append(obj->Items[i].artist);
+ if (title.Get() && title.Get()[0] && obj->Items[i].title && obj->Items[i].title[0])
+ title.Append(" - ");
+ if (obj->Items[i].title) title.Append(obj->Items[i].title);
+
+ newItems.Items[newItems.Size].title = AutoWideDup(title.Get());
+
+ newItems.Items[newItems.Size].length = obj->Items[i].length;
+ newItems.Size++;
+ BurnAddStatus_Step(&newItems);
+ }
+ }
+ BurnAddStatus_Done();
+ if (validFile != -1)
+ copyRecordList(&itemCache[idx], &newItems);
+ refreshList();
+ if (m_hwndstatus) SetStatus(m_hwndstatus, cLetter);
+}
+
+void cdburn_appendItemRecord(itemRecordListW *obj, char cLetter)
+{
+ int idx = LETTERTOINDEX(cLetter);
+ int validFile = 1;
+ itemRecordListW newItems = {0, 0, 0};
+ BurnAddStatus_Create(obj->Size);
+
+ for (int i = 0;i < obj->Size;i++)
+ {
+ validFile = checkFile(obj->Items[i].filename);
+ if (validFile == -1)
+ break;
+ if (validFile)
+ {
+ if (newItems.Size > 255) break;
+
+ allocRecordList(&newItems, newItems.Size + 1);
+ if (!newItems.Alloc) return ;
+
+ memset((void *)&(newItems.Items[newItems.Size]), 0, sizeof(itemRecordW));
+ newItems.Items[newItems.Size].filename = _wcsdup(obj->Items[i].filename);
+
+ GayStringW title;
+ if (obj->Items[i].artist) title.Append(obj->Items[i].artist);
+ if (title.Get() && title.Get()[0] && obj->Items[i].title && obj->Items[i].title[0])
+ title.Append(L" - ");
+ if (obj->Items[i].title) title.Append(obj->Items[i].title);
+
+ newItems.Items[newItems.Size].title = _wcsdup(title.Get());
+
+ newItems.Items[newItems.Size].length = obj->Items[i].length;
+ newItems.Size++;
+ BurnAddStatus_Step(&newItems);
+ }
+ }
+ BurnAddStatus_Done();
+ if (validFile != -1)
+ copyRecordList(&itemCache[idx], &newItems);
+ refreshList();
+ if (m_hwndstatus) SetStatus(m_hwndstatus, cLetter);
+}
+
+static void Shell_Free(void *p)
+{
+ IMalloc *m;
+ SHGetMalloc(&m);
+ m->Free(p);
+}
+
+HWND cdburn_FindBurningHWND(char cLetter)
+{
+ HWND h = 0;
+ while (NULL != (h = FindWindowExW(NULL, h, L"#32770", NULL)))
+ {
+ if (!GetPropW(h, L"WABURNER")) continue;
+ if (((char)(INT_PTR)GetPropW(h, L"DRIVE")) == cLetter) return h;
+ }
+ return NULL;
+}
+
+CHAR cdburn_IsMeBurning(void)
+{
+ if (pidBurner)
+ {
+ HWND h = NULL;
+ DWORD pid;
+ while (NULL != (h = FindWindowExW(NULL, h, L"#32770", NULL)))
+ {
+ if (GetPropW(h, L"WABURNER") && GetWindowThreadProcessId(h, &pid) && pid == pidBurner)
+ return (CHAR)(INT_PTR)GetPropW(h, L"DRIVE");
+ }
+ }
+ return 0;
+}
+
+static void NotifyInfoWindow(HWND hwnd, LPCWSTR pszFileName, BOOL bForceRefresh)
+{
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ if (hwndParent) SendMessageW(hwndParent, WM_SHOWFILEINFO,
+ (WPARAM)((bForceRefresh) ? WISF_FORCE : WISF_NORMAL),
+ (LPARAM)pszFileName);
+}
+
+static void moveSelItemsUp()
+{
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom)) return;
+
+ int idx = LETTERTOINDEX(m_cdrom);
+ for (int i = 0;i < itemCache[idx].Size;i++)
+ {
+ if (m_statuslist.GetSelected(i))
+ {
+ //swap the 2 items
+ if (i > 0)
+ {
+ itemRecordW tmp = itemCache[idx].Items[i];
+ itemCache[idx].Items[i] = itemCache[idx].Items[i - 1];
+ itemCache[idx].Items[i - 1] = tmp;
+ ListView_SetItemState(m_statuslist.getwnd(), i - 1, LVIS_SELECTED, LVIS_SELECTED);
+ ListView_SetItemState(m_statuslist.getwnd(), i, 0, LVIS_SELECTED);
+ ListView_RedrawItems(m_statuslist.getwnd(), i - 1, i);
+ if (ListView_GetItemState(m_statuslist.getwnd(), i, LVIS_FOCUSED))
+ {
+ ListView_SetItemState(m_statuslist.getwnd(), i - 1, LVIS_FOCUSED, LVIS_FOCUSED);
+ }
+ }
+ }
+ }
+}
+
+static void moveSelItemsDown()
+{
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom)) return ;
+
+ int idx = LETTERTOINDEX(m_cdrom);
+ for (int i = itemCache[idx].Size - 1;i >= 0;i--)
+ {
+ if (m_statuslist.GetSelected(i))
+ {
+ //swap the 2 items
+ if (i < (itemCache[idx].Size - 1))
+ {
+ itemRecordW tmp = itemCache[idx].Items[i];
+ itemCache[idx].Items[i] = itemCache[idx].Items[i + 1];
+ itemCache[idx].Items[i + 1] = tmp;
+ ListView_SetItemState(m_statuslist.getwnd(), i + 1, LVIS_SELECTED, LVIS_SELECTED);
+ ListView_SetItemState(m_statuslist.getwnd(), i, 0, LVIS_SELECTED);
+ ListView_RedrawItems(m_statuslist.getwnd(), i, i + 1);
+ if (ListView_GetItemState(m_statuslist.getwnd(), i, LVIS_FOCUSED))
+ {
+ ListView_SetItemState(m_statuslist.getwnd(), i + 1, LVIS_FOCUSED, LVIS_FOCUSED);
+ }
+ }
+ }
+ }
+}
+
+
+int g_burn_hack_startburn;
+
+void OnBurnDlgInit(HWND hwndDlg, LPARAM lParam)
+{
+ m_hwndstatus = hwndDlg;
+ m_is_cdrw = 0;
+ m_dragging = 0;
+
+ m_cdrom = (char)lParam;
+
+ SendMessageW(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(IDC_BTN_SHOWINFO, BN_EX_GETTEXT), (LPARAM)GetDlgItem(hwndDlg, IDC_BTN_SHOWINFO));
+
+ m_statuslist.setwnd(GetDlgItem(hwndDlg, IDC_LIST2));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER), g_view_metaconf->ReadInt(L"col_track", 60));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_TITLE), g_view_metaconf->ReadInt(L"col_title", 200));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_LENGTH), g_view_metaconf->ReadInt(L"col_len", 80));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_STATUS), g_view_metaconf->ReadInt(L"col_status", 200));
+
+ childSizer.Init(hwndDlg, burnwnd_rlist, sizeof(burnwnd_rlist) / sizeof(burnwnd_rlist[0]));
+
+ if(m_statuslist.getwnd())
+ {
+ MLSKINWINDOW sw;
+
+ sw.hwndToSkin = m_statuslist.getwnd();
+ sw.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ sw.style = SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS | SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+ }
+
+ refreshList();
+
+ // this will make sure that we've got the cddb logo shown even when using a localised version
+ HANDLE hPrev = (HANDLE) SendDlgItemMessage(hwndDlg,IDC_LOGO,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(IDB_LISTITEM_CDDRIVE),
+ IMAGE_BITMAP,0,0, 0));
+ if (hPrev) DeleteObject(hPrev);
+
+ NotifyInfoWindow(hwndDlg, NULL, TRUE); // ignore cache
+ SetStatus(hwndDlg, m_cdrom);
+
+ if (g_burn_hack_startburn)
+ {
+ g_burn_hack_startburn = 0;
+ PostMessage(hwndDlg, WM_COMMAND, IDC_BURN, 0);
+ }
+
+}
+
+void OnBurnNotify(HWND hwndDlg, DWORD notification, DWORD param)
+{
+ switch (notification)
+ {
+ case BURN_READY:
+ SetStatus(hwndDlg, m_cdrom);
+ break;
+ case BURN_STATECHANGED:
+ {
+ wchar_t title[512] = {0};
+ const wchar_t *buf = NULL;
+ switch (param)
+ {
+ case BURNERPLAYLIST_BURNCANCELING:
+ SetDlgItemText(hwndDlg, IDC_CANCEL_BURN, WASABI_API_LNGSTRINGW(IDS_CANCELLING));
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_BURNING_AUDIO_CANCELLING,title,512);
+ break;
+ case BURNERPLAYLIST_BURNFINISHING:
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_BURNING_AUDIO_FINISHING,title,512);
+ break;
+ case BURNERPLAYLIST_DECODEFINISHED:
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_BURNING_AUDIO_DATA_PREP_FINISHED,title,512);
+ break;
+ case BURNERPLAYLIST_LICENSINGSTARTING:
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_BURNING_AUDIO_VERIFYING_FILES,title,512);
+ break;
+ case BURNERPLAYLIST_LICENSINGFINISHED:
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_BURNING_AUDIO_VERIFICATION_COMPLETED,title,512);
+ break;
+ case BURNERPLAYLIST_BURNPROGRESS:
+ wchar_t buf2[256] = {0};
+ switch (SendMessage(m_burning_other_wnd, WM_BURNGETSTATUS, BURNSTATUS_ERROR, 0))
+ {
+ case BURNERPLAYLIST_WRITELEADIN:
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_OPENING_DISC_WRITING_LEAD_IN,buf2,256);
+ break;
+ case BURNERPLAYLIST_WRITELEADOUT:
+ buf = WASABI_API_LNGSTRINGW_BUF(IDS_CLOSING_DISC_WRITING_LEAD_OUT,buf2,256);
+ break;
+ default: break;
+ }
+ if (buf)
+ {
+ int percent = (int)(INT_PTR)SendMessage(m_burning_other_wnd, WM_BURNGETSTATUS, BURNSTATUS_PROGRESS, 0);
+ percentCompleted = max(percent, percentCompleted);
+ StringCchPrintf(title, 512, WASABI_API_LNGSTRINGW(IDS_BURNING_AUDIO_CURRENT_OPERATION), percentCompleted, buf);
+ }
+ break;
+ }
+ if (buf) SetDlgItemText(hwndDlg, IDC_CDINFO, title);
+ }
+ break;
+ case BURN_ITEMSTATECHANGED:
+ ListView_RedrawItems(m_statuslist.getwnd(), param, param);
+ break;
+ case BURN_ITEMDECODEPROGRESS:
+ ListView_RedrawItems(m_statuslist.getwnd(), param, param);
+ {
+ wchar_t title[512] = {0};
+ int percent = (int)(INT_PTR)SendMessage(m_burning_other_wnd, WM_BURNGETSTATUS, BURNSTATUS_PROGRESS, 0);
+ percentCompleted = max(percent, percentCompleted);
+ StringCchPrintf(title, 512, WASABI_API_LNGSTRINGW(IDS_BURNING_AUDIO_CD_PREP_DATA), percentCompleted);
+ SetDlgItemText(hwndDlg, IDC_CDINFO, title);
+ }
+ break;
+ case BURN_ITEMBURNPROGRESS:
+ ListView_RedrawItems(m_statuslist.getwnd(), param, param);
+ {
+ wchar_t title[512] = {0};
+ int percent = (int)(INT_PTR)SendMessage(m_burning_other_wnd, WM_BURNGETSTATUS, BURNSTATUS_PROGRESS, 0);
+ percentCompleted = max(percent, percentCompleted);
+ StringCchPrintf(title, 512, WASABI_API_LNGSTRINGW(IDS_BURNING_AUDIO_BURNING_DATA), percentCompleted);
+ SetDlgItemText(hwndDlg, IDC_CDINFO, title);
+ }
+ break;
+ case BURN_WORKING:
+ ListView_RedrawItems(m_statuslist.getwnd(), 0, ListView_GetItemCount(m_statuslist.getwnd()));
+ break;
+ case BURN_FINISHED:
+ {
+ wchar_t buf1[128] = {0}, closeStr[16] = {0};
+ GetDlgItemText(hwndDlg, IDC_CANCEL_BURN, buf1, ARRAYSIZE(buf1));
+ if (lstrcmpi(buf1, WASABI_API_LNGSTRINGW_BUF(IDS_CLOSE,closeStr,16)))
+ SetDlgItemText(hwndDlg, IDC_CANCEL_BURN, closeStr);
+ wchar_t buf[128] = {0};
+ switch (param)
+ {
+ case BURNERPLAYLIST_SUCCESS:
+ WASABI_API_LNGSTRINGW_BUF(IDS_AUDIO_CD_BURNED_SUCCESSFULLY,buf,128);
+ break;
+ case BURNERPLAYLIST_ABORTED:
+ WASABI_API_LNGSTRINGW_BUF(IDS_BURN_ABORTED_BY_USER,buf,128);
+ break;
+ default:
+ WASABI_API_LNGSTRINGW_BUF(IDS_BURNING_FAILED,buf,128);
+ break;
+ }
+ StringCchPrintf(buf1, 128, WASABI_API_LNGSTRINGW(IDS_BURNING_COMPLETED_STATUS_X), buf);
+ SetDlgItemText(hwndDlg, IDC_CDINFO, buf1);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BURN), TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BURN), TRUE);
+ }
+ break;
+ case BURN_DESTROYED:
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BURN), TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BURN), TRUE);
+ m_burning_other_wnd = NULL;
+ SetStatus(hwndDlg, m_cdrom);
+ break;
+ case BURN_CONFIGCHANGED:
+ switch (LOWORD(param))
+ {
+ case BURNCFG_AUTOCLOSE:
+ g_config->WriteInt(L"cdburnautoclose", HIWORD(param));
+ break;
+ case BURNCFG_AUTOEJECT:
+ g_config->WriteInt(L"cdburnautoeject", HIWORD(param));
+ break;
+ case BURNCFG_ADDTODB:
+ g_config->WriteInt(L"cdburnautoadddb", HIWORD(param));
+ break;
+ case BURNCFG_HIDEVIEW:
+ g_config->WriteInt(L"cdburnstatuswnd", !HIWORD(param));
+ break;
+ }
+ break;
+ }
+}
+
+static int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ switch (uMsg)
+ {
+ case BFFM_INITIALIZED:
+ {
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)WASABI_API_APP->path_getWorkingPath());
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ }
+ return 0;
+}
+
+wchar_t* BuildFilterList(void)
+{
+ static wchar_t fileExtensionsString[128] = {L"*.*"}; // "All files\0*.*\0\0"
+ wchar_t *temp=fileExtensionsString+lstrlenW(fileExtensionsString) +1;
+ lstrcpynW(temp, WASABI_API_LNGSTRINGW(IDS_ALL_FILES), 128);
+ *(temp = temp + lstrlenW(temp) + 1) = 0;
+ return fileExtensionsString;
+}
+
+
+static void CALLBACK Window_TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ HWND hwndList;
+ int index, driveIdx;
+ wchar_t *pszFileName;
+
+ switch(idEvent)
+ {
+ case TIMER_NOTIFYINFO_ID:
+ KillTimer(hwnd, TIMER_NOTIFYINFO_ID);
+ hwndList = GetDlgItem(hwnd, IDC_LIST2);
+
+ driveIdx = LETTERTOINDEX(m_cdrom);
+ index = (hwndList) ? (INT)SendMessage(hwndList, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_FOCUSED) : -1;
+ pszFileName = (index >= 0 && index < itemCache[driveIdx].Size) ? itemCache[driveIdx].Items[index].filename : NULL;
+ NotifyInfoWindow(hwnd, pszFileName, FALSE);
+ break;
+ }
+}
+
+static void ListView_OnItemChanged(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ if (LVIF_STATE & pnmv->uChanged)
+ {
+ if ((LVIS_FOCUSED & pnmv->uOldState) != (LVIS_FOCUSED & pnmv->uNewState))
+ {
+ KillTimer(hwndDlg, TIMER_NOTIFYINFO_ID);
+ SetTimer(hwndDlg, TIMER_NOTIFYINFO_ID, TIMER_NOTIFYINFO_DELAY, Window_TimerProc);
+ }
+ }
+}
+static void Window_OnQueryInfo(HWND hwnd)
+{
+ KillTimer(hwnd, TIMER_NOTIFYINFO_ID);
+ NotifyInfoWindow(hwnd, NULL, TRUE);
+ SetTimer(hwnd, TIMER_NOTIFYINFO_ID, TIMER_NOTIFYINFO_DELAY, Window_TimerProc);
+}
+
+static void Window_OnOperationCompleted(HWND hwnd, DM_NOTIFY_PARAM *phdr)
+{
+ MEDIAINFO mediaInfo;
+
+ if (phdr->cLetter != m_cdrom) return;
+
+ ZeroMemory(&mediaInfo, sizeof(MEDIAINFO));
+ mediaInfo.cLetter = m_cdrom;
+
+ switch(phdr->opCode)
+ {
+ case DMOP_IMAPIINFO:
+ if (S_OK == phdr->result)
+ {
+ DM_IMAPI_PARAM *pIMAPI = (DM_IMAPI_PARAM*)phdr;
+
+ if ((0 != pIMAPI->fMediaType && 0 != pIMAPI->fMediaFlags))
+ {
+ mediaInfo.bInserted = TRUE;
+ if (MEDIA_WRITABLE & pIMAPI->fMediaFlags) mediaInfo.bRecordable = TRUE;
+ if (MEDIA_RW & pIMAPI->fMediaFlags) mediaInfo.bRewritable = TRUE;
+ if (MEDIA_BLANK & pIMAPI->fMediaFlags) mediaInfo.bBlank = TRUE;
+ mediaInfo.nSectorsFree = pIMAPI->ulFreeBlocks;
+ mediaInfo.nSectorsUsed = pIMAPI->ulNextWritable;
+ }
+ }
+ else GetMediaInfoFromSonic(&mediaInfo);
+ FinishSetStatus(hwnd, &mediaInfo);
+ return;
+ }
+}
+
+static INT_PTR CALLBACK DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+ switch (uMsg)
+ {
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ {
+ childSizer.Resize(hwndDlg, burnwnd_rlist, sizeof(burnwnd_rlist) / sizeof(burnwnd_rlist[0]));
+ }
+ break;
+ case WM_BURNNOTIFY:
+ OnBurnNotify(hwndDlg, (DWORD)wParam, (DWORD)lParam);
+ PostMessage(prevWnd, uMsg, wParam, lParam);
+ break;
+ case WM_INITDIALOG: OnBurnDlgInit(hwndDlg, lParam); return 0;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ // link is dead so disabling for the time being
+ /*case IDC_LOGO:
+ if (HIWORD(wParam) == BN_CLICKED)
+ ShellExecute(hwndDlg, L"open", L"http://estore.sonic.com/redirect.asp?id=spaol110103", NULL, L".", 0);
+ break;*/
+ case IDC_BURN_OPTS:
+ {
+ RECT r;
+ HMENU menu = GetSubMenu(g_context_menus, 6);
+ GetWindowRect((HWND)lParam, &r);
+ CheckMenuItem(menu, ID_RIPOPTIONS_RIPPINGSTATUSWINDOW, g_config->ReadInt(L"cdburnstatuswnd", 0) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_EJECTCDWHENCOMPLETED, g_config->ReadInt(L"cdburnautoeject", 1) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_BURNOPTIONS_ADDCDTITLESTOLOCALCDDBCACHE, g_config->ReadInt(L"cdburnautoadddb", 1) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_CLOSEVIEWWHENCOMPLETE, g_config->ReadInt(L"cdburnautoclose", 0) ? MF_CHECKED : MF_UNCHECKED);
+
+ int x = Menu_TrackPopup(plugin.hwndLibraryParent, menu,
+ TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN |
+ TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ r.left, r.top, hwndDlg, NULL);
+ int val = 0, msgid;
+ switch (x)
+ {
+ case ID_RIPOPTIONS_RIPPINGSTATUSWINDOW:
+ val = g_config->ReadInt(L"cdburnstatuswnd", 0);
+ g_config->WriteInt(L"cdburnstatuswnd", !val);
+ msgid = BURNCFG_HIDEVIEW;
+ break;
+ case ID_RIPOPTIONS_EJECTCDWHENCOMPLETED:
+ val = !g_config->ReadInt(L"cdburnautoeject", 1);
+ g_config->WriteInt(L"cdburnautoeject", val);
+ msgid = BURNCFG_AUTOEJECT;
+ break;
+ case ID_BURNOPTIONS_ADDCDTITLESTOLOCALCDDBCACHE:
+ val = !g_config->ReadInt(L"cdburnautoadddb", 0);
+ g_config->WriteInt(L"cdburnautoadddb", val);
+ msgid = BURNCFG_ADDTODB;
+ break;
+ case ID_RIPOPTIONS_CLOSEVIEWWHENCOMPLETE:
+ val = !g_config->ReadInt(L"cdburnautoclose", 0);
+ g_config->WriteInt(L"cdburnautoclose", val);
+ msgid = BURNCFG_AUTOCLOSE;
+ break;
+ default: msgid = 0; break;
+ }
+ if (msgid)
+ {
+ HWND h;
+ h = cdburn_FindBurningHWND(m_cdrom);
+ if (h)
+ {
+ PostMessage(h, WM_BURNCONFIGCHANGED, msgid, val);
+ if (BURNCFG_HIDEVIEW == msgid) ShowWindow(h, val ? SW_HIDE : SW_SHOW);
+ }
+ }
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ }
+ return 0;
+ case IDC_ADD:
+ if (DM_MODE_BURNING != DriveManager_GetDriveMode(m_cdrom))
+ {
+ RECT r;
+ GetWindowRect((HWND)lParam, &r);
+ int x = Menu_TrackPopup(plugin.hwndLibraryParent, GetSubMenu(g_context_menus, 3),
+ TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN |
+ TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ r.left, r.top, hwndDlg, NULL);
+ switch (x)
+ {
+ case ID_BURNADDMENU_FILES:
+ {
+ OPENFILENAMEW l = {sizeof(l), };
+ wchar_t *temp;
+ const int len = 256 * 1024 - 128;
+ wchar_t *m_extlist = 0;
+ m_extlist = (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 1, IPC_GET_EXTLISTW);
+ if ((int)(INT_PTR)m_extlist == 1) m_extlist = 0;
+
+ temp = (wchar_t *)GlobalAlloc(GPTR, len);
+ l.hwndOwner = hwndDlg;
+ l.lpstrFilter = m_extlist ? m_extlist : BuildFilterList();
+ l.lpstrFile = temp;
+ l.nMaxFile = len - 1;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_ADD_FILES_TO_BURNING_LIST);
+ l.lpstrDefExt = L"";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
+
+ l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
+ if (GetOpenFileNameW(&l))
+ {
+ wchar_t newCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, newCurPath);
+ WASABI_API_APP->path_setWorkingPath(newCurPath);
+
+ if (temp[wcslen(temp) + 1])
+ {
+ wchar_t buf[MAX_PATH] = {0};
+ wchar_t *p = temp;
+ wchar_t *path = p;
+ p += wcslen(p) + 1;
+ while (p && *p)
+ {
+ if (*path)
+ StringCchPrintfW(buf, MAX_PATH, L"%s%s%s", path, path[wcslen(path) - 1] == '\\' ? L"" : L"\\" , p);
+ else
+ StringCchPrintfW(buf, MAX_PATH, L"%s", p);
+
+ cdburn_appendFile(buf, m_cdrom);
+ p += wcslen(p) + 1;
+ }
+ }
+ else
+ cdburn_appendFile(temp, m_cdrom);
+ }
+ GlobalFree(temp);
+ if (m_extlist) GlobalFree((HGLOBAL)m_extlist);
+ SetStatus(hwndDlg, m_cdrom);
+ }
+ break;
+ case ID_BURNADDMENU_FOLDER:
+ {
+ BROWSEINFOW bi = {0};
+ wchar_t name[MAX_PATH] = {0};
+ bi.hwndOwner = hwndDlg;
+ bi.pszDisplayName = name;
+ bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_A_FOLDER_TO_ADD_TO_BURNING_LIST);
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ ITEMIDLIST *idlist = SHBrowseForFolderW(&bi);
+ if (idlist)
+ {
+ wchar_t path[MAX_PATH] = {0};
+ SHGetPathFromIDListW(idlist, path);
+ Shell_Free(idlist);
+ cdburn_appendFile(path, m_cdrom);
+ SetStatus(hwndDlg, m_cdrom);
+ }
+ }
+ break;
+ case ID_BURNADDMENU_CURRENTPLAYLIST:
+ {
+ int plsize = (int)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETLISTLENGTH);
+ for (int i = 0;i < plsize;i++)
+ {
+ wchar_t *name = (wchar_t *)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, i, IPC_GETPLAYLISTFILEW);
+ cdburn_appendFile(name, m_cdrom);
+ }
+ SetStatus(hwndDlg, m_cdrom);
+ }
+ break;
+ }
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ }
+ break;
+ case IDC_CLEAR:
+ if (DM_MODE_BURNING != DriveManager_GetDriveMode(m_cdrom))
+ {
+ wchar_t title[32] = {0};
+ if (MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_SURE_YOU_WANT_TO_CLEAR_BURNING_LIST),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,title,32), MB_YESNO) != IDYES)
+ break;
+ emptyRecordList(&itemCache[LETTERTOINDEX(m_cdrom)]);
+ SetStatus(hwndDlg, m_cdrom);
+ refreshList();
+ }
+ break;
+ case IDC_CANCEL_BURN:
+ case IDC_BURN:
+ {
+ HWND h;
+ if (NULL != (h = cdburn_FindBurningHWND(m_cdrom)))
+ {
+ PostMessage(h, WM_COMMAND, MAKEWPARAM(IDCANCEL, BN_CLICKED), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BURN), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BURN), FALSE);
+
+ }
+ else doBurnDialog(hwndDlg);
+ }
+ break;
+ case IDC_BTN_SHOWINFO:
+ switch(HIWORD(wParam))
+ {
+ case BN_CLICKED: SendMessageW(GetParent(hwndDlg), WM_COMMAND, wParam, lParam); break;
+ }
+ break;
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ {
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom) || m_statuslist.GetCount() == 0) return 0;
+
+ HMENU menu = GetSubMenu(g_context_menus, 4);
+
+ POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+
+ if (pt.x == -1 || pt.y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = m_statuslist.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ m_statuslist.GetItemRect(selected, &itemRect);
+ ClientToScreen(m_statuslist.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(m_statuslist.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG((HWND)wParam, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ pt.x = itemRect.left;
+ pt.y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG((HWND)wParam, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return 0;
+ }
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu,
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ pt.x, pt.y, hwndDlg, NULL);
+ switch (r)
+ {
+ case ID_BURNCONTEXTMENU_PLAYSELECTEDITEMS:
+ playSelectedItems(hwndDlg, 0);
+ break;
+ case ID_BURNCONTEXTMENU_ENQUEUESELECTEDITEMS:
+ playSelectedItems(hwndDlg, 1);
+ break;
+ case ID_BURNCONTEXTMENU_SELECTALL:
+ selectAll();
+ break;
+ case ID_BURNCONTEXTMENU_REMOVESELECTEDITEMS:
+ if (DM_MODE_BURNING != DriveManager_GetDriveMode(m_cdrom)) deleteSelectedItems(hwndDlg, m_cdrom);
+ break;
+ case ID_BURNCONTEXTMENU_BURN:
+ doBurnDialog(hwndDlg);
+ break;
+ case ID_BURNCONTEXTMENU_MOVESELECTEDITEMSUP:
+ moveSelItemsUp();
+ break;
+ case ID_BURNCONTEXTMENU_MOVESELECTEDITEMSDOWN:
+ moveSelItemsDown();
+ break;
+ }
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ return 1;
+ }
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ if (l->idFrom == IDC_LIST2)
+ {
+ if (l->code == LVN_KEYDOWN)
+ {
+ LPNMLVKEYDOWN pnkd = (LPNMLVKEYDOWN) lParam;
+ switch (pnkd->wVKey)
+ {
+ case 38: //up
+ if (GetAsyncKeyState(VK_LMENU))
+ {
+ moveSelItemsUp();
+ return 1;
+ }
+ break;
+ case 40: //down
+ if (GetAsyncKeyState(VK_LMENU))
+ {
+ moveSelItemsDown();
+ return 1;
+ }
+ break;
+ case 46: //delete
+ if (DM_MODE_BURNING != DriveManager_GetDriveMode(m_cdrom)) deleteSelectedItems(hwndDlg, m_cdrom);
+ break;
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL))
+ selectAll();
+ break;
+ }
+ }
+ else if (l->code == NM_DBLCLK)
+ {
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom)) return 0;
+ playSelectedItems(hwndDlg, (!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)));
+ }
+ else if (l->code == NM_RETURN)
+ {
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom)) return 0;
+ playSelectedItems(hwndDlg, 0 ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)));
+ }
+ else if (l->code == LVN_GETDISPINFO)
+ {
+ NMLVDISPINFO *lpdi = (NMLVDISPINFO*) lParam;
+ int item = lpdi->item.iItem;
+ int idx = LETTERTOINDEX(m_cdrom);
+ if (item < 0 || item >= itemCache[idx].Size) return 0;
+
+ itemRecordW *thisitem = itemCache[idx].Items + item;
+
+ if (lpdi->item.mask & (LVIF_TEXT | /*LVIF_IMAGE*/0)) // we can always do images too :)
+ {
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ wchar_t tmpbuf[128] = {0};
+ wchar_t *nameptr = 0;
+ switch (lpdi->item.iSubItem)
+ {
+ case 0:
+ //track #
+ StringCchPrintfW(tmpbuf, 128, L"%d", item + 1);
+ nameptr = tmpbuf;
+ break;
+ case 1:
+ //title
+ lstrcpynW(tmpbuf, thisitem->title, 128);
+ nameptr = tmpbuf;
+ break;
+ case 2:
+ //length
+ StringCchPrintfW(tmpbuf, 128, L"%01d:%02d", thisitem->length / 60, thisitem->length % 60);
+ nameptr = tmpbuf;
+ break;
+ case 3:
+ DWORD state = (DWORD) SendMessage(m_burning_other_wnd, WM_BURNGETITEMSTATUS, BURNSTATUS_STATE, (LPARAM)item);
+ switch (state)
+ {
+ case BURNERITEM_BURNING:
+ case BURNERITEM_DECODING:
+ StringCchPrintfW(tmpbuf, 128, L"%s (%d%%)",
+ WASABI_API_LNGSTRINGW((BURNERITEM_BURNING == state) ? IDS_BURNING_ : IDS_PREPARING),
+ (DWORD)SendMessage(m_burning_other_wnd, WM_BURNGETITEMSTATUS, BURNSTATUS_PROGRESS, (LPARAM)item));
+ nameptr = tmpbuf;
+ break;
+ case BURNERITEM_SUCCESS: break;
+ case BURNERITEM_BURNED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_FINISHED,tmpbuf,128);
+ break;
+ case BURNERITEM_DECODED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_PREPARED,tmpbuf,128);
+ break;
+ case BURNERITEM_SKIPPED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_SKIPPED,tmpbuf,128);
+ break;
+ case BURNERITEM_READY:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_SCHEDULED,tmpbuf,128);
+ break;
+ case BURNERITEM_LICENSING:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_CHECKING_LICENSE,tmpbuf,128);
+ break;
+ case BURNERITEM_LICENSED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_LICENSED,tmpbuf,128);
+ break;
+ case BURNERITEM_ABORTED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_CANCELLED,tmpbuf,128);
+ break;
+ case BURNERITEM_FAILED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_FAILED,tmpbuf,128);
+ break;
+ case BURNERITEM_CANCELING:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_CANCELLING,tmpbuf,128);
+ break;
+ case BURNERITEM_BADFILENAME:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_BAD_FILENAME,tmpbuf,128);
+ break;
+ case BURNERITEM_UNABLEOPENFILE:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_UNABLE_TO_OPEN_FILE,tmpbuf,128);
+ break;
+ case BURNERITEM_WRITEERROR:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_CACHE_WRITE_FAILED,tmpbuf,128);
+ break;
+ case BURNERITEM_DECODEERROR:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_UNABLE_TO_FIND_DECODER,tmpbuf,128);
+ break;
+ case BURNERITEM_ADDSTREAMFAILED:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_CANNOT_ADD_TO_THE_DISC,tmpbuf,128);
+ break;
+ case BURNERITEM_READSTREAMERROR:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_CACHE_READ_FAILED,tmpbuf,128);
+ break;
+ default:
+ nameptr = WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_ERROR,tmpbuf,128);
+ break;
+ }
+ //status
+ break;
+ }
+ if (nameptr)
+ lstrcpynW(lpdi->item.pszText, nameptr, lpdi->item.cchTextMax);
+ else
+ lpdi->item.pszText[0] = 0;
+ }
+ }
+ return 0;
+ }
+ else if (l->code == LVN_BEGINDRAG)
+ {
+ SetCapture(hwndDlg);
+ m_dragging = 1;
+ LPNMLISTVIEW nlv = (LPNMLISTVIEW)lParam;
+ m_drag_item = nlv->iItem;
+ }
+ else if (l->code == LVN_ITEMCHANGED) ListView_OnItemChanged(hwndDlg, (NMLISTVIEW*)l);
+ }
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ if (m_dragging)
+ {
+ POINT p;
+ p.x = GET_X_LPARAM(lParam);
+ p.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg, &p);
+ ScreenToClient(m_statuslist.getwnd(), &p);
+ int i = m_statuslist.FindItemByPoint(p.x, p.y);
+ if (i != -1 && i != m_drag_item)
+ {
+ if (i > m_drag_item)
+ {
+ for (int j = 0;j < (i - m_drag_item);j++)
+ moveSelItemsDown();
+ }
+ else
+ {
+ for (int j = 0;j < (m_drag_item - i);j++)
+ moveSelItemsUp();
+ }
+ m_drag_item = i;
+ }
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (GetCapture() == hwndDlg)
+ {
+ ReleaseCapture();
+ m_dragging = 0;
+ }
+ break;
+
+ case WM_DESTROY:
+ if (m_statuslist.getwnd())
+ {
+ g_view_metaconf->WriteInt(L"col_track", m_statuslist.GetColumnWidth(0));
+ g_view_metaconf->WriteInt(L"col_title", m_statuslist.GetColumnWidth(1));
+ g_view_metaconf->WriteInt(L"col_len", m_statuslist.GetColumnWidth(2));
+ g_view_metaconf->WriteInt(L"col_status", m_statuslist.GetColumnWidth(3));
+ }
+
+ if (m_burning_other_wnd && IsWindow(m_burning_other_wnd))
+ {
+ PostMessage(m_burning_other_wnd, WM_BURNUPDATEOWNER, 0, (LPARAM)prevWnd);
+ prevWnd = NULL;
+ }
+ m_hwndstatus = 0;
+
+ if (hPLFont)
+ {
+ DeleteObject(hPLFont);
+ hPLFont = NULL;
+ }
+
+ {
+ HANDLE hPrev = (HANDLE) SendDlgItemMessage(hwndDlg,IDC_LOGO,STM_SETIMAGE,IMAGE_BITMAP, 0L);
+ if (hPrev) DeleteObject(hPrev);
+ }
+ return 0;
+ case WM_ML_CHILDIPC:
+ if (lParam == ML_CHILDIPC_DROPITEM && wParam)
+ {
+ mlDropItemStruct *dis = (mlDropItemStruct *)wParam;
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom))
+ {
+ dis->result = -1;
+ return 0;
+ }
+
+ if (dis->type != ML_TYPE_ITEMRECORDLISTW && dis->type != ML_TYPE_ITEMRECORDLIST &&
+ dis->type != ML_TYPE_FILENAMES && dis->type != ML_TYPE_FILENAMESW)
+ {
+ dis->result = -1;
+ }
+ else
+ {
+ if (dis->data)
+ {
+ dis->result = 1;
+ if (dis->type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordList *obj = (itemRecordList *)dis->data;
+ cdburn_appendItemRecord(obj, m_cdrom);
+ }
+ else if (dis->type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *obj = (itemRecordListW *)dis->data;
+ cdburn_appendItemRecord(obj, m_cdrom);
+ }
+ else if (dis->type == ML_TYPE_FILENAMES) // playlist
+ {
+ char *p = (char*)dis->data;
+ while (p && *p)
+ {
+ cdburn_appendFile(p, m_cdrom);
+ p += strlen(p) + 1;
+ }
+ }
+ else if (dis->type == ML_TYPE_FILENAMESW)
+ {
+ wchar_t *p = (wchar_t*)dis->data;
+ while (p && *p)
+ {
+ cdburn_appendFile(p, m_cdrom);
+ p += wcslen(p) + 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+
+ case WM_DROPFILES:
+ {
+ char temp[2048] = {0};
+ HDROP h = (HDROP) wParam;
+ int y = DragQueryFileA(h, 0xffffffff, temp, sizeof(temp));
+
+ if (DM_MODE_BURNING == DriveManager_GetDriveMode(m_cdrom))
+ {
+// MessageBoxA(hwndDlg,"Cannot add files while burning","CD Burner",MB_OK);
+ }
+ else for (int x = 0; x < y; x ++)
+ {
+ DragQueryFileA(h, x, temp, sizeof(temp));
+ cdburn_appendFile(temp, m_cdrom);
+ }
+
+ DragFinish(h);
+ }
+ return 0;
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST2 | DCW_SUNKENBORDER, IDC_LOGO | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 2);
+ }
+ return 0;
+ case WM_ERASEBKGND: return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ case WM_QUERYFILEINFO: Window_OnQueryInfo(hwndDlg); break;
+ case WM_EX_OPCOMPLETED: Window_OnOperationCompleted(hwndDlg, (DM_NOTIFY_PARAM*)lParam);
+ }
+ return FALSE;
+}
+
+static HWND BurnAddStatus_wnd;
+
+static BOOL CALLBACK BurnAddStatus_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_CLOSE)
+ DestroyWindow(hwndDlg);
+ return 0;
+}
+
+void BurnAddStatus_Create(int num)
+{
+ if (BurnAddStatus_wnd && IsWindow(BurnAddStatus_wnd)) DestroyWindow(BurnAddStatus_wnd);
+ BurnAddStatus_wnd = WASABI_API_CREATEDIALOGW(IDD_BURN_ADD_STATUS, plugin.hwndLibraryParent, BurnAddStatus_proc);
+ if (!BurnAddStatus_wnd) return ;
+
+ SetTimer(BurnAddStatus_wnd, 1, 100, NULL);
+ SendDlgItemMessage(BurnAddStatus_wnd, IDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, num));
+
+ unsigned int start_t = GetTickCount();
+ if (start_t >= 0xffffff00) start_t = 0;
+
+ MSG msg;
+ while (GetTickCount() < start_t + 100 && GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+}
+
+void BurnAddStatus_Step(itemRecordListW *items)
+{
+ if (!BurnAddStatus_wnd || !items || items && !items->Size || items && !items->Items) return ;
+ SendDlgItemMessage(BurnAddStatus_wnd, IDC_PROGRESS1, PBM_DELTAPOS, 1, 0);
+
+ int l = 0;
+ for (int i = 0;i < items->Size;i++)
+ {
+ l += items->Items[i].length;
+ }
+ wchar_t buf[512] = {0};
+ StringCchPrintf(buf, 512, WASABI_API_LNGSTRINGW(IDS_ADDING_TRACKS_TO_BURNER_TOTAL_LENGTH_X), l / 60, l % 60);
+ SetDlgItemText(BurnAddStatus_wnd, IDC_STAT, buf);
+}
+
+void BurnAddStatus_Done()
+{
+ if (!BurnAddStatus_wnd) return ;
+ unsigned int start_t = GetTickCount();
+ if (start_t >= 0xffffff00) start_t = 0;
+
+ MSG msg;
+ while (GetTickCount() < start_t + 1000 && IsWindow(BurnAddStatus_wnd) && GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ DestroyWindow(BurnAddStatus_wnd);
+ BurnAddStatus_wnd = 0;
+}
+
+static bool cdrFound(char letter)
+{
+ wchar_t name[]= L"cda://X.cda";
+ wchar_t info[16] = L"";
+ name[6] = letter;
+ getFileInfoW(name, L"cdtype", info, sizeof(info)/sizeof(wchar_t));
+ return !lstrcmpW(info, L"CDR") || !lstrcmpW(info, L"CDRW");
+}
+
+static char m_burnwait_letter;
+static BOOL CALLBACK BurnWaitProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ SetTimer(hwndDlg, 1, 2000, NULL);
+ {
+ wchar_t buf[512] = {0};
+ StringCchPrintf(buf, 512, WASABI_API_LNGSTRINGW(IDS_PLEASE_INSERT_BLANK_RECORDABLE_CD), toupper(m_burnwait_letter));
+ SetDlgItemText(hwndDlg, IDC_TEXT, buf);
+ }
+ return 0;
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDCANCEL) EndDialog(hwndDlg, 1);
+ return 0;
+ case WM_TIMER:
+ if (cdrFound(m_burnwait_letter)) EndDialog(hwndDlg, 0);
+ return 0;
+ }
+ return 0;
+}
+
+int Burn_WaitForCDR(HWND hwndParent, char driveletter) // returns 0 on CD-R found, 1 on cancel
+{
+ CHAR cMode;
+ if (!driveletter) return 1;
+ cMode = DriveManager_GetDriveMode(driveletter);
+ if (DM_MODE_BURNING == cMode || DM_MODE_RIPPING == cMode)
+ {
+ return 1; // if burning or ripping, don't fuck with it
+ }
+
+ if (cdrFound(driveletter)) return 0;
+ if (m_burnwait_letter) return 1;
+
+ m_burnwait_letter = (char)toupper(driveletter);
+ int x = (int)(INT_PTR)WASABI_API_DIALOGBOXW(IDD_WAITFORCDR, hwndParent, BurnWaitProc);
+ m_burnwait_letter = 0;
+ return x;
+}
diff --git a/Src/Plugins/Library/ml_disc/cdrip.cpp b/Src/Plugins/Library/ml_disc/cdrip.cpp
new file mode 100644
index 00000000..58192596
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/cdrip.cpp
@@ -0,0 +1,1061 @@
+#include "main.h"
+#include <windowsx.h>
+#include "resource.h"
+
+#include "..\nu\listview.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/ChildSizer.h"
+
+#include "config.h"
+#include "../winamp/wa_ipc.h"
+
+#include "..\..\General\gen_ml/gaystring.h"
+
+#include <stdio.h>
+#include <shlobj.h>
+#include <time.h>
+#include "../nu/AutoChar.h"
+#include "../nu/AutoCharFn.h"
+#include "../nu/AutoWide.h"
+
+#include "ReplayGain.h"
+
+#include "M3UWriter.h"
+#include "PLSWriter.h"
+#include "./settings.h"
+#include <shlwapi.h>
+#include <windows.h>
+#include <strsafe.h>
+
+extern unsigned int FileTimeToUnixTime(FILETIME *ft);
+
+static UINT uMsgRipperNotify = 0;
+
+
+static INT_PTR WINAPI DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+HWND CreateCDRipWindow(HWND hwndParent, CHAR cLetter)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_CDROM_EX2, hwndParent, DlgProc, (LPARAM)cLetter);
+}
+//physically update metadata in a given file
+int updateFileInfo(char *filename, char *metadata, char *data)
+{
+ extendedFileInfoStruct efis = {
+ filename,
+ metadata,
+ data ? data : "",
+ data ? strlen(data) : 0,
+ };
+ return (INT)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFO);
+}
+
+//physically update metadata in a given file
+int updateFileInfoW(wchar_t *filename, const wchar_t *metadata, const wchar_t *data)
+{
+ extendedFileInfoStructW efis = {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? lstrlenW(data) : 0,
+ };
+ return (INT)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+
+static int m_extract_curtrack, m_extract_nb, m_extract_nb_total;
+static int m_db_has_upd;
+static convertFileStructW m_fcs;
+static wchar_t m_extract_src[64];
+static DWORD m_extract_time;
+static int m_extracting;
+static cdrip_params *m_rip_params;
+HWND m_extract_wnd = 0;
+static wchar_t m_last_total_status[512];
+static wchar_t m_last_item_status[512];
+static int m_cur_rg = 0;
+static int done = 0;
+
+bool RegisteredEncoder(DWORD fourcc)
+{
+ if (fourcc == mmioFOURCC('M', 'P', '3', 'l')
+ || fourcc == mmioFOURCC('A', 'A', 'C', 'H')
+ || fourcc == mmioFOURCC('M', '4', 'A', 'H'))
+ return true;
+ else
+ return false;
+}
+
+static void createDirForFile(char *str)
+{
+ char *p = str;
+ if ((p[0] == '\\' || p[0] == '/') && (p[1] == '\\' || p[1] == '/'))
+ {
+ p += 2;
+ while (p && *p && *p != '\\' && *p != '/') p++;
+ if (!p || !*p) return ;
+ p++;
+ while (p && *p && *p != '\\' && *p != '/') p++;
+ }
+ else
+ {
+ while (p && *p && *p != '\\' && *p != '/') p++;
+ }
+
+ while (p && *p)
+ {
+ while (p && *p != '\\' && *p != '/' && *p) p = CharNextA(p);
+ if (p && *p)
+ {
+ char lp = *p;
+ *p = 0;
+ CreateDirectoryA(str, NULL);
+ *p++ = lp;
+ }
+ }
+}
+
+static void createDirForFileW(wchar_t *str)
+{
+ wchar_t *p = str;
+ if ((p[0] ==L'\\' || p[0] ==L'/') && (p[1] ==L'\\' || p[1] ==L'/'))
+ {
+ p += 2;
+ while (p && *p && *p !=L'\\' && *p !=L'/') p++;
+ if (!p || !*p) return ;
+ p++;
+ while (p && *p && *p !=L'\\' && *p !=L'/') p++;
+ }
+ else
+ {
+ while (p && *p && *p !=L'\\' && *p !=L'/') p++;
+ }
+
+ while (p && *p)
+ {
+ while (p && *p !=L'\\' && *p !=L'/' && *p) p = CharNextW(p);
+ if (p && *p)
+ {
+ wchar_t lp = *p;
+ *p = 0;
+ CreateDirectoryW(str, NULL);
+ *p++ = lp;
+ }
+ }
+}
+
+static void LockCD(char cLetter, BOOL bLock)
+{
+ wchar_t info[32] = {0};
+ wchar_t name[] = L"cda://X";
+ name[6] = cLetter;
+ getFileInfoW(name, (bLock) ? L"cdlock" : L"cdunlock", info, sizeof(info)/sizeof(wchar_t));
+}
+
+static int m_pstat_bytesdone;
+static int m_pstat_bytesout;
+static int m_pstat_timedone;
+
+static int m_rip_done;
+
+static HWND m_hwndstatus;
+static W_ListView m_statuslist;
+
+static void NotifyInfoWindow(HWND hwnd, BOOL bForceRefresh)
+{
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ if (hwndParent) SendMessageW(hwndParent, WM_SHOWFILEINFO,
+ (WPARAM) WISF_MESSAGE | ((bForceRefresh) ? WISF_FORCE : WISF_NORMAL),
+ (LPARAM)WASABI_API_LNGSTRINGW(IDS_INFO_RIPPING));
+}
+
+static void ListView_OnItemChanged(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ if (LVIF_STATE & pnmv->uChanged)
+ {
+ if ((LVIS_FOCUSED & pnmv->uOldState) != (LVIS_FOCUSED & pnmv->uNewState))
+ {
+ NotifyInfoWindow(hwndDlg, TRUE);
+ }
+ }
+}
+
+static void Window_OnQueryInfo(HWND hwnd)
+{
+ NotifyInfoWindow(hwnd, FALSE);
+}
+
+static INT_PTR Window_OnNotify(HWND hwndDlg, INT ctrlId, LPNMHDR phdr)
+{
+ switch(phdr->idFrom)
+ {
+ case IDC_LIST2:
+ switch(phdr->code)
+ {
+ case LVN_ITEMCHANGED: ListView_OnItemChanged(hwndDlg, (NMLISTVIEW*)phdr); break;
+ }
+ break;
+ }
+ return 0;
+}
+
+INT_PTR WINAPI DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static ChildWndResizeItem ripwnd_rlist[] = {
+ {IDC_LIST2, 0x0011},
+ {IDC_CDINFO, 0x0000},
+ {IDC_RIPOPTS, 0x0101},
+ {IDC_CANCEL_RIP, 0x0101},
+ {IDC_BTN_SHOWINFO, 0x1111},
+ };
+
+ INT_PTR a = (INT_PTR)dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+ switch (uMsg)
+ {
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ {
+ childSizer.Resize(hwndDlg, ripwnd_rlist, sizeof(ripwnd_rlist) / sizeof(ripwnd_rlist[0]));
+ }
+ break;
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST2 | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 1);
+ }
+ return 0;
+ case WM_INITDIALOG:
+
+ m_hwndstatus = hwndDlg;
+
+ SendMessageW(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(IDC_BTN_SHOWINFO, BN_EX_GETTEXT), (LPARAM)GetDlgItem(hwndDlg, IDC_BTN_SHOWINFO));
+
+ m_statuslist.setwnd(GetDlgItem(hwndDlg, IDC_LIST2));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER), g_view_metaconf->ReadInt(L"col_track", 60));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_TITLE), g_view_metaconf->ReadInt(L"col_title", 200));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_LENGTH), g_view_metaconf->ReadInt(L"col_len", 80));
+ m_statuslist.AddCol(WASABI_API_LNGSTRINGW(IDS_STATUS), g_view_metaconf->ReadInt(L"col_status", 200));
+
+ SetDlgItemText(hwndDlg, IDC_CDINFO, m_last_total_status);
+ {
+ int l = m_rip_params->ntracks;
+ int x = 0;
+ for (int i = 0;i < l;i++) if (m_rip_params->tracks[i])
+ {
+ wchar_t buf[1024] = {0};
+ StringCchPrintf(buf, 1024, L"%d", i + 1);
+ m_statuslist.InsertItem(x, buf, 0);
+ m_statuslist.SetItemText(x, 1, m_rip_params->tracks[i]);
+
+ StringCchPrintf(buf, 512, L"%d:%02d", m_rip_params->lengths[i] / 60, m_rip_params->lengths[i] % 60);
+ m_statuslist.SetItemText(x, 2, buf);
+
+ if (i < m_extract_curtrack || m_rip_done)
+ m_statuslist.SetItemText(x, 3, WASABI_API_LNGSTRINGW(IDS_COMPLETED));
+ else if (i > m_extract_curtrack)
+ m_statuslist.SetItemText(x, 3, WASABI_API_LNGSTRINGW(IDS_QUEUED));
+ else
+ {
+ m_statuslist.SetItemText(x, 3, m_last_item_status[0] ? m_last_item_status : WASABI_API_LNGSTRINGW(IDS_RIPPING));
+ }
+ x++;
+ }
+ }
+
+ if (!m_extract_wnd || m_rip_done)
+ {
+ SetDlgItemText(hwndDlg, IDC_CANCEL_RIP, WASABI_API_LNGSTRINGW(IDS_CLOSE));
+ }
+
+ if (g_config->ReadInt(L"cdripautoeject", 0)) CheckDlgButton(hwndDlg, IDC_CHECK2, BST_CHECKED);
+ if (g_config->ReadInt(L"cdripautoplay", 0)) CheckDlgButton(hwndDlg, IDC_CHECK3, BST_CHECKED);
+ if (g_config->ReadInt(L"cdripautoclose", 1)) CheckDlgButton(hwndDlg, IDC_CHECK1, BST_CHECKED);
+
+ childSizer.Init(hwndDlg, ripwnd_rlist, sizeof(ripwnd_rlist) / sizeof(ripwnd_rlist[0]));
+
+ ListView_SetTextColor(m_statuslist.getwnd(), dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(m_statuslist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(m_statuslist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+
+ if(m_statuslist.getwnd())
+ {
+ MLSKINWINDOW sw;
+ sw.hwndToSkin = m_statuslist.getwnd();
+ sw.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ sw.style = SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS | SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+ }
+ NotifyInfoWindow(hwndDlg, TRUE);
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_RIPOPTS:
+ {
+ RECT r;
+ HMENU menu = GetSubMenu(g_context_menus, 5);
+ GetWindowRect((HWND)lParam, &r);
+ CheckMenuItem(menu, ID_RIPOPTIONS_RIPPINGSTATUSWINDOW, g_config->ReadInt(L"cdripstatuswnd", 0) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_EJECTCDWHENCOMPLETED, g_config->ReadInt(L"cdripautoeject", 0) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PLAYTRACKSWHENCOMPLETED, g_config->ReadInt(L"cdripautoplay", 0) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_CLOSEVIEWWHENCOMPLETE, g_config->ReadInt(L"cdripautoclose", 0) ? MF_CHECKED : MF_UNCHECKED);
+
+ int prio = g_config->ReadInt(L"extractprio", THREAD_PRIORITY_NORMAL);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PRIORITY_IDLE, prio == THREAD_PRIORITY_IDLE ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PRIORITY_LOWEST, prio == THREAD_PRIORITY_LOWEST ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PRIORITY_BELOWNORMAL, prio == THREAD_PRIORITY_BELOW_NORMAL ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PRIORITY_NORMAL, prio == THREAD_PRIORITY_NORMAL ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PRIORITY_ABOVENORMAL, prio == THREAD_PRIORITY_ABOVE_NORMAL ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(menu, ID_RIPOPTIONS_PRIORITY_HIGH, prio == THREAD_PRIORITY_HIGHEST ? MF_CHECKED : MF_UNCHECKED);
+
+ int x = Menu_TrackPopup(plugin.hwndLibraryParent, menu,
+ TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN |
+ TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ r.left, r.top, hwndDlg, NULL);
+ switch (x)
+ {
+ case ID_RIPOPTIONS_RIPPINGSTATUSWINDOW:
+ {
+ int x = g_config->ReadInt(L"cdripstatuswnd", 0);
+ g_config->WriteInt(L"cdripstatuswnd", !x);
+ ShowWindow(m_extract_wnd, x ? SW_HIDE : SW_SHOW);
+ }
+ break;
+ case ID_RIPOPTIONS_EJECTCDWHENCOMPLETED:
+ g_config->WriteInt(L"cdripautoeject", !g_config->ReadInt(L"cdripautoeject", 0));
+ break;
+ case ID_RIPOPTIONS_PLAYTRACKSWHENCOMPLETED:
+ g_config->WriteInt(L"cdripautoplay", !g_config->ReadInt(L"cdripautoplay", 0));
+ break;
+ case ID_RIPOPTIONS_CLOSEVIEWWHENCOMPLETE:
+ g_config->WriteInt(L"cdripautoclose", !g_config->ReadInt(L"cdripautoclose", 0));
+ break;
+ case ID_RIPOPTIONS_PRIORITY_IDLE:
+ case ID_RIPOPTIONS_PRIORITY_LOWEST:
+ case ID_RIPOPTIONS_PRIORITY_BELOWNORMAL:
+ case ID_RIPOPTIONS_PRIORITY_NORMAL:
+ case ID_RIPOPTIONS_PRIORITY_ABOVENORMAL:
+ case ID_RIPOPTIONS_PRIORITY_HIGH:
+ {
+ int prio = THREAD_PRIORITY_NORMAL;
+ if (x == ID_RIPOPTIONS_PRIORITY_IDLE) prio = THREAD_PRIORITY_IDLE;
+ if (x == ID_RIPOPTIONS_PRIORITY_LOWEST) prio = THREAD_PRIORITY_LOWEST;
+ if (x == ID_RIPOPTIONS_PRIORITY_BELOWNORMAL) prio = THREAD_PRIORITY_BELOW_NORMAL;
+ if (x == ID_RIPOPTIONS_PRIORITY_ABOVENORMAL) prio = THREAD_PRIORITY_ABOVE_NORMAL;
+ if (x == ID_RIPOPTIONS_PRIORITY_HIGH) prio = THREAD_PRIORITY_HIGHEST;
+ g_config->WriteInt(L"extractprio", prio);
+ convertSetPriorityW csp = {
+ &m_fcs,
+ prio,
+ };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW);
+ }
+ break;
+ }
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ }
+ return 0;
+ case IDC_CANCEL_RIP:
+ {
+ wchar_t title[64] = {0};
+ if (!m_extract_wnd ||
+ m_rip_done ||
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_CANCEL_RIP),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD_RIP_QUESTION,title,64),
+ MB_YESNO | MB_ICONQUESTION) == IDYES)
+ {
+ if (m_rip_params) LockCD(m_rip_params->drive_letter, FALSE);
+ if (m_extract_wnd) DestroyWindow(m_extract_wnd);
+ DestroyWindow(hwndDlg);
+ }
+ return 0;
+ }
+ case IDC_BTN_SHOWINFO:
+ switch(HIWORD(wParam))
+ {
+ case BN_CLICKED:
+ SendMessageW(GetParent(hwndDlg), WM_COMMAND, wParam, lParam);
+ NotifyInfoWindow(hwndDlg, TRUE);
+ break;
+ }
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ if (m_statuslist.getwnd())
+ {
+ g_view_metaconf->WriteInt(L"col_track", m_statuslist.GetColumnWidth(0));
+ g_view_metaconf->WriteInt(L"col_title", m_statuslist.GetColumnWidth(1));
+ g_view_metaconf->WriteInt(L"col_len", m_statuslist.GetColumnWidth(2));
+ g_view_metaconf->WriteInt(L"col_status", m_statuslist.GetColumnWidth(3));
+ }
+
+ m_hwndstatus = 0;
+ return 0;
+
+ case WM_ERASEBKGND: return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ case WM_QUERYFILEINFO: Window_OnQueryInfo(hwndDlg); break;
+ case WM_NOTIFY: return Window_OnNotify(hwndDlg, (INT)wParam, (LPNMHDR) lParam);
+
+
+ }
+ return 0;
+}
+
+static BOOL CALLBACK extract_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ m_extract_wnd = hwndDlg;
+ m_rip_done = 0;
+ SetDlgItemText(hwndDlg, IDC_STATUS, WASABI_API_LNGSTRINGW(IDS_INITIALIZING));
+ m_pstat_bytesdone = 0;
+ m_pstat_bytesout = 0;
+ m_pstat_timedone = 0;
+ m_extract_nb = 0;
+ m_extract_curtrack = -1;
+ m_cur_rg = 0;
+ {
+ m_extract_nb_total = 0;
+ int l = m_rip_params->ntracks;
+ for (int i = 0;i < l;i++) if (m_rip_params->tracks[i]) m_extract_nb_total++;
+ }
+
+ LockCD(m_rip_params->drive_letter, TRUE);
+
+ SetPropW(hwndDlg, L"WARIPPER", (HANDLE)hwndDlg);
+ SetPropW(hwndDlg, L"DRIVE", (HANDLE)(INT_PTR)(0xFF & m_rip_params->drive_letter));
+
+ if (!uMsgRipperNotify) uMsgRipperNotify = RegisterWindowMessageA("WARIPPER_BROADCAST_MSG");
+ if (uMsgRipperNotify) SendNotifyMessage(HWND_BROADCAST, uMsgRipperNotify, (WPARAM)m_rip_params->drive_letter, (LPARAM)TRUE);
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS2, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+ PostMessage(hwndDlg, WM_APP + 1, 0, 0);
+ if (g_config->ReadInt(L"cdripstatuswnd", 0)) ShowWindow(hwndDlg, SW_SHOW);
+
+ INT bVal;
+
+ if (S_OK == Settings_GetBool(C_EXTRACT, EF_CALCULATERG, &bVal) && bVal)
+ {
+ CreateGain();
+ QueueUserAPC(StartGain, rgThread,
+ (ULONG_PTR)((m_rip_params->ntracks == m_extract_nb_total) ? RG_ALBUM : RG_INDIVIDUAL_TRACKS));
+ }
+ break;
+ case WM_APP + 2: // track is starting to be RG scanned
+ m_statuslist.SetItemText(m_cur_rg, 3, WASABI_API_LNGSTRINGW(IDS_CALCULATING_REPLAY_GAIN));
+ break;
+ case WM_APP + 3: // track is starting to be RG scanned
+ m_statuslist.SetItemText(m_cur_rg, 3, WASABI_API_LNGSTRINGW(IDS_COMPLETED));
+ m_cur_rg++;
+ break;
+ case WM_APP + 1:
+ {
+ INT trackOffset, cchDest;
+ TCHAR szDestination[MAX_PATH] = {0}, szFormat[MAX_PATH] = {0};
+ int l = m_rip_params->ntracks;
+ done = 1;
+
+ Settings_GetInt(C_EXTRACT, EF_TRACKOFFSET, &trackOffset);
+ Settings_ReadString(C_EXTRACT, EF_TITLEFMT, szFormat, ARRAYSIZE(szFormat));
+ Settings_ReadString(C_EXTRACT, EF_PATH, szDestination, ARRAYSIZE(szDestination));
+ CleanupDirectoryString(szDestination);
+
+ cchDest = lstrlen(szDestination);
+
+ for (int i = m_extract_curtrack + 1;i < l;i++)
+ {
+ if (m_rip_params->tracks[i])
+ {
+ StringCchPrintfW(m_extract_src, 64, L"cda://%c,%d.cda", m_rip_params->drive_letter, i + 1);
+ szDestination[cchDest] = TEXT('\0');
+ if (cchDest) PathAddBackslash(szDestination);
+
+ wchar_t tmp1[32] = {0}, tmp2[32] = {0}, tmp3[32] = {0}, tmp4[32] = {0}, tmp5[32] = {0};
+ FormatFileName(szDestination,
+ ARRAYSIZE(szDestination)-11, // ensure we're leaving enough room for the extension
+ szFormat,
+ i + trackOffset,
+ (m_rip_params->artist && *(m_rip_params->artist)) ? (m_rip_params->artist) : WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_ARTIST,tmp1,32),
+ (m_rip_params->album && *(m_rip_params->album)) ? (m_rip_params->album) : WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_ALBUM,tmp2,32),
+ (m_rip_params->tracks[i] && *(m_rip_params->tracks[i])) ? (m_rip_params->tracks[i]) : L"0",
+ (m_rip_params->genre && *(m_rip_params->genre)) ? (m_rip_params->genre) : WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,tmp3,32),
+ (m_rip_params->year && *(m_rip_params->year)) ? (m_rip_params->year) : WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,tmp4,32),
+ (m_rip_params->trackArtists[i] && m_rip_params->trackArtists[i][0])?m_rip_params->trackArtists[i] : WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,tmp5,32),
+ NULL, (m_rip_params->disc && *(m_rip_params->disc)) ? (m_rip_params->disc) : L"");
+ memset(&m_fcs, 0, sizeof(m_fcs));
+ m_fcs.sourcefile = m_extract_src;
+ INT fourcc;
+ Settings_GetInt(C_EXTRACT, EF_FOURCC, &fourcc);
+ m_fcs.destformat[0]=fourcc;
+ if (m_fcs.destformat[0] == OLD_AAC_CODEC) Settings_GetDefault(C_EXTRACT, EF_FOURCC, &m_fcs.destformat[0]);
+
+ // now determine the extension
+ wchar_t fmt[10] = {0};
+ GetExtensionString(fmt, ARRAYSIZE(fmt), (DWORD)m_fcs.destformat[0]);
+ BOOL upperCase;
+ if (SUCCEEDED(Settings_GetBool(C_EXTRACT, EF_UPPEREXTENSION, &upperCase)) && FALSE != upperCase)
+ CharUpper(fmt);
+ else
+ CharLower(fmt);
+
+ StringCchCat(szDestination, ARRAYSIZE(szDestination), TEXT("."));
+ StringCchCat(szDestination, ARRAYSIZE(szDestination), fmt);
+
+ if (m_rip_params->filenames[i]) free(m_rip_params->filenames[i]);
+ m_rip_params->filenames[i] = _wcsdup(szDestination);
+
+ wchar_t tempFile[MAX_PATH] = {0};
+ wchar_t tmppath[MAX_PATH] = {0};
+ GetTempPath(MAX_PATH,tmppath);
+ GetTempFileName(tmppath,L"rip",0,tempFile);
+
+ m_rip_params->tempFilenames[i] = _wcsdup(tempFile);
+ createDirForFileW(m_rip_params->filenames[i]);
+ m_fcs.destfile = _wcsdup(tempFile);
+ createDirForFileW(m_fcs.destfile);
+ m_fcs.callbackhwnd = hwndDlg;
+ m_fcs.error = L"";
+ m_extract_time = 0;
+ m_extract_curtrack = i;
+
+ wchar_t *ptr = m_rip_params->filenames[i];
+ if (cchDest && cchDest < (int)lstrlenW(ptr)) ptr += (cchDest + 1);
+ SetDlgItemText(hwndDlg, IDC_CURTRACK, ptr);
+
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&m_fcs, IPC_CONVERTFILEW) != 1)
+ {
+ wchar_t tmp[512] = {0};
+ StringCchPrintf(tmp, 512, WASABI_API_LNGSTRINGW(IDS_ERROR_RIPPING_TRACK), i + 1, m_fcs.error ? m_fcs.error : L"");
+ MessageBox(hwndDlg, tmp, WASABI_API_LNGSTRINGW(IDS_ERROR), MB_OK);
+ done = -1;
+ break;
+ }
+ convertSetPriorityW csp = {
+ &m_fcs,
+ g_config->ReadInt(L"extractprio", THREAD_PRIORITY_NORMAL),
+ };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW);
+ m_extracting = 1;
+ done = 0;
+ PostMessage(hwndDlg, WM_WA_IPC , 0, IPC_CB_CONVERT_STATUS);
+ break;
+ }
+ }
+ if (done && m_rip_params)
+ {
+ LockCD(m_rip_params->drive_letter, FALSE);
+
+ if (g_config->ReadInt(L"cdripautoeject", 0) && done > 0)
+ {
+ char buf[64] = {0};
+ StringCchPrintfA(buf, 64, "cda://%c.cda", m_rip_params->drive_letter);
+ char buf2[32] = {0};
+ getFileInfo(buf, "<eject>", buf2, sizeof(buf2));
+ }
+
+ if (g_config->ReadInt(L"cdripautoplay", 0) && done > 0)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+ for (int i = 0;i < m_rip_params->ntracks;i++)
+ {
+ if (m_rip_params->tracks[i])
+ {
+ COPYDATASTRUCT cds;
+ cds.dwData = IPC_PLAYFILEW;
+ cds.lpData = (void *) m_rip_params->filenames[i];
+ cds.cbData = sizeof(wchar_t) * (lstrlenW(m_rip_params->filenames[i]) + 1); // include space for null char
+ SendMessage(plugin.hwndWinampParent, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);
+ }
+ }
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ }
+
+
+ if (m_rip_params->ntracks == m_extract_nb_total && done > 0)
+ {
+ INT bVal;
+ if ((S_OK == Settings_GetBool(C_EXTRACT, EF_CREATEM3U, &bVal) && bVal) ||
+ (S_OK == Settings_GetBool(C_EXTRACT, EF_CREATEPLS, &bVal) && bVal))
+ {
+
+ wchar_t str[MAX_PATH] = {0}, fmt[MAX_PATH] = {0};
+ Settings_ReadString(C_EXTRACT, EF_PLAYLISTFMT, fmt, ARRAYSIZE(fmt));
+ Settings_ReadString(C_EXTRACT, EF_PATH, str, ARRAYSIZE(str));
+
+ int l = lstrlenW(str);
+ if (l)
+ PathAddBackslash(str);
+
+ FormatFileName(str, ARRAYSIZE(str)-5, // ensure we're leaving enough room for the extension
+ fmt, 0xdeadbeef,
+ m_rip_params->artist ? m_rip_params->artist : L"",
+ m_rip_params->album ? m_rip_params->album : L"",
+ NULL,
+ m_rip_params->genre ? m_rip_params->genre : L"",
+ m_rip_params->year ? m_rip_params->year : L"",
+ NULL,
+ NULL,
+ m_rip_params->disc ? m_rip_params->disc : L"");
+
+ if (S_OK == Settings_GetBool(C_EXTRACT, EF_CREATEM3U, &bVal) && bVal)
+ {
+ wchar_t str2[MAX_PATH] = {0};
+ lstrcpynW(str2, str, MAX_PATH);
+ StringCchCatW(str2, MAX_PATH, L".m3u");
+ createDirForFileW(str2);
+ M3UWriter w;
+ FILE *fp=_wfopen(str2, L"wt");
+ w.Open(fp, AutoCharFn(str2), TRUE);
+ BOOL ext;
+ Settings_GetBool(C_EXTRACT, EF_USEM3UEXT, &ext);
+ for (int i = 0;i < m_rip_params->ntracks;i++)
+ {
+ if (m_rip_params->tracks[i] && m_rip_params->filenames[i])
+ {
+ GayString str;
+ str.Set(AutoChar(m_rip_params->artist));
+ str.Append(" - ");
+ str.Append(AutoChar(m_rip_params->tracks[i]));
+ if (ext)
+ w.SetExtended(AutoCharFn(m_rip_params->filenames[i]), str.Get(), m_rip_params->lengths[i]);
+ else
+ w.SetFilename(AutoCharFn(m_rip_params->filenames[i]));
+
+ }
+ }
+ w.Close();
+ }
+
+ if (S_OK == Settings_GetBool(C_EXTRACT, EF_CREATEPLS, &bVal) && bVal)
+ {
+ char str2[MAX_PATH] = {0};
+ lstrcpynA(str2, AutoChar(str), MAX_PATH);
+ StringCchCatA(str2, MAX_PATH, ".pls");
+ createDirForFile(str2);
+ // TODO: check for bad unicode conversion
+ PLSWriter w;
+ w.Open(str2);
+ for (int i = 0;i < m_rip_params->ntracks;i++)
+ {
+ if (m_rip_params->tracks[i] && m_rip_params->filenames[i])
+ {
+ GayString str;
+ str.Set(AutoChar(m_rip_params->artist));
+ str.Append(" - ");
+ str.Append(AutoChar(m_rip_params->tracks[i]));
+ w.SetFilename(AutoCharFn(m_rip_params->filenames[i]));
+ w.SetTitle(str.Get());
+ w.SetLength(m_rip_params->lengths[i]);
+ w.Next();
+ }
+ }
+ w.Close();
+ }
+ }
+
+ if (S_OK == Settings_GetBool(C_EXTRACT, EF_CREATEMLPL, &bVal) && bVal)
+ {
+ itemRecordListW irl = {0, };
+ allocRecordList(&irl, m_rip_params->ntracks, 0);
+ for (int i = 0;i < m_rip_params->ntracks;i++)
+ {
+ if (m_rip_params->tracks[i])
+ {
+ int n = irl.Size;
+ memset(&irl.Items[n], 0, sizeof(itemRecordW));
+ irl.Items[n].filename = _wcsdup(m_rip_params->filenames[i]);
+ irl.Items[n].album = _wcsdup(m_rip_params->album);
+ irl.Items[n].artist = _wcsdup(m_rip_params->artist);
+ irl.Items[n].title = _wcsdup(m_rip_params->tracks[i]);
+ irl.Items[n].genre = _wcsdup(m_rip_params->genre);
+ irl.Items[n].year = _wtoi(m_rip_params->year);
+ irl.Items[n].length = m_rip_params->lengths[i];
+ irl.Size++;
+ }
+ }
+
+ GayString str;
+ str.Set(AutoChar(m_rip_params->artist));
+ str.Append(" - ");
+ str.Append(AutoChar(m_rip_params->album));
+ AutoWide name(str.Get());
+ mlMakePlaylist pl = {sizeof(mlMakePlaylist), (const wchar_t*)name, ML_TYPE_ITEMRECORDLISTW, (void *) & irl, 0x01};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&pl, ML_IPC_PLAYLIST_MAKE);
+ freeRecordList(&irl);
+ }
+ } // playlist creation
+
+ if (rgThread)
+ QueueUserAPC(WriteGain, rgThread, 0);
+ else
+ PostMessage(m_extract_wnd, WM_APP + 4, 0, 0);
+ }
+ }
+ break;
+ case WM_APP + 4:
+ if (m_db_has_upd)
+ {
+ // TODO: benski> does mldb read metadata from this call or the 'add' call - because it won't have replaygain tags until now
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_DB_SYNCDB);
+ }
+
+ m_rip_done = 1;
+ if (g_config->ReadInt(L"cdripautoclose", 1))
+ {
+ DestroyWindow(hwndDlg);
+ }
+ else
+ {
+ SetWindowText(hwndDlg, WASABI_API_LNGSTRINGW(done > 0 ? IDS_RIP_COMPLETE : IDS_RIP_FAILED));
+ SetDlgItemText(hwndDlg, IDC_BUTTON1, WASABI_API_LNGSTRINGW(IDS_CLOSE));
+ if (m_hwndstatus)
+ {
+ SetDlgItemText(m_hwndstatus, IDC_CANCEL_RIP, WASABI_API_LNGSTRINGW(IDS_CLOSE));
+ }
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, 100, 0);
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS2, PBM_SETPOS, 100, 0);
+
+
+ int now = m_pstat_timedone;
+
+ int bytesout = m_pstat_bytesout;
+
+ double extracted_time = (double) m_pstat_bytesdone * (1.0 / 44100.0 / 4.0);
+ if (extracted_time < 1.0) extracted_time = 1.0;
+
+ int br = (int) (((double)bytesout * 8.0 / extracted_time) / 1000.0 + 0.5);
+
+ if (done > 0)
+ {
+ wchar_t sstr[16] = {0};
+ StringCchPrintf(m_last_total_status, 512,
+ WASABI_API_LNGSTRINGW(IDS_X_TRACKS_RIPPED_IN_X),
+ m_extract_nb_total,
+ WASABI_API_LNGSTRINGW_BUF(m_extract_nb_total == 1 ? IDS_TRACK : IDS_TRACKS,sstr,16),
+ now / 1000 / 60, (now / 1000) % 60,
+ extracted_time / (now / 1000.0),
+ br, (double)bytesout * (1.0 / (1024.0*1024.0))
+ );
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_RIP_FAILED,m_last_total_status,512);
+ }
+
+ SetDlgItemText(hwndDlg, IDC_STATUS2, m_last_total_status);
+ SetDlgItemText(hwndDlg, IDC_CURTRACK, WASABI_API_LNGSTRINGW(IDS_COMPLETED));
+ SetDlgItemText(hwndDlg, IDC_STATUS, L"");
+
+ if (m_hwndstatus && IsWindow(m_hwndstatus))
+ {
+ SetDlgItemText(m_hwndstatus, IDC_CDINFO, m_last_total_status);
+ SetDlgItemText(m_hwndstatus, IDC_CANCEL_RIP, WASABI_API_LNGSTRINGW(IDS_DONE));
+ }
+ }
+ break;
+ case WM_WA_IPC:
+ switch (lParam)
+ {
+ case IPC_CB_CONVERT_STATUS:
+ {
+ if (!m_extract_time)
+ {
+ m_extract_time = GetTickCount();
+ break;
+ }
+ DWORD now = GetTickCount() - m_extract_time;
+ if (!now) now = 1000; //safety
+ wchar_t tmp[512 + 128] = {0};
+
+ {
+ int total_t = 0;
+ if (wParam) total_t = MulDiv(100, now, (int)wParam);
+ int rem_t = total_t - now;
+
+ double extracted_time = (double) m_fcs.bytes_done * (1.0 / 44100.0 / 4.0);
+ if (extracted_time < 1.0) extracted_time = 1.0;
+ int br = (int) (((double)m_fcs.bytes_out * 8.0 / extracted_time) / 1000.0 + 0.5);
+
+ int estsize = 0;
+ if (m_fcs.bytes_total > 0) estsize = MulDiv(m_fcs.bytes_out, m_fcs.bytes_total, m_fcs.bytes_done);
+
+ if (rem_t < 0) rem_t = 0;
+ if (total_t < 0) total_t = 0;
+
+ StringCchPrintf(tmp, 640,
+ WASABI_API_LNGSTRINGW(IDS_ELAPSED_X_REMAINING_X_TOTAL_X),
+ now / 1000 / 60, (now / 1000) % 60,
+ rem_t / 1000 / 60, (rem_t / 1000) % 60,
+ total_t / 1000 / 60, (total_t / 1000) % 60,
+ extracted_time / ((double)now / 1000.0),
+ br, (double)estsize * (1.0 / (1024.0*1024.0))
+ );
+ SetDlgItemText(hwndDlg, IDC_STATUS, tmp);
+
+ if (m_hwndstatus && IsWindow(m_hwndstatus))
+ {
+ StringCchPrintf(m_last_item_status, 512,
+ WASABI_API_LNGSTRINGW(IDS_X_KBPS_AT_X_REALTIME),
+ wParam,
+ br,
+ extracted_time / ((double)now / 1000.0)
+ );
+ m_statuslist.SetItemText(m_extract_nb, 3, m_last_item_status);
+ }
+ }
+
+ {
+ int total_in_bytes_calc = m_rip_params->total_length_bytes;
+
+ now += m_pstat_timedone;
+
+ int total_t = 0;
+ int bytesdone = m_fcs.bytes_done + m_pstat_bytesdone;
+ int bytesout = m_fcs.bytes_out + m_pstat_bytesout;
+ if (bytesdone) total_t = MulDiv(total_in_bytes_calc, now, bytesdone);
+
+ int rem_t = total_t - now;
+
+ double extracted_time = (double) bytesdone * (1.0 / 44100.0 / 4.0);
+ if (extracted_time < 1.0) extracted_time = 1.0;
+ int br = (int) (((double)bytesout * 8.0 / extracted_time) / 1000.0 + 0.5);
+
+ int estsize = 0;
+ if (total_in_bytes_calc > 0) estsize = MulDiv(bytesout, total_in_bytes_calc, bytesdone);
+
+ if (rem_t < 0) rem_t = 0;
+ if (total_t < 0) total_t = 0;
+
+ StringCchPrintf(m_last_total_status, 512,
+ WASABI_API_LNGSTRINGW(IDS_X_OF_X_ELAPSED_X_REMAINING_X),
+ m_extract_nb + 1, m_extract_nb_total,
+ now / 1000 / 60, (now / 1000) % 60,
+ rem_t / 1000 / 60, (rem_t / 1000) % 60,
+ total_t / 1000 / 60, (total_t / 1000) % 60,
+ extracted_time / ((double)now / 1000.0),
+ br, (double)estsize * (1.0 / (1024.0*1024.0))
+ );
+
+ if (m_hwndstatus && IsWindow(m_hwndstatus))
+ {
+ SetDlgItemText(m_hwndstatus, IDC_CDINFO, m_last_total_status);
+ }
+ SetDlgItemText(hwndDlg, IDC_STATUS2, m_last_total_status);
+
+ int a = 0;
+ if (total_in_bytes_calc) a = MulDiv(bytesdone, 100, total_in_bytes_calc);
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS2, PBM_SETPOS, a, 0);
+
+ StringCchPrintf(tmp, 640, WASABI_API_LNGSTRINGW(IDS_X_PERCENT_RIPPING_FROM_CD), a);
+ SetWindowText(hwndDlg, tmp);
+ }
+
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, wParam, 0);
+ }
+ break;
+ case IPC_CB_CONVERT_DONE:
+
+ SendMessage(plugin.hwndWinampParent , WM_WA_IPC, (WPARAM)&m_fcs, IPC_CONVERTFILEW_END);
+ free(m_fcs.destfile);
+ m_pstat_bytesdone += m_fcs.bytes_done;
+ m_pstat_bytesout += m_fcs.bytes_out;
+ if (m_extract_time) m_pstat_timedone += GetTickCount() - m_extract_time;
+
+ CopyFileW(m_rip_params->tempFilenames[m_extract_curtrack], m_rip_params->filenames[m_extract_curtrack], FALSE);
+ DeleteFileW(m_rip_params->tempFilenames[m_extract_curtrack]);
+ if (AGAVE_API_STATS)
+ {
+ AGAVE_API_STATS->IncrementStat(api_stats::RIP_COUNT);
+ AGAVE_API_STATS->SetStat(api_stats::RIP_FORMAT, m_fcs.destformat[0]);
+ }
+ wchar_t *lastfn = m_rip_params->filenames[m_extract_curtrack];
+
+ if (g_config->ReadInt(L"extracttag", 1))
+ {
+ // add metadata to this file
+ if (updateFileInfoW(lastfn, L"title", m_rip_params->tracks[m_extract_curtrack]))
+ {
+ updateFileInfoW(lastfn, L"conductor", m_rip_params->conductors[m_extract_curtrack]);
+ updateFileInfoW(lastfn, L"composer", m_rip_params->composers[m_extract_curtrack]);
+ updateFileInfoW(lastfn, L"GracenoteFileID", m_rip_params->gracenoteFileIDs[m_extract_curtrack]);
+ updateFileInfoW(lastfn, L"GracenoteExtData", m_rip_params->gracenoteExtData[m_extract_curtrack]);
+ updateFileInfoW(lastfn, L"artist", m_rip_params->trackArtists[m_extract_curtrack]);
+ //if (lstrcmpiW(m_rip_params->trackArtists[m_extract_curtrack], m_rip_params->artist)) // only write albumartist if they're different
+ updateFileInfoW(lastfn, L"albumartist", m_rip_params->artist);
+ updateFileInfoW(lastfn, L"album", m_rip_params->album);
+ updateFileInfoW(lastfn, L"genre", m_rip_params->genre);
+ updateFileInfoW(lastfn, L"year", m_rip_params->year);
+ updateFileInfoW(lastfn, L"disc", m_rip_params->disc);
+ updateFileInfoW(lastfn, L"publisher", m_rip_params->publisher);
+ if (m_rip_params->comment && m_rip_params->comment[0])
+ updateFileInfoW(lastfn, L"comment", m_rip_params->comment);
+ else
+ {
+ TCHAR szComment[8192] = {0};
+ Settings_ReadString(C_EXTRACT, EF_COMMENTTEXT, szComment, ARRAYSIZE(szComment));
+ updateFileInfoW(lastfn, L"comment", szComment);
+ }
+
+ wchar_t buf[32] = {0};
+ if (m_extract_curtrack >= 0)
+ {
+ if (g_config->ReadInt(L"total_tracks", 0))
+ StringCchPrintfW(buf, 32, L"%d/%d", m_extract_curtrack + g_config->ReadInt(L"trackoffs", 1), m_rip_params->ntracks);
+ else
+ StringCchPrintfW(buf, 32, L"%d", m_extract_curtrack + g_config->ReadInt(L"trackoffs", 1));
+ }
+ else buf[0] = 0;
+ updateFileInfoW(lastfn, L"track", buf);
+
+ if (WASABI_API_APP)
+ updateFileInfoW(lastfn, L"tool", WASABI_API_APP->main_getVersionString());
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+
+ if (g_config->ReadInt(L"extractaddml", 1))
+ {
+ LMDB_FILE_ADD_INFOW fi = {lastfn, -1, -1};
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_DB_ADDORUPDATEFILEW, (WPARAM)&fi);
+ m_db_has_upd = 1;
+ }
+
+ if (m_hwndstatus && IsWindow(m_hwndstatus))
+ {
+ m_statuslist.SetItemText(m_extract_nb, 3, WASABI_API_LNGSTRING(IDS_WAITING));
+ }
+
+ if (rgThread)
+ QueueUserAPC(CalculateGain, rgThread, (ULONG_PTR)_wcsdup(lastfn));
+ else
+ PostMessage(m_extract_wnd, WM_APP + 3, 0, 0);
+
+
+ m_extract_nb++;
+ m_extracting = 0;
+ PostMessage(hwndDlg, WM_APP + 1, 0, 0);
+ break;
+ }
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON1:
+ {
+ wchar_t title[64] = {0};
+ if (m_rip_done ||
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_CANCEL_RIP),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD_RIP_QUESTION,title,64),
+ MB_YESNO | MB_ICONQUESTION) == IDYES)
+ {
+ LockCD(m_rip_params->drive_letter, FALSE);
+ DestroyWindow(hwndDlg);
+ }
+ return 0;
+ }
+ case IDCANCEL:
+ g_config->WriteInt(L"cdripstatuswnd", 0);
+ ShowWindow(hwndDlg, SW_HIDE);
+ break;
+ }
+ break;
+ case WM_CLOSE:
+ return 0;
+ case WM_DESTROY:
+ if (m_extracting)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&m_fcs, IPC_CONVERTFILEW_END);
+ // make sure we clean up on cancel!
+ m_extracting = 0;
+ DeleteFileW(m_rip_params->tempFilenames[m_extract_curtrack]);
+ }
+ if (uMsgRipperNotify) SendNotifyMessage(HWND_BROADCAST, uMsgRipperNotify, (WPARAM)(m_rip_params) ? m_rip_params->drive_letter : 0, (LPARAM)FALSE);
+ if (m_rip_params)
+ {
+ int i;
+ for (i = 0; i < m_rip_params->ntracks; i++)
+ {
+ free(m_rip_params->tracks[i]);
+ free(m_rip_params->trackArtists[i]);
+ free(m_rip_params->composers[i]);
+ free(m_rip_params->gracenoteFileIDs[i]);
+ free(m_rip_params->gracenoteExtData[i]);
+ free(m_rip_params->conductors[i]);
+ free(m_rip_params->filenames[i]);
+ free(m_rip_params->tempFilenames[i]);
+ }
+ free(m_rip_params->gracenoteFileIDs);
+ free(m_rip_params->gracenoteExtData);
+ free(m_rip_params->composers);
+ free(m_rip_params->conductors);
+ free(m_rip_params->tracks);
+ free(m_rip_params->trackArtists);
+ free(m_rip_params->filenames);
+ free(m_rip_params->tempFilenames);
+ free(m_rip_params->lengths);
+
+ free(m_rip_params->album);
+ free(m_rip_params->artist);
+ free(m_rip_params->genre);
+ free(m_rip_params->year);
+ free(m_rip_params->publisher);
+ free(m_rip_params->comment);
+ free(m_rip_params->disc);
+
+ free(m_rip_params);
+ m_rip_params = 0;
+ }
+ m_extract_wnd = 0;
+
+ if (rgThread)
+ QueueUserAPC(CloseGain, rgThread, 0);
+
+ break;
+ }
+ return 0;
+}
+
+void cdrip_stop_all_extracts()
+{
+ if (m_rip_params) LockCD(m_rip_params->drive_letter, FALSE);
+ if (m_extract_wnd) DestroyWindow(m_extract_wnd);
+ if (m_hwndstatus) DestroyWindow(m_hwndstatus);
+}
+
+int cdrip_isextracting(char drive)
+{
+ if (!m_rip_params) return 0;
+ if (drive == -1 && m_rip_done)
+ {
+ if (m_extract_wnd && IsWindow(m_extract_wnd)) DestroyWindow(m_extract_wnd);
+ if (m_hwndstatus && IsWindow(m_hwndstatus)) DestroyWindow(m_hwndstatus);
+ return 0;
+ }
+ if (drive == 0 || drive == -1) return toupper(m_rip_params->drive_letter);
+ return toupper(m_rip_params->drive_letter) == toupper(drive);
+}
+
+HWND cdrip_FindBurningHWND(char cLetter)
+{
+ HWND h = 0;
+ while (NULL != (h = FindWindowExW(NULL, h, L"#32770", NULL)))
+ {
+ if (!GetPropW(h, L"WARIPPER")) continue;
+ if (((char)(INT_PTR)GetPropW(h, L"DRIVE")) == cLetter) return h;
+ }
+ return NULL;
+}
+
+void cdrip_extractFiles(cdrip_params *parms)
+{
+ WASABI_API_LNGSTRINGW_BUF(IDS_INITIALIZING,m_last_total_status,512);
+ m_last_item_status[0] = 0;
+ m_rip_params = parms;
+ WASABI_API_CREATEDIALOGW(IDD_VIEW_CDROM_EXTRACT, plugin.hwndWinampParent, extract_dialogProc);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/cmdbar_data.cpp b/Src/Plugins/Library/ml_disc/cmdbar_data.cpp
new file mode 100644
index 00000000..1cd8b511
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/cmdbar_data.cpp
@@ -0,0 +1,485 @@
+#include "main.h"
+#include <windowsx.h>
+#include "./resource.h"
+#include "./commandbar.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+static HMLIMGLST hmlilButton = NULL;
+
+static LPCTSTR GetImageTagStr(INT resId)
+{
+ switch(resId)
+ {
+ case IDB_PLAY_NORMAL: return TEXT("button.play");
+ case IDB_PLAY_HIGHLIGHTED: return TEXT("button.play.highlighted");
+ case IDB_PLAY_PRESSED: return TEXT("button.play.pressed");
+ case IDB_PLAY_DISABLED: return TEXT("button.play.disabled");
+ case IDB_ENQUEUE_NORMAL: return TEXT("button.enqueue");
+ case IDB_ENQUEUE_HIGHLIGHTED: return TEXT("button.enqueue.highlighted");
+ case IDB_ENQUEUE_PRESSED: return TEXT("button.enqueue.pressed");
+ case IDB_ENQUEUE_DISABLED: return TEXT("button.enqueue.disabled");
+ case IDB_EJECT2_NORMAL: return TEXT("button.eject");
+ case IDB_EJECT2_HIGHLIGHTED: return TEXT("button.eject.highlighted");
+ case IDB_EJECT2_PRESSED: return TEXT("button.eject.pressed");
+ case IDB_EJECT2_DISABLED: return TEXT("button.eject.disabled");
+ }
+ return NULL;
+}
+
+static HMLIMGLST DataCmdBar_CreateImageList()
+{
+ HMLIMGLST hmlil;
+ MLIMAGELISTCREATE mlilCreate;
+ MLIMAGESOURCE mlis;
+ MLIMAGELISTITEM mlilItem;
+
+ mlilCreate.cx = g_view_metaconf->ReadIntEx(TEXT("artwork"), TEXT("button.icon.cx"), 12);
+ mlilCreate.cy = g_view_metaconf->ReadIntEx(TEXT("artwork"), TEXT("button.icon.cy"), 12);
+ mlilCreate.cInitial = 12;
+ mlilCreate.cGrow = 3;
+ mlilCreate.cCacheSize = 4;
+ mlilCreate.flags = MLILC_COLOR32;
+
+ hmlil = MLImageList_Create(plugin.hwndLibraryParent, &mlilCreate);
+ if (NULL == hmlil) return NULL;
+
+
+ ZeroMemory(&mlilItem, sizeof(MLIMAGELISTITEM));
+ mlilItem.cbSize = sizeof(MLIMAGELISTITEM);
+ mlilItem.hmlil = hmlil;
+ mlilItem.filterUID = MLIF_BUTTONBLENDPLUSCOLOR_UID;
+ mlilItem.pmlImgSource = &mlis;
+
+ ZeroMemory(&mlis, sizeof(MLIMAGESOURCE));
+ mlis.cbSize = sizeof(MLIMAGESOURCE);
+ mlis.type = SRC_TYPE_PNG;
+ mlis.hInst = plugin.hDllInstance;
+
+
+ INT imageList[] =
+ { IDB_PLAY_NORMAL, IDB_PLAY_PRESSED, IDB_PLAY_HIGHLIGHTED, IDB_PLAY_DISABLED,
+ IDB_ENQUEUE_NORMAL, IDB_ENQUEUE_PRESSED, IDB_ENQUEUE_HIGHLIGHTED, IDB_ENQUEUE_DISABLED,
+ IDB_EJECT2_NORMAL, IDB_EJECT2_PRESSED, IDB_EJECT2_HIGHLIGHTED, IDB_EJECT2_DISABLED,
+ };
+
+
+ TCHAR szResource[MAX_PATH] = {0}, szPath[MAX_PATH] = {0}, szFullPath[MAX_PATH] = {0};
+ g_view_metaconf->ReadCchStringEx(szPath, ARRAYSIZE(szPath), TEXT("artwork"), TEXT("path"), NULL);
+ for(int i = 0; i < sizeof(imageList)/sizeof(imageList[0]); i++)
+ {
+ mlilItem.nTag = imageList[i];
+ g_view_metaconf->ReadCchStringEx(szResource, ARRAYSIZE(szResource), TEXT("artwork"), GetImageTagStr(imageList[i]), NULL);
+ if (TEXT('\0') != szResource[0])
+ {
+ PathCombine(szFullPath, szPath, szResource);
+ mlis.lpszName = szFullPath;
+ mlis.flags |= ISF_LOADFROMFILE;
+ }
+ else
+ {
+ mlis.lpszName = MAKEINTRESOURCE(imageList[i]);
+ mlis.flags &= ~ISF_LOADFROMFILE;
+ }
+
+ MLImageList_Add(plugin.hwndLibraryParent, &mlilItem);
+ }
+ return hmlil;
+}
+
+static HMLIMGLST DataCmdBar_CreateDropDownImageList(HMENU hMenu)
+{
+ HMLIMGLST hmlil;
+ MLIMAGELISTCREATE mlilCreate;
+ MLIMAGESOURCE mlis;
+
+ if (!hMenu) return NULL;
+
+ mlilCreate.cx = 16;
+ mlilCreate.cy = 16;
+ mlilCreate.cInitial = 2;
+ mlilCreate.cGrow = 1;
+ mlilCreate.cCacheSize = 3;
+ mlilCreate.flags = MLILC_COLOR32;
+
+ hmlil = MLImageList_Create(plugin.hwndLibraryParent, &mlilCreate);
+ if (NULL == hmlil) return NULL;
+
+ ZeroMemory(&mlis, sizeof(MLIMAGESOURCE));
+ mlis.cbSize = sizeof(MLIMAGESOURCE);
+ mlis.type = SRC_TYPE_PNG;
+ mlis.hInst = plugin.hDllInstance;
+
+
+ INT imageList[] = { IDB_PLAY_MENU, IDB_ENQUEUE_MENU, };
+ MENUITEMINFOW mii = { sizeof(MENUITEMINFOW), };
+ mii.fMask = MIIM_ID;
+ for(int i = 0; i < sizeof(imageList)/sizeof(imageList[0]); i++)
+ {
+ if (GetMenuItemInfoW(hMenu, i, TRUE, &mii))
+ {
+ mlis.lpszName = MAKEINTRESOURCEW(imageList[i]);
+ MLImageList_Add2(plugin.hwndLibraryParent, hmlil, MLIF_FILTER1_UID, &mlis, mii.wID);
+ }
+ }
+ return hmlil;
+
+}
+
+
+
+static void DataCmdBar_SetButtonImages(HWND hButton, HMLIMGLST hmlil, INT normal, INT hover, INT pressed, INT disabled)
+{
+ MLBUTTONIMAGELIST bil;
+ MLIMAGELISTTAG t;
+ bil.hmlil = hmlil;
+ t.hmlil = bil.hmlil;
+
+ t.nTag = normal;
+ bil.normalIndex = (MLImageList_GetIndexFromTag(plugin.hwndLibraryParent, &t)) ? t.mlilIndex : -1;
+
+ if (disabled == normal) bil.disabledIndex = bil.normalIndex;
+ else
+ {
+ t.nTag = disabled;
+ bil.disabledIndex = (MLImageList_GetIndexFromTag(plugin.hwndLibraryParent, &t)) ? t.mlilIndex : bil.normalIndex;
+ }
+
+ if (hover == normal) bil.hoverIndex = bil.normalIndex;
+ else if (hover == disabled) bil.hoverIndex = bil.disabledIndex;
+ else
+ {
+ t.nTag = hover;
+ bil.hoverIndex = (MLImageList_GetIndexFromTag(plugin.hwndLibraryParent, &t)) ? t.mlilIndex : bil.normalIndex;
+ }
+
+ if (pressed == normal) bil.pressedIndex = bil.normalIndex;
+ else if (pressed == disabled) bil.pressedIndex = bil.disabledIndex;
+ else if (pressed == hover) bil.pressedIndex = bil.hoverIndex;
+ else
+ {
+ t.nTag = pressed;
+ bil.pressedIndex = (MLImageList_GetIndexFromTag(plugin.hwndLibraryParent, &t)) ? t.mlilIndex : bil.normalIndex;
+ }
+
+ SENDMLIPC(hButton, ML_IPC_SKINNEDBUTTON_SETIMAGELIST, (LPARAM)&bil);
+}
+
+static void PlayEx_Initialize(HWND hdlg)
+{
+ HWND hButton = GetDlgItem(hdlg, IDC_BTN_PLAYEX);
+ if (!hButton) return;
+
+ HWND hFileView = (HWND)CommandBar_GetData(hdlg);
+ if (NULL == hFileView) return;
+
+ HMENU hMenu = FileView_GetMenu(hFileView, FVMENU_PLAY);
+ if (!hMenu) return;
+
+ BOOL bPlay = (!hFileView || 0 == (FVS_ENQUEUE & FileView_GetStyle(hFileView)));
+
+ WCHAR szBuffer[256] = {0};
+ MENUITEMINFOW mii = { sizeof(MENUITEMINFOW), };
+ mii.fMask = MIIM_STRING;
+ mii.dwTypeData = szBuffer;
+ mii.cch = sizeof(szBuffer)/sizeof(szBuffer[0]);
+ if (GetMenuItemInfoW(hMenu, (bPlay) ? 0 : 1, TRUE, &mii))
+ {
+ while(mii.cch && L'\t' != szBuffer[mii.cch]) mii.cch--;
+ if (mii.cch > 0) szBuffer[mii.cch] = L'\0';
+ SetWindowTextW(hButton, szBuffer);
+ }
+
+ if (bPlay)
+ {
+ DataCmdBar_SetButtonImages(hButton, hmlilButton, IDB_PLAY_NORMAL,
+ IDB_PLAY_PRESSED, IDB_PLAY_HIGHLIGHTED, IDB_PLAY_DISABLED);
+ }
+ else
+ {
+ DataCmdBar_SetButtonImages(hButton, hmlilButton, IDB_ENQUEUE_NORMAL,
+ IDB_ENQUEUE_PRESSED, IDB_ENQUEUE_HIGHLIGHTED, IDB_ENQUEUE_DISABLED);
+ }
+}
+
+static void DataCmdBar_UpdateControls(HWND hdlg, BOOL bRedraw)
+{
+ INT buttonList[] = { IDC_BTN_PLAYEX, IDC_BTN_COPY, };
+ HDWP hdwp;
+ DWORD flags, size;
+ INT w;
+
+ flags = SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | ((bRedraw) ? 0 : SWP_NOREDRAW);
+ hdwp = BeginDeferWindowPos(sizeof(buttonList)/sizeof(buttonList[0]));
+ if (!hdwp) return;
+
+ for(int i =0; i < sizeof(buttonList)/sizeof(buttonList[0]); i++)
+ {
+ HWND hctrl = GetDlgItem(hdlg, buttonList[i]);
+ if (NULL != hctrl)
+ {
+ size = 0;
+ switch(buttonList[i])
+ {
+ case IDC_BTN_PLAYEX:
+ {
+ HWND hFileView = (HWND)CommandBar_GetData(hdlg);
+ if (NULL == hFileView) return;
+
+ HMENU hMenu = FileView_GetMenu(hFileView, FVMENU_PLAY);
+ if (hMenu)
+ {
+ WCHAR szText[256] = {0};
+ INT count = GetMenuItemCount(hMenu);
+ MENUITEMINFO mii = {0};
+
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_STRING;
+ mii.dwTypeData = szText;
+
+ w = 0;
+ for (int i = 0; i < count; i++)
+ {
+ mii.cch = sizeof(szText)/sizeof(szText[0]);
+ if (GetMenuItemInfo(hMenu, i, TRUE, &mii))
+ {
+ while(mii.cch && L'\t' != szText[mii.cch]) mii.cch--;
+ if (mii.cch > 0) szText[mii.cch] = L'\0';
+ size = MLSkinnedButton_GetIdealSize(hctrl, szText);
+ if (w < LOWORD(size)) w = LOWORD(size);
+ }
+ }
+ size = MAKELONG(w + 8, HIWORD(size));
+ }
+ }
+ break;
+ default:
+ size = MLSkinnedButton_GetIdealSize(hctrl, NULL);
+ break;
+ }
+
+ INT width = LOWORD(size), height = HIWORD(size);
+ if (width < 82) width = 82;
+ if (height < 14) height = 14;
+
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, 0, 0, width, height, flags);
+ }
+ }
+
+ EndDeferWindowPos(hdwp);
+
+ SetWindowPos(hdlg, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE |
+ SWP_NOZORDER | SWP_FRAMECHANGED | ((bRedraw) ? 0 : SWP_NOREDRAW));
+}
+
+static BOOL DataCmdBar_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam)
+{
+ MLSKINWINDOW sw;
+ sw.skinType = SKINNEDWND_TYPE_AUTO;
+ sw.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ sw.hwndToSkin = hdlg;
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+
+
+ sw.hwndToSkin = GetDlgItem(hdlg, IDC_BTN_EJECT);
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+
+ sw.style |= SWBS_SPLITBUTTON;
+ sw.hwndToSkin = GetDlgItem(hdlg, IDC_BTN_PLAYEX);
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+
+ sw.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ sw.hwndToSkin = GetDlgItem(hdlg, IDC_BTN_COPY);
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+
+
+ sw.skinType = SKINNEDWND_TYPE_STATIC;
+ sw.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ sw.hwndToSkin = GetDlgItem(hdlg, IDC_LBL_STATUS);
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+
+ hmlilButton = DataCmdBar_CreateImageList();
+
+ PlayEx_Initialize(hdlg);
+
+ DataCmdBar_SetButtonImages(GetDlgItem(hdlg, IDC_BTN_EJECT), hmlilButton, IDB_EJECT2_NORMAL,
+ IDB_EJECT2_PRESSED, IDB_EJECT2_HIGHLIGHTED, IDB_EJECT2_DISABLED);
+
+ DataCmdBar_UpdateControls(hdlg, FALSE);
+ return FALSE;
+}
+
+static void DataCmdBar_OnDestroy(HWND hdlg)
+{
+ if (hmlilButton)
+ {
+ MLImageList_Destroy(plugin.hwndLibraryParent, hmlilButton);
+ hmlilButton = NULL;
+ }
+}
+
+static void DataCmdBar_OnWindowPosChanged(HWND hdlg, WINDOWPOS *pwp)
+{
+ if (0 == (SWP_NOSIZE & pwp->flags) || 0 != (SWP_FRAMECHANGED & pwp->flags))
+ {
+ HWND hctrl;
+ HDWP hdwp;
+ RECT rc, rw;
+ DWORD flags;
+
+ if (!GetClientRect(hdlg, &rc)) return;
+ InflateRect(&rc, -2, 0);
+ LONG left = rc.left-2;
+ LONG right = rc.right;
+
+ hdwp = BeginDeferWindowPos(4);
+ if (!hdwp) return;
+
+ flags = SWP_NOACTIVATE | SWP_NOZORDER | ((SWP_NOREDRAW | SWP_NOCOPYBITS) & pwp->flags);
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_BTN_PLAYEX)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, left, rc.top, rw.right - rw.left, rc.bottom - rc.top, flags);
+ left += ((rw.right - rw.left) + 8);
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_BTN_COPY)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, left, rc.top, rw.right - rw.left, rc.bottom - rc.top, flags);
+ left += ((rw.right - rw.left) + 8);
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_BTN_EJECT)) && GetWindowRect(hctrl, &rw))
+ {
+ right -= (rw.right - rw.left);
+ if (right < (left + 16)) right = left + 16;
+
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, right, rc.top, rw.right - rw.left, rc.bottom - rc.top, flags);
+ right -= 4;
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_LBL_STATUS)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, left, rc.top, right - left, rc.bottom - rc.top, flags);
+ }
+ EndDeferWindowPos(hdwp);
+ }
+ if (0 == (SWP_NOREDRAW & pwp->flags)) InvalidateRect(GetDlgItem(hdlg, IDC_LBL_STATUS), NULL, TRUE);
+}
+
+static void DataCmdBar_OnPlayDropDown(HWND hdlg, HWND hctrl)
+{
+ RECT r;
+ if (!GetWindowRect(hctrl, &r)) return;
+
+ HWND hFileView = (HWND)CommandBar_GetData(hdlg);
+ if (NULL == hFileView) return;
+
+
+ HMENU hMenu = FileView_GetMenu(hFileView, FVMENU_PLAY);
+ if (!hMenu) return;
+
+
+ MLSkinnedButton_SetDropDownState(hctrl, TRUE);
+
+ HMLIMGLST hmlilDropDown = DataCmdBar_CreateDropDownImageList(hMenu);
+
+ MLTrackSkinnedPopupMenuEx(plugin.hwndLibraryParent, hMenu,
+ TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN | TPM_NONOTIFY,
+ r.left, r.top - 2, hFileView, NULL, hmlilDropDown, r.right - r.left,
+ SMS_USESKINFONT, NULL, 0L);
+
+ MLSkinnedButton_SetDropDownState(hctrl, FALSE);
+ MLImageList_Destroy(plugin.hwndLibraryParent, hmlilDropDown);
+}
+
+static void DataCmdBar_OnPlayClick(HWND hdlg, HWND hButton)
+{
+ EnableWindow(hButton, FALSE);
+
+ HWND hFileView = (HWND)CommandBar_GetData(hdlg);
+ if (NULL != hFileView)
+ {
+ HMENU hMenu = FileView_GetMenu(hFileView, FVMENU_PLAY);
+ if (NULL != hMenu)
+ {
+ UINT uCmd = (FVS_ENQUEUE & FileView_GetStyle(hFileView)) ? FVA_ENQUEUE : FVA_PLAY;
+ SendMessageW(hFileView, WM_COMMAND, MAKEWPARAM(FileView_GetActionCommand(hFileView, uCmd), 0), 0L);
+ }
+ }
+
+ EnableWindow(hButton, TRUE);
+
+}
+
+static void DataCmdBar_OnCommand(HWND hdlg, INT eventId, INT ctrlId, HWND hwndCtrl)
+{
+ switch (ctrlId)
+ {
+ case IDC_BTN_PLAYEX:
+ switch(eventId)
+ {
+ case MLBN_DROPDOWN: DataCmdBar_OnPlayDropDown(hdlg, hwndCtrl); break;
+ case BN_CLICKED: DataCmdBar_OnPlayClick(hdlg, hwndCtrl); break;
+ }
+ break;
+ case IDC_BTN_EJECT:
+ switch(eventId)
+ {
+ case BN_CLICKED: SendMessageW(GetParent(hdlg), WM_COMMAND, MAKEWPARAM(ID_EJECT_DISC, 0), 0L); break; // straight to container...
+ }
+ break;
+ case IDC_BTN_COPY:
+ switch(eventId)
+ {
+ case BN_CLICKED: SendMessageW(hdlg, WM_COMMAND, MAKEWPARAM(ID_COPY_SELECTION, 0), 0L); break;
+ }
+ break;
+ }
+}
+
+
+static INT DataCmdBar_OnGetBestHeight(HWND hdlg)
+{
+ INT h, height = 0;
+ INT buttonList[] = { IDC_BTN_PLAYEX, };
+
+ for(int i =0; i < sizeof(buttonList)/sizeof(buttonList[0]); i++)
+ {
+ HWND hctrl = GetDlgItem(hdlg, buttonList[i]);
+ if (NULL != hctrl)
+ {
+ DWORD sz = MLSkinnedButton_GetIdealSize(hctrl, NULL);
+ h = HIWORD(sz);
+ if (height < h) height = h;
+ }
+ }
+
+ return height;
+}
+
+INT_PTR WINAPI DataCmdBar_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG: return DataCmdBar_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ case WM_DESTROY: DataCmdBar_OnDestroy(hdlg); break;
+ case WM_WINDOWPOSCHANGED: DataCmdBar_OnWindowPosChanged(hdlg, (WINDOWPOS*)lParam); return TRUE;
+ case WM_COMMAND: DataCmdBar_OnCommand(hdlg, HIWORD(wParam), LOWORD(wParam), (HWND)lParam); break;
+ case CBM_GETBESTHEIGHT: SetWindowLongPtrW(hdlg, DWLP_MSGRESULT, DataCmdBar_OnGetBestHeight(hdlg)); return TRUE;
+ case WM_DISPLAYCHANGE:
+ PlayEx_Initialize(hdlg);
+ break;
+ case WM_SETFONT:
+ DataCmdBar_UpdateControls(hdlg, LOWORD(lParam));
+ return 0;
+ case WM_SETTEXT:
+ case WM_GETTEXT:
+ case WM_GETTEXTLENGTH:
+ SetWindowLongPtrW(hdlg, DWLP_MSGRESULT, (LONGX86)(LONG_PTR)SendDlgItemMessageW(hdlg, IDC_LBL_STATUS, uMsg, wParam, lParam));
+ return TRUE;
+
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/commandbar.cpp b/Src/Plugins/Library/ml_disc/commandbar.cpp
new file mode 100644
index 00000000..ef560c68
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/commandbar.cpp
@@ -0,0 +1,119 @@
+#include "main.h"
+#include "./commandbar.h"
+
+typedef struct _CMDBAR
+{
+ HWND hwndOwner;
+ DLGPROC fnDlgProc;
+ ULONG_PTR uData;
+} CMDBAR;
+
+#define GetBarData(/*HWND*/ __hwndCmdBar) ((CMDBAR*)GetPropW((__hwndCmdBar), L"MLDISCCMDBAR"))
+
+
+static BOOL CommandBar_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam)
+{
+ CMDBARCREATESTRUCT *pcbcs = (CMDBARCREATESTRUCT*)lParam;
+ if (pcbcs && pcbcs->fnDialogProc)
+ {
+ CMDBAR *pcb = (CMDBAR*)calloc(1, sizeof(CMDBAR));
+ if (pcb)
+ {
+ pcb->fnDlgProc = pcbcs->fnDialogProc;
+ pcb->hwndOwner = pcbcs->hwndOwner;
+ pcb->uData = pcbcs->uData;
+ if (!SetPropW(hdlg, L"MLDISCCMDBAR", (HANDLE)pcb))
+ {
+ free(pcb);
+ DestroyWindow(hdlg);
+ }
+ else return pcbcs->fnDialogProc(hdlg, WM_INITDIALOG, (WPARAM)hwndFocus, pcbcs->uData);
+ }
+ }
+ return FALSE;
+}
+
+static void CommandBar_OnDestroy(HWND hdlg)
+{
+ CMDBAR *pcb = GetBarData(hdlg);
+ if (pcb)
+ {
+ RemovePropW(hdlg, L"MLDISCCMDBAR");
+ free(pcb);
+ }
+}
+
+static BOOL CALLBACK CommandBar_EnumChildHeight(HWND hwnd, LPARAM param)
+{
+ if (!param) return FALSE;
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE);
+ if (hdc)
+ {
+ HFONT hf = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0L);
+ if (NULL == hf) hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ if (NULL != hf)
+ {
+ TEXTMETRICW tm = {0};
+ HFONT hfo = (HFONT)SelectObject(hdc, hf);
+ if (GetTextMetricsW(hdc, &tm))
+ {
+ INT *pmh = (INT*)param;
+ if (tm.tmHeight > *pmh) *pmh = tm.tmHeight;
+ }
+ SelectObject(hdc, hfo);
+ }
+ ReleaseDC(hwnd, hdc);
+ }
+ return TRUE;
+
+}
+static INT CommandBar_OnGetBestHeight(HWND hwnd)
+{
+ INT maxHeight = 0;
+ EnumChildWindows(hwnd, CommandBar_EnumChildHeight, (LPARAM)&maxHeight);
+ if (maxHeight != 0) maxHeight += 2;
+ return maxHeight;
+}
+
+INT_PTR CALLBACK CommandBar_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ CMDBAR *pcb = GetBarData(hdlg);
+ if (!pcb)
+ {
+ if (WM_INITDIALOG == uMsg) return CommandBar_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ return 0;
+ }
+ if (pcb->fnDlgProc(hdlg, uMsg, wParam, lParam))
+ {
+ if (WM_DESTROY == uMsg) CommandBar_OnDestroy(hdlg);
+ return TRUE;
+ }
+
+ switch(uMsg)
+ {
+ case WM_DESTROY: CommandBar_OnDestroy(hdlg); return TRUE;
+ case WM_COMMAND:
+ if (pcb->hwndOwner) SendMessageW(pcb->hwndOwner, uMsg, wParam, lParam); return TRUE;
+ break;
+
+ case CBM_GETBESTHEIGHT: MSGRESULT(hdlg, CommandBar_OnGetBestHeight(hdlg));
+ case CBM_GETOWNER: MSGRESULT(hdlg, pcb->hwndOwner);
+ case CBM_SETOWNER:
+ SetWindowLongPtrW(hdlg, DWLP_MSGRESULT, (LONGX86)(LONG_PTR)pcb->hwndOwner);
+ pcb->hwndOwner = (HWND)lParam;
+ return TRUE;
+ case CBM_GETDATA:
+ {
+ CMDBAR *pBar = GetBarData(hdlg);
+ MSGRESULT(hdlg, (pBar) ? pBar->uData : NULL);
+ }
+ case CBM_SETDATA:
+ {
+ CMDBAR *pBar = GetBarData(hdlg);
+ if (pBar) pBar->uData = (ULONG_PTR)lParam;
+ MSGRESULT(hdlg, (NULL != pBar));
+ }
+
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/commandbar.h b/Src/Plugins/Library/ml_disc/commandbar.h
new file mode 100644
index 00000000..6ff90583
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/commandbar.h
@@ -0,0 +1,48 @@
+#ifndef NULLOSFT_MEDIALIBRARY_COMMANDBAR_CONTROL_HEADER
+#define NULLOSFT_MEDIALIBRARY_COMMANDBAR_CONTROL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef CBM_FIRST
+#define CBM_FIRST (WM_APP + 100)
+#endif
+
+#define CBM_GETBESTHEIGHT (CBM_FIRST + 1)
+#define CBM_GETOWNER (CBM_FIRST + 2)
+#define CBM_SETOWNER (CBM_FIRST + 3)
+#define CBM_GETDATA (CBM_FIRST + 4)
+#define CBM_SETDATA (CBM_FIRST + 5)
+
+#define CommandBar_GetBestHeight(/*HWND*/ __hwndCB)\
+ ((INT)(INT_PTR)SENDMSG((__hwndCB), CBM_GETBESTHEIGHT, 0, 0L))
+
+#define CommandBar_GetOwner(/*HWND*/ __hwndCB)\
+ ((HWND)SENDMSG((__hwndCB), CBM_GETOWNER, 0, 0L))
+
+#define CommandBar_SetOwner(/*HWND*/ __hwndCB, /*HWND*/ __hwndNewOwner)\
+ ((BOOL)SENDMSG((__hwndCB), CBM_SETOWNER, 0, (LPARAM)(__hwndNewOwner)))
+
+#define CommandBar_GetData(/*HWND*/ __hwndCB)\
+ ((HWND)SENDMSG((__hwndCB), CBM_GETDATA, 0, 0L))
+
+#define CommandBar_SetData(/*HWND*/ __hwndCB, /*ULONG_PTR*/ __userData)\
+ ((BOOL)SENDMSG((__hwndCB), CBM_SETDATA, 0, (LPARAM)(__userData)))
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#endif // NULLOSFT_MEDIALIBRARY_COMMANDBAR_CONTROL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/config.cpp b/Src/Plugins/Library/ml_disc/config.cpp
new file mode 100644
index 00000000..71474145
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/config.cpp
@@ -0,0 +1,129 @@
+#include "main.h"
+#include "./config.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define DEFAULT_SECTION TEXT("gen_ml_config")
+
+#define STRCOMP_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+C_Config::~C_Config()
+{
+ if (m_inifile) CoTaskMemFree(m_inifile);
+}
+
+C_Config::C_Config(LPCTSTR pszIniFile)
+{
+ if (S_OK != SHStrDup(pszIniFile, &m_inifile))
+ m_inifile = NULL;
+}
+
+void C_Config::WriteIntEx(LPCTSTR pszSection, LPCTSTR pszKey, int nValue)
+{
+ TCHAR buf[32] = {0};
+ StringCchPrintf(buf, ARRAYSIZE(buf), TEXT("%d"), nValue);
+ WriteStringEx(pszSection, pszKey, buf);
+}
+
+int C_Config::ReadIntEx(LPCTSTR pszSection, LPCTSTR pszKey, int nDefault)
+{
+ return GetPrivateProfileInt(pszSection, pszKey, nDefault, m_inifile);
+}
+
+void C_Config::WriteStringEx(LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszValue)
+{
+ WritePrivateProfileString(pszSection, pszKey, pszValue, m_inifile);
+ }
+
+LPTSTR C_Config::ReadCbStringEx(LPTSTR pszBuffer, INT cbBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault)
+{
+ return ReadCchStringEx(pszBuffer, cbBuffer/sizeof(TCHAR), pszSection, pszKey, pszDefault);
+}
+
+LPTSTR C_Config::ReadCchStringEx(LPTSTR pszBuffer, INT cchBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault)
+{
+ const static TCHAR foobuf[] = TEXT("___________gen_ml_lameness___________");
+ pszBuffer[0] = TEXT('\0');
+ GetStringEx(pszSection, pszKey, foobuf, pszBuffer, cchBuffer);
+ pszBuffer[cchBuffer -1] = TEXT('\0');
+ if (CSTR_EQUAL == CompareString(STRCOMP_INVARIANT, 0, foobuf, -1, pszBuffer, -1))
+ {
+ if (S_OK != StringCchCopyEx(pszBuffer, cchBuffer, pszDefault, NULL, NULL, STRSAFE_IGNORE_NULLS))
+ return NULL;
+ }
+ return pszBuffer;
+}
+
+LPTSTR C_Config::ReadCchQuotedString(LPTSTR pszBuffer, INT cchBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault)
+{
+ LPTSTR p = ReadCchStringEx(pszBuffer, cchBuffer, pszSection, pszKey, pszDefault);
+ if (p) PathUnquoteSpaces(pszBuffer);
+ return p;
+}
+
+LPTSTR C_Config::ReadCbQuotedString(LPTSTR pszBuffer, INT cbBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault)
+{
+ return ReadCchQuotedString(pszBuffer, cbBuffer/sizeof(TCHAR), pszSection, pszKey, pszDefault);
+}
+
+void C_Config::WriteQuotedString(LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszValue)
+{
+ if (!pszValue) return;
+ INT cch = lstrlen(pszValue);
+ if (cch < MAX_PATH)
+ {
+ TCHAR szBuffer[MAX_PATH] = {0};
+ if (S_OK == StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), pszValue))
+ {
+ PathQuoteSpaces(szBuffer);
+ WriteStringEx(pszSection, pszKey, szBuffer);
+ return;
+ }
+ }
+ else
+ {
+ LPTSTR pszBuffer = (LPTSTR)calloc((cch + 4), sizeof(TCHAR));
+ if (pszBuffer)
+ {
+ if (S_OK == StringCchCopy(pszBuffer, cch + 4, pszValue))
+ {
+ PathQuoteSpaces(pszBuffer);
+ WriteStringEx(pszSection, pszKey, pszBuffer);
+ free(pszBuffer);
+ return;
+ }
+ free(pszBuffer);
+ }
+ }
+ WriteStringEx(pszSection, pszKey, pszValue);
+}
+
+DWORD C_Config::GetStringEx(LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault, LPTSTR pszReturnedValue, DWORD nSize)
+{
+ return GetPrivateProfileString(pszSection, pszKey, pszDefault, pszReturnedValue, nSize, m_inifile);
+}
+
+void C_Config::WriteInt(LPCTSTR pszKey, int nValue)
+{
+ WriteIntEx(DEFAULT_SECTION, pszKey, nValue);
+}
+
+int C_Config::ReadInt(LPCTSTR pszKey, int nDefault)
+{
+ return ReadIntEx(DEFAULT_SECTION, pszKey, nDefault);
+}
+
+void C_Config::WriteString(LPCTSTR pszKey, LPCTSTR pszValue)
+{
+ WriteStringEx(DEFAULT_SECTION, pszKey, pszValue);
+}
+
+LPCTSTR C_Config::ReadString(LPCTSTR pszKey, LPCTSTR pszDefault)
+{
+ static TCHAR szBuffer[4096];
+ return ReadCchStringEx(szBuffer, ARRAYSIZE(szBuffer), DEFAULT_SECTION, pszKey, pszDefault);
+}
+DWORD C_Config::GetString(LPCTSTR pszKey, LPCTSTR pszDefault, LPTSTR pszReturnedValue, DWORD nSize)
+{
+ return GetStringEx(DEFAULT_SECTION, pszKey, pszDefault, pszReturnedValue, nSize);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/config.h b/Src/Plugins/Library/ml_disc/config.h
new file mode 100644
index 00000000..6ec17685
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/config.h
@@ -0,0 +1,44 @@
+#ifndef _C_CONFIG_H_
+#define _C_CONFIG_H_
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#define C_CONFIG_WIN32NATIVE
+#include <windows.h>
+
+class C_Config
+{
+ public:
+ C_Config(LPCTSTR pszIniFile);
+ ~C_Config();
+
+ void WriteIntEx(LPCTSTR pszSection, LPCTSTR pszKey, int nValue);
+ void WriteStringEx(LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszValue);
+ int ReadIntEx(LPCTSTR pszSection, LPCTSTR pszKey, int nDefault);
+ LPTSTR ReadCchStringEx(LPTSTR pszBuffer, INT cchBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault);
+ LPTSTR ReadCbStringEx(LPTSTR pszBuffer, INT cbBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault);
+ DWORD GetStringEx(LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault, LPTSTR pszReturnedValue, DWORD nSize);
+
+ void WriteBoolEx(LPCTSTR pszSection, LPCTSTR pszKey, int nValue) { WriteIntEx(pszSection, pszKey, ( 0 != nValue)); }
+ BOOL ReadBoolEx(LPCTSTR pszSection, LPCTSTR pszKey, int nDefault) { return (0 != ReadIntEx(pszSection, pszKey, nDefault)); }
+
+ LPTSTR ReadCchQuotedString(LPTSTR pszBuffer, INT cchBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault);
+ LPTSTR ReadCbQuotedString(LPTSTR pszBuffer, INT cbBuffer, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszDefault);
+ void WriteQuotedString(LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszValue);
+
+ void WriteInt(LPCTSTR pszKey, int nValue);
+ void WriteString(LPCTSTR pszKey, LPCTSTR pszValue);
+ int ReadInt(LPCTSTR pszKey, int nDefault);
+ LPCTSTR ReadString(LPCTSTR pszKey, LPCTSTR pszDefault);
+ DWORD GetString(LPCTSTR pszKey, LPCTSTR pszDefault, LPTSTR pszReturnedValue, DWORD nSize);
+
+ void WriteBool(LPCTSTR pszKey, int nValue) { WriteInt(pszKey, ( 0 != nValue)); }
+ BOOL ReadBool(LPCTSTR pszKey, int nDefault) { return (0 != ReadInt(pszKey, nDefault)); }
+
+ private:
+ TCHAR *m_inifile;
+};
+
+#endif//_C_CONFIG_H_
diff --git a/Src/Plugins/Library/ml_disc/copyfiles.cpp b/Src/Plugins/Library/ml_disc/copyfiles.cpp
new file mode 100644
index 00000000..426bd79d
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/copyfiles.cpp
@@ -0,0 +1,613 @@
+#include "main.h"
+#include "./copyfiles.h"
+#include "./copyinternal.h"
+#include "./resource.h"
+#include "./settings.h"
+#include "../nu/trace.h"
+#include <api/service/waServiceFactory.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+
+static LONG szBusyDrive[26] = {0, };
+static CRITICAL_SECTION cs_copy = { 0,};
+
+static void NotifyDialog(COPYDATA *pcd, UINT uTask, UINT uOperation, LPARAM lParam)
+{
+ if(pcd->hDialog) PostMessage(pcd->hDialog, CFM_NOTIFY, MAKEWPARAM(uTask, uOperation), lParam);
+}
+
+static INT_PTR QueryDialog(COPYDATA *pcd, UINT uTask, UINT uOperation, LPARAM lParam)
+{
+ return (pcd->hDialog) ? SendMessage(pcd->hDialog, CFM_NOTIFY, MAKEWPARAM(uTask, uOperation), lParam) : FALSE;
+}
+
+static void CopyFiles_MarkDrivesBusy(LPCTSTR *ppsz, INT count, BOOL bBusy)
+{
+ INT i, n;
+ DWORD driveMask = 0x00;
+
+ EnterCriticalSection(&cs_copy);
+ if (bBusy)
+ {
+ for(i = 0; i < count; i++)
+ {
+ n = PathGetDriveNumber(ppsz[i]);
+ if (-1 != n)
+ {
+ if (0 == szBusyDrive[n]) driveMask |= (((DWORD)0x01) << n);
+ szBusyDrive[n]++;
+ }
+ }
+ }
+ else
+ {
+ for(i = 0; i < count; i++)
+ {
+ n = PathGetDriveNumber(ppsz[i]);
+ if (-1 != n)
+ {
+ if (szBusyDrive[n] <= 0) continue;
+ szBusyDrive[n]--;
+ if (0 == szBusyDrive[n]) driveMask |= (((DWORD)0x01) << n);
+ }
+ }
+ }
+ LeaveCriticalSection(&cs_copy);
+
+ if (0x00 != driveMask)
+ {
+ static UINT uMsgCopyNotify = 0;
+ if (!uMsgCopyNotify) uMsgCopyNotify = RegisterWindowMessageA("WACOPY_BROADCAST_MSG");
+ if (uMsgCopyNotify)
+ {
+ for (CHAR c = 'A'; 0x00 != driveMask; c++, driveMask>>=1)
+ {
+ if (0 == (0x01 & driveMask)) continue;
+ if (bBusy) SendNotifyMessage(HWND_BROADCAST, uMsgCopyNotify, MAKEWPARAM(c, 0), (LPARAM)TRUE);
+ else
+ {
+ SendNotifyMessage(HWND_BROADCAST, uMsgCopyNotify, MAKEWPARAM(c, 0), (LPARAM)FALSE);
+ SendNotifyMessage(HWND_BROADCAST, uMsgCopyNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ }
+ }
+ }
+ }
+}
+static BOOL ReadCopyParameters(COPYDATA *pcd)
+{
+ BOOL bVal;
+ HRESULT hr = S_OK;
+ pcd->uFlags = 0;
+ if (S_OK == hr) hr = Settings_ReadString(C_COPY, CF_PATH, pcd->szDestination, ARRAYSIZE(pcd->szDestination));
+ if (S_OK == hr) hr = Settings_ReadString(C_COPY, CF_TITLEFMT, pcd->szTitleFormat, ARRAYSIZE(pcd->szTitleFormat));
+ if (S_OK == hr && S_OK == (hr = Settings_GetBool(C_COPY, CF_ADDTOMLDB, &bVal)) && bVal) pcd->uFlags |= FCF_ADDTOMLDB;
+ if (S_OK == hr && S_OK == (hr = Settings_GetBool(C_COPY, CF_USETITLEFMT, &bVal)) && bVal) pcd->uFlags |= FCF_USETITLEFMT;
+ CleanupDirectoryString(pcd->szDestination);
+
+ return (S_OK == hr);
+
+}
+
+void MLDisc_InitializeCopyData()
+{
+ InitializeCriticalSection(&cs_copy);
+}
+
+void MLDisc_ReleaseCopyData()
+{
+ DeleteCriticalSection(&cs_copy);
+}
+
+BOOL MLDisc_IsDiscCopying(CHAR cLetter)
+{
+ if (cLetter >= 'a' && cLetter <= 'z') cLetter -= 0x20;
+ return (cLetter >= 'A' && cLetter <= 'Z' && szBusyDrive[cLetter - 'A'] > 0);
+}
+
+
+BOOL MLDisc_CopyFiles(HWND hParent, LPWSTR *ppszFiles, ULONGLONG *pFSizes, INT count)
+{
+ if (!ppszFiles || !count) return FALSE;
+ if (NULL == hParent) hParent = plugin.hwndLibraryParent;
+
+ COPYDATA *pcd = (COPYDATA*)CoTaskMemAlloc(sizeof(COPYDATA));
+ if (!pcd) return FALSE;
+ ZeroMemory(pcd, sizeof(COPYDATA));
+
+ CopyFiles_AddRef(pcd);
+
+ pcd->hOwner = hParent;
+ pcd->ppszFiles = ppszFiles;
+ pcd->pFSizes = pFSizes;
+ pcd->count = count;
+ pcd->bCancel = FALSE;
+
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(api_metadataGUID);
+ if (factory) pcd->pMetaReader = (api_metadata*) factory->getInterface();
+ factory = plugin.service->service_getServiceByGuid(mldbApiGuid);
+ if (factory) pcd->pMlDb = (api_mldb*) factory->getInterface();
+
+ HWND hRoot = GetAncestor(hParent, GA_ROOT);
+ if (NULL == hRoot) hRoot = hParent;
+
+ INT_PTR result = WASABI_API_DIALOGBOXPARAMW(IDD_FILECOPY_PREPARE, hRoot, CopyPrepare_DialogProc, (LPARAM)pcd);
+ if (IDCANCEL != result)
+ {
+ if (ReadCopyParameters(pcd))
+ {
+ HWND hCopy = WASABI_API_CREATEDIALOGPARAMW(IDD_FILECOPY_PROGRESS, hRoot, CopyProgress_DialogProc, (LPARAM)pcd);
+ if (hCopy)
+ {
+ pcd->hDialog = hCopy;
+ ShowWindow(hCopy, SW_SHOWNORMAL);
+ RedrawWindow(hCopy, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
+ }
+ CopyFiles_StartCopy(pcd);
+ }
+ }
+
+ CopyFiles_Release(pcd);
+ return TRUE;
+}
+
+BOOL CopyFiles_CancelCopy(COPYDATA *pcd)
+{
+ if (pcd) pcd->bCancel = TRUE;
+ return (NULL != pcd);
+}
+
+LONG CopyFiles_AddRef(COPYDATA *pcd)
+{
+ return (pcd) ? InterlockedIncrement(&pcd->ref) : 0;
+
+}
+LONG CopyFiles_Release(COPYDATA *pcd)
+{
+ if (pcd && pcd->ref > 0)
+ {
+ LONG r = InterlockedDecrement(&pcd->ref);
+ if ( 0 == r)
+ {
+ if (pcd->ppszFiles)
+ {
+ for (int i = 0; i < pcd->count; i++) CoTaskMemFree(pcd->ppszFiles[i]);
+ CoTaskMemFree(pcd->ppszFiles);
+ }
+ if (pcd->pFSizes) CoTaskMemFree(pcd->pFSizes);
+ if (pcd->hThread) CloseHandle(pcd->hThread);
+
+ if(pcd->pMetaReader)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(api_metadataGUID);
+ if (factory) factory->releaseInterface(pcd->pMetaReader);
+ }
+ if(pcd->pMlDb)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(mldbApiGuid);
+ if (factory) factory->releaseInterface(pcd->pMlDb);
+ }
+ CoTaskMemFree(pcd);
+ }
+ return r;
+ }
+ return 0;
+}
+
+static ULONGLONG CopyFiles_CalculateTotalSize(COPYDATA *pcd)
+{
+ ULONGLONG total = 0;
+ if (!pcd->pFSizes)
+ {
+ pcd->pFSizes = (ULONGLONG*)CoTaskMemAlloc(sizeof(ULONGLONG)*pcd->count);
+ if (!pcd->pFSizes)
+ {
+ pcd->errorCode = ERROR_NOT_ENOUGH_MEMORY;
+ return 0;
+ }
+ LARGE_INTEGER fs;
+ for(int i = 0; i < pcd->count; i++)
+ {
+ HANDLE hFile = CreateFile(pcd->ppszFiles[i], FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
+ if (INVALID_HANDLE_VALUE == hFile)
+ {
+ pcd->errorCode = GetLastError();
+ return 0;
+ }
+ if (!GetFileSizeEx(hFile, &fs)) { pcd->errorCode = GetLastError(); CloseHandle(hFile); return 0; }
+ CloseHandle(hFile);
+ pcd->pFSizes[i] = fs.QuadPart;
+ }
+ }
+ for(int i = 0; i < pcd->count; i++) total += pcd->pFSizes[i];
+ return total;
+}
+
+BOOL CopyFiles_CreateDirectory(LPCTSTR pszDirectory)
+{
+ DWORD ec = ERROR_SUCCESS;
+ if (!CreateDirectory(pszDirectory, NULL))
+ {
+ ec = GetLastError();
+ if (ERROR_PATH_NOT_FOUND == ec)
+ {
+ LPCTSTR pszBlock = pszDirectory;
+ TCHAR szBuffer[MAX_PATH] = {0};
+
+ LPCTSTR pszCursor = PathFindNextComponent(pszBlock);
+ ec = (pszCursor == pszBlock || S_OK != StringCchCopyN(szBuffer, ARRAYSIZE(szBuffer), pszBlock, (pszCursor - pszBlock))) ?
+ ERROR_INVALID_NAME : ERROR_SUCCESS;
+
+ pszBlock = pszCursor;
+
+ while (ERROR_SUCCESS == ec && NULL != (pszCursor = PathFindNextComponent(pszBlock)))
+ {
+ if (pszCursor == pszBlock || S_OK != StringCchCatN(szBuffer, ARRAYSIZE(szBuffer), pszBlock, (pszCursor - pszBlock)))
+ ec = ERROR_INVALID_NAME;
+
+ if (ERROR_SUCCESS == ec && !CreateDirectory(szBuffer, NULL))
+ {
+ ec = GetLastError();
+ if (ERROR_ALREADY_EXISTS == ec) ec = ERROR_SUCCESS;
+ }
+ pszBlock = pszCursor;
+ }
+ }
+
+ if (ERROR_ALREADY_EXISTS == ec) ec = ERROR_SUCCESS;
+ }
+ SetLastError(ec);
+ return (ERROR_SUCCESS == ec);
+}
+
+
+static BOOL CopyFiles_CheckDestination(COPYDATA *pcd, ULONGLONG needSize)
+{
+ TCHAR szRoot[MAX_PATH] = {0};
+ if (S_OK != StringCchCopy(szRoot, ARRAYSIZE(szRoot), pcd->szDestination))
+ {
+ pcd->errorCode = ERROR_OUTOFMEMORY;
+ return FALSE;
+ }
+ PathStripToRoot(szRoot);
+ ULARGE_INTEGER free, total;
+ if (!GetDiskFreeSpaceEx(szRoot, &free, &total, NULL))
+ {
+ pcd->errorCode = GetLastError();
+ return FALSE;
+ }
+ if (needSize > free.QuadPart)
+ {
+ pcd->errorCode = ERROR_DISK_FULL;
+ return FALSE;
+ }
+
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(pcd->szDestination))
+ {
+ DWORD ec = GetLastError();
+ if (ERROR_PATH_NOT_FOUND == ec || ERROR_FILE_NOT_FOUND == ec)
+ {
+ if (TRUE == QueryDialog(pcd, CFT_CONFLICT, CFO_DESTNOTEXIST, (LPARAM)pcd->szDestination))
+ pcd->errorCode = ERROR_REQUEST_ABORTED;
+ }
+ else pcd->errorCode = ec;
+
+ if (ERROR_SUCCESS != pcd->errorCode)
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_DIRECTORYCREATE_FAILED;
+ return FALSE;
+ }
+
+ }
+
+ if (!CopyFiles_CreateDirectory(pcd->szDestination))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_DIRECTORYCREATE_FAILED;
+ pcd->errorCode = ERROR_CANNOT_MAKE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL CopyFiles_FixCdAttributes(LPCTSTR pszFileName)
+{
+ DWORD attr = GetFileAttributes(pszFileName);
+ if (INVALID_FILE_ATTRIBUTES == attr) return FALSE;
+ return SetFileAttributes(pszFileName, (attr & ~FILE_ATTRIBUTE_READONLY) | FILE_ATTRIBUTE_ARCHIVE);
+}
+
+
+
+static BOOL CopyFiles_DeleteFile(LPCTSTR pszFileName, COPYDATA *pcd)
+{
+ DWORD attr = GetFileAttributes(pszFileName);
+ if (INVALID_FILE_ATTRIBUTES == attr) return FALSE;
+ if (FILE_ATTRIBUTE_READONLY & attr)
+ {
+ BOOL bReset = FALSE;
+ if (FCF_DELETEREADONLY & pcd->uFlags) bReset = TRUE;
+ else
+ {
+ INT_PTR r = QueryDialog(pcd, CFT_CONFLICT, CFO_READONLY, (LPARAM)pszFileName);
+ switch(r)
+ {
+ case READONLY_CANCELCOPY:
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_USERABORT;
+ SetLastError(ERROR_REQUEST_ABORTED);
+ return FALSE;
+ case READONLY_DELETEALL: pcd->uFlags |= FCF_DELETEREADONLY; // no break
+ case READONLY_DELETE: bReset = TRUE; break;
+ }
+ }
+
+ if (bReset)
+ {
+ if (!SetFileAttributes(pszFileName, (attr & ~FILE_ATTRIBUTE_READONLY)))
+ return FALSE;
+ }
+ }
+ return DeleteFile(pszFileName);
+}
+
+#define NOT_EXIST 0
+#define EXIST_AND_SKIP 1
+#define EXIST_AND_OVERWRITE 2
+#define EXIST_AND_CANCEL 3
+
+static DWORD CopyFiles_CheckIfExist(LPCTSTR pszFileNameDest, LPCTSTR pszFileNameSource, COPYDATA *pcd)
+{
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(pszFileNameDest))
+ {
+ return NOT_EXIST;
+ }
+
+
+ if (FCF_SKIPFILE & pcd->uFlags) return EXIST_AND_SKIP;
+ if (FCF_OVERWRITEFILE & pcd->uFlags) return EXIST_AND_OVERWRITE;
+
+ FILECONFLICT conflict;
+ ZeroMemory(&conflict, sizeof(FILECONFLICT));
+
+ pcd->uFlags &= ~(FCF_SKIPFILE | FCF_OVERWRITEFILE);
+
+ conflict.pszNameExisting = pszFileNameDest;
+ conflict.pszNameNew = pszFileNameSource;
+
+ INT_PTR r = QueryDialog(pcd, CFT_CONFLICT, CFO_FILEALREDYEXIST, (LPARAM)&conflict);
+
+ switch(0xFF & r)
+ {
+ case EXISTFILE_CANCELCOPY: return EXIST_AND_CANCEL;
+ case EXISTFILE_SKIP:
+ if (EXISTFILE_APPLY_TO_ALL & r) pcd->uFlags |= FCF_SKIPFILE;
+ return EXIST_AND_SKIP;
+ case EXISTFILE_OVERWRITE:
+ if (EXISTFILE_APPLY_TO_ALL & r) pcd->uFlags |= FCF_OVERWRITEFILE;
+ return EXIST_AND_OVERWRITE;
+ }
+
+ return NOT_EXIST;
+}
+
+typedef struct _COPYPROGRESS
+{
+ ULONGLONG total;
+ ULONGLONG completed;
+ HWND hDialog;
+ INT percent;
+} COPYPROGRESS;
+
+static DWORD CALLBACK CopyFiles_ProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize,
+ LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason,
+ HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData)
+{
+ COPYPROGRESS *pProgress = (COPYPROGRESS*)lpData;
+ if (pProgress)
+ {
+ switch(dwCallbackReason)
+ {
+ case CALLBACK_STREAM_SWITCH:
+ pProgress->completed += TotalFileSize.QuadPart;
+ break;
+
+ }
+ INT p = (INT)((pProgress->completed - TotalFileSize.QuadPart + TotalBytesTransferred.QuadPart) * 100 / pProgress->total);
+ if (p != pProgress->percent)
+ {
+ pProgress->percent = p;
+ if (pProgress->hDialog)
+ PostMessage(pProgress->hDialog, CFM_NOTIFY, MAKEWPARAM(CFT_COPYING, CFO_PROGRESS), p);
+ }
+ }
+ return PROGRESS_CONTINUE;
+};
+
+static DWORD WINAPI CopyFiles_ThreadProc(LPVOID param)
+{
+
+ COPYDATA *pcd = (COPYDATA*)param;
+ if (!pcd) return 1;
+
+ CopyFiles_MarkDrivesBusy((LPCTSTR*)pcd->ppszFiles, pcd->count, TRUE);
+
+ ULONGLONG needSize;
+ NotifyDialog(pcd, CFT_INITIALIZING, CFO_INIT, 0L);
+ NotifyDialog(pcd, CFT_INITIALIZING, CFO_CACLSIZE, 0L);
+ needSize = CopyFiles_CalculateTotalSize(pcd);
+ BOOL bSuccess = (ERROR_SUCCESS == pcd->errorCode);
+ if (bSuccess)
+ {
+ NotifyDialog(pcd, CFT_INITIALIZING, CFO_CHECKDESTINATION, 0L);
+ bSuccess = CopyFiles_CheckDestination(pcd, needSize);
+ }
+
+ if (!bSuccess)
+ {
+ if (0 == pcd->errorMsgId)
+ pcd->errorMsgId = IDS_COPY_ERRMSG_INITIALIZATION_FAILED;
+ NotifyDialog(pcd, CFT_FINISHED, CFO_FAILED, pcd->errorCode);
+ CopyFiles_MarkDrivesBusy((LPCTSTR*)pcd->ppszFiles, pcd->count, FALSE);
+ CopyFiles_Release(pcd);
+ return 0;
+ }
+
+ COPYPROGRESS progress;
+ ZeroMemory(&progress, sizeof(COPYPROGRESS));
+ progress.total = needSize;
+ progress.hDialog = pcd->hDialog;
+
+ NotifyDialog(pcd, CFT_COPYING, CFO_INIT, 0L);
+
+ TCHAR szFile[MAX_PATH] = {0};
+
+ for (int i = 0; i < pcd->count && ERROR_SUCCESS == pcd->errorCode; i++)
+ {
+ if (pcd->bCancel)
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_USERABORT;
+ pcd->errorCode = ERROR_REQUEST_ABORTED;
+ }
+
+ LPCTSTR pszOrigFileName = PathFindFileName(pcd->ppszFiles[i]);
+
+ NotifyDialog(pcd, CFT_COPYING, CFO_NEXTFILE, MAKELPARAM(i, pcd->count));
+
+ if (NULL == PathCombine(szFile, pcd->szDestination, pszOrigFileName))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_FAILED;
+ pcd->errorCode = ERROR_BAD_PATHNAME;
+ }
+
+ DWORD r = CopyFiles_CheckIfExist(szFile, pcd->ppszFiles[i], pcd);
+ switch(r)
+ {
+ case EXIST_AND_SKIP:
+ continue;
+ break;
+ case EXIST_AND_OVERWRITE:
+ if (!CopyFiles_DeleteFile(szFile, pcd))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_DELETEFILE_FAILED;
+ pcd->errorCode = GetLastError();
+ }
+ break;
+ case EXIST_AND_CANCEL:
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_USERABORT;
+ pcd->errorCode = ERROR_REQUEST_ABORTED;
+ break;
+ }
+
+ // copy
+ if (ERROR_SUCCESS == pcd->errorCode && !CopyFileEx(pcd->ppszFiles[i], szFile, CopyFiles_ProgressRoutine, &progress, &pcd->bCancel, 0))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_FAILED;
+ pcd->errorCode = GetLastError();
+ }
+
+ if (pcd->bCancel)
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_USERABORT;
+ pcd->errorCode = ERROR_REQUEST_ABORTED;
+ }
+
+ if (ERROR_SUCCESS == pcd->errorCode) // post copy
+ {
+ // fix attributes
+ if (ERROR_SUCCESS == pcd->errorCode && !CopyFiles_FixCdAttributes(szFile))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_SETATTRIBUTES_FAILED;
+ pcd->errorCode = GetLastError();
+ }
+
+ // format title & rename
+ if (ERROR_SUCCESS == pcd->errorCode && (FCF_USETITLEFMT & pcd->uFlags) && pcd->pMetaReader)
+ {
+ TCHAR szBuffer[MAX_PATH] = {0};
+ if (!CopyFiles_FormatFileName(szBuffer, ARRAYSIZE(szBuffer), szFile, pszOrigFileName,
+ pcd->szDestination, pcd->szTitleFormat, pcd->pMetaReader))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_TITLEFORMAT_FAILED;
+ pcd->errorCode = GetLastError();
+ }
+
+ if (pcd->bCancel)
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_USERABORT;
+ pcd->errorCode = ERROR_REQUEST_ABORTED;
+ }
+
+ if (ERROR_SUCCESS == pcd->errorCode &&
+ CSTR_EQUAL != CompareString(STRCOMP_INVARIANT, 0, szBuffer, -1, szFile, -1))
+ {
+
+ DWORD r = CopyFiles_CheckIfExist(szBuffer, szFile, pcd);
+ switch(r)
+ {
+ case EXIST_AND_SKIP:
+ CopyFiles_DeleteFile(szFile, pcd);
+ continue;
+ break;
+ case EXIST_AND_OVERWRITE:
+ if (!CopyFiles_DeleteFile(szBuffer, pcd))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_DELETEFILE_FAILED;
+ pcd->errorCode = GetLastError();
+ }
+ break;
+ case EXIST_AND_CANCEL:
+ pcd->errorMsgId = IDS_COPY_ERRMSG_COPYFILE_USERABORT;
+ pcd->errorCode = ERROR_REQUEST_ABORTED;
+ break;
+ }
+
+ if (ERROR_SUCCESS == pcd->errorCode)
+ {
+ if (!MoveFileEx(szFile, szBuffer, MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_TITLEFORMAT_FAILED;
+ pcd->errorCode = GetLastError();
+ }
+ else StringCchCopy(szFile, ARRAYSIZE(szFile), szBuffer);
+ }
+
+ }
+ }
+
+
+ if (ERROR_SUCCESS != pcd->errorCode)
+ {
+ CopyFiles_DeleteFile(szFile, pcd);
+ }
+ else if ((FCF_ADDTOMLDB & pcd->uFlags) && pcd->pMlDb)
+ {
+ if (0 == pcd->pMlDb->AddFile(szFile))
+ {
+ pcd->errorMsgId = IDS_COPY_ERRMSG_ADDTOMLDB_FAILED;
+ pcd->errorCode = ERROR_FILE_NOT_FOUND;
+ }
+ }
+ }
+
+ }
+
+ NotifyDialog(pcd, CFT_FINISHED, (ERROR_SUCCESS == pcd->errorCode) ? CFO_SUCCESS : CFO_FAILED, pcd->errorCode);
+ if ((FCF_ADDTOMLDB & pcd->uFlags) && pcd->pMlDb) pcd->pMlDb->Sync();
+ CopyFiles_MarkDrivesBusy((LPCTSTR*)pcd->ppszFiles, pcd->count, FALSE);
+ CopyFiles_Release(pcd);
+ return 0;
+}
+
+BOOL CopyFiles_StartCopy(COPYDATA *pcd)
+{
+ DWORD threadId;
+
+ CopyFiles_AddRef(pcd);
+
+ pcd->hThread = CreateThread(NULL, 0, CopyFiles_ThreadProc, pcd, 0, &threadId);
+ if (pcd->hThread) return TRUE;
+
+
+ pcd->errorCode = GetLastError();
+ NotifyDialog(pcd, CFT_FINISHED, CFO_FAILED, pcd->errorCode);
+ CopyFiles_Release(pcd);
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/copyfiles.h b/Src/Plugins/Library/ml_disc/copyfiles.h
new file mode 100644
index 00000000..bd762f53
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/copyfiles.h
@@ -0,0 +1,27 @@
+#ifndef NULLOSFT_MEDIALIBRARY_MLDISC_COPYFILES_HEADER
+#define NULLOSFT_MEDIALIBRARY_MLDISC_COPYFILES_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void MLDisc_InitializeCopyData();
+void MLDisc_ReleaseCopyData();
+
+// use CoTaskMemAlloc/CoTackMemFree to allocate buffers and each string. pszFSize can be NULL. if return TRUE do not free data.
+BOOL MLDisc_CopyFiles(HWND hParent, LPWSTR *ppszFiles, ULONGLONG *pFSizes, INT count);
+BOOL MLDisc_IsDiscCopying(CHAR cLetter);
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#endif // NULLOSFT_MEDIALIBRARY_MLDISC_COPYFILES_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/copyfiles_post.cpp b/Src/Plugins/Library/ml_disc/copyfiles_post.cpp
new file mode 100644
index 00000000..44893398
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/copyfiles_post.cpp
@@ -0,0 +1,138 @@
+#include "main.h"
+#include "./copyfiles.h"
+#include "./copyinternal.h"
+#include "./resource.h"
+#include "../nu/trace.h"
+#include <api/service/waServiceFactory.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+
+typedef struct _FILEMETA
+{
+ LPWSTR pszArtist;
+ LPWSTR pszAlbum;
+ LPWSTR pszTitle;
+ LPWSTR pszGenre;
+ LPWSTR pszAlbumArtist;
+ INT nYear;
+ INT nTrackNum;
+ INT nTrackCount;
+ LPWSTR pszDisc;
+} FILEMETA;
+
+// sets part and parts to -1 or 0 on fail/missing (e.g. parts will be -1 on "1", but 0 on "1/")
+static void ParseIntSlashInt(wchar_t *string, int *part, int *parts)
+{
+ *part = -1;
+ *parts = -1;
+
+ if (string && string[0])
+ {
+ *part = _wtoi(string);
+ while (string && *string && *string != '/')
+ {
+ string++;
+ }
+ if (*string == '/')
+ {
+ string++;
+ *parts = _wtoi(string);
+ }
+ }
+}
+
+#define READFILEINFO(__fileName, __tag, __result, __pszBuffer, __cchBuffer)\
+ (pReader->GetExtendedFileInfo((__fileName), (__tag), (__pszBuffer), (__cchBuffer)) && L'\0' != *(__pszBuffer))
+
+
+static void CopyFiles_ReadFileMeta(FILEMETA *pMeta, api_metadata *pReader, LPCWSTR pszFileName)
+{
+ WCHAR szBuffer[2048] = {0};
+
+ #define GETFILEINFO_STR(__tag, __result, __resId)\
+ { szBuffer[0] = L'\0';\
+ READFILEINFO(pszFileName, __tag, __result, szBuffer, ARRAYSIZE(szBuffer));\
+ if (TEXT('\0') == *szBuffer)\
+ { if (IS_INTRESOURCE(__resId)) WASABI_API_LNGSTRINGW_BUF(((UINT)(UINT_PTR)(__resId)), szBuffer, ARRAYSIZE(szBuffer));\
+ else StringCchCopyEx(szBuffer, ARRAYSIZE(szBuffer), (__resId), NULL, NULL, STRSAFE_IGNORE_NULLS);\
+ }\
+ (__result) = _wcsdup(szBuffer); }
+ #define GETFILEINFO_INT(__tag, __result) { szBuffer[0] = L'\0';\
+ if (READFILEINFO(pszFileName, __tag, __result, szBuffer, ARRAYSIZE(szBuffer)))\
+ {(__result) = _wtoi(szBuffer); }}
+ #define GETFILEINFO_INTINT(__tag, __result1, __result2) { szBuffer[0] = L'\0';\
+ if (READFILEINFO(pszFileName, __tag, __result, szBuffer, ARRAYSIZE(szBuffer)))\
+ {ParseIntSlashInt(szBuffer, (__result1), (__result2)); }}
+
+ if (!pMeta) return;
+ ZeroMemory(pMeta, sizeof(FILEMETA));
+
+#pragma warning(push)
+#pragma warning(disable : 4127)
+
+ GETFILEINFO_STR(L"artist", pMeta->pszArtist, MAKEINTRESOURCE(IDS_UNKNOWN_ARTIST));
+ GETFILEINFO_STR(L"album", pMeta->pszAlbum, MAKEINTRESOURCE(IDS_UNKNOWN_ALBUM));
+ GETFILEINFO_STR(L"title", pMeta->pszTitle, MAKEINTRESOURCE(IDS_UNKNOWN));
+ GETFILEINFO_STR(L"albumartist", pMeta->pszAlbumArtist, pMeta->pszArtist);
+ GETFILEINFO_STR(L"genre", pMeta->pszGenre, MAKEINTRESOURCE(IDS_UNKNOWN_GENRE));
+ GETFILEINFO_INT(L"year", pMeta->nYear);
+ GETFILEINFO_INTINT(L"track", &pMeta->nTrackNum, &pMeta->nTrackCount);
+ GETFILEINFO_STR(L"disc", pMeta->pszDisc, MAKEINTRESOURCE(IDS_UNKNOWN));
+#pragma warning(pop)
+}
+
+static void CopyFiles_ReleaseFileMeta(FILEMETA *pMeta)
+{
+ if (pMeta->pszArtist) free(pMeta->pszArtist);
+ if (pMeta->pszAlbum) free(pMeta->pszAlbum);
+ if (pMeta->pszTitle) free(pMeta->pszTitle);
+ if (pMeta->pszGenre) free(pMeta->pszGenre);
+ if (pMeta->pszDisc) free(pMeta->pszDisc);
+ if (pMeta->pszAlbumArtist) free(pMeta->pszAlbumArtist);
+ ZeroMemory(pMeta, sizeof(FILEMETA));
+}
+
+static BOOL CopyFiles_GetFormattedName(LPTSTR pszBuffer, INT cchBufferMax, LPCTSTR pszFileToFormat, LPCTSTR pszOrigFileName, LPCTSTR pszFormat, api_metadata *pMetaReader)
+{
+ HRESULT hr;
+ FILEMETA meta;
+ CopyFiles_ReadFileMeta(&meta, pMetaReader, pszFileToFormat);
+
+ WCHAR szYear[16] = {0};
+ StringCchPrintf(szYear, ARRAYSIZE(szYear), TEXT("%d"), meta.nYear);
+
+ pszBuffer[0] = TEXT('\0');
+ hr = FormatFileName(pszBuffer, cchBufferMax, pszFormat,
+ meta.nTrackNum, meta.pszAlbumArtist,
+ meta.pszAlbum, meta.pszTitle,
+ meta.pszGenre, szYear, meta.pszArtist,
+ pszOrigFileName, meta.pszDisc);
+
+ CopyFiles_ReleaseFileMeta(&meta);
+
+ if (S_OK != hr)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL CopyFiles_FormatFileName(LPTSTR pszNewFileName, INT cchBufferMax, LPCTSTR pszFileToRename, LPCTSTR pszOrigFileName, LPCTSTR pszDestination, LPCTSTR pszFormat, api_metadata *pMetaReader)
+{
+ StringCchCopy(pszNewFileName, cchBufferMax, pszDestination);
+ INT l = lstrlen(pszNewFileName);
+ if (l) { pszNewFileName[l] = TEXT('\\'); pszNewFileName[l + 1] = TEXT('\0'); l++; }
+ if (!CopyFiles_GetFormattedName(pszNewFileName + l, cchBufferMax - l, pszFileToRename, pszOrigFileName, pszFormat, pMetaReader))
+ return FALSE;
+
+ LPTSTR p = PathFindFileName(pszNewFileName);
+ if (p && p > pszNewFileName)
+ {
+ *(p - 1) = TEXT('\0');
+ if (!CopyFiles_CreateDirectory(pszNewFileName)) return FALSE;
+ *(p - 1) = TEXT('\\');
+ }
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/copyinternal.h b/Src/Plugins/Library/ml_disc/copyinternal.h
new file mode 100644
index 00000000..b59be7a6
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/copyinternal.h
@@ -0,0 +1,125 @@
+#ifndef NULLOSFT_MEDIALIBRARY_MLDISC_COPYFILES_INTERNAL_HEADER
+#define NULLOSFT_MEDIALIBRARY_MLDISC_COPYFILES_INTERNAL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+#include "../Agave/Metadata/api_metadata.h"
+#include "../ml_local/api_mldb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define STRCOMP_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#define CPM_UPDATEDISKSIZE (WM_APP + 2)
+
+
+typedef struct _COPYDATA
+{
+ LONG ref;
+ HWND hDialog;
+ HWND hOwner;
+ HANDLE hThread;
+ BOOL bCancel;
+ DWORD errorCode;
+ UINT errorMsgId;
+ LPWSTR *ppszFiles;
+ ULONGLONG *pFSizes;
+ INT count;
+ UINT uFlags;
+ api_metadata *pMetaReader;
+ api_mldb *pMlDb;
+ WCHAR szDestination[MAX_PATH];
+ WCHAR szTitleFormat[128];
+} COPYDATA;
+
+#define FCF_ADDTOMLDB 0x00000002L
+#define FCF_USETITLEFMT 0x00000004L
+#define FCF_SKIPFILE 0x00010000L
+#define FCF_OVERWRITEFILE 0x00020000L
+#define FCF_DELETEREADONLY 0x00040000L
+
+
+
+INT_PTR CALLBACK CopyPrepare_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK CopyProgress_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+LONG CopyFiles_AddRef(COPYDATA *pcd);
+LONG CopyFiles_Release(COPYDATA *pcd);
+BOOL CopyFiles_StartCopy(COPYDATA *pcd);
+BOOL CopyFiles_CancelCopy(COPYDATA *pcd);
+
+BOOL CopyFiles_CreateDirectory(LPCTSTR pszDirectory);
+BOOL CopyFiles_FormatFileName(LPTSTR pszNewFileName, INT cchBufferMax, LPCTSTR pszFileToRename, LPCTSTR pszOrigFileName, LPCTSTR pszDestination, LPCTSTR pszFormat, api_metadata *pMetaReader);
+
+HBITMAP CopyFiles_LoadResourcePng(LPCTSTR pszResource);
+
+#define CFM_NOTIFY (WM_APP + 3)
+
+// notify task
+#define CFT_INITIALIZING 0x0001
+#define CFT_COPYING 0x0002
+#define CFT_FINISHED 0x0003
+#define CFT_CONFLICT 0x0004 // conflicts always use SendMessage
+
+
+// init task operations code
+#define CFO_INIT 0x0000
+#define CFO_CACLSIZE 0x0001
+#define CFO_CHECKDESTINATION 0x0002
+
+
+// copy task operations code
+#define CFO_INIT 0x0000 // time to set tast text
+#define CFO_NEXTFILE 0x0001 // lParam - MAKELPARAM(file index, total count)
+#define CFO_PROGRESS 0x0002 // lParam - percent
+#define CFO_POSTCOPY 0x0003
+
+
+// conflicts
+
+#define EXISTFILE_CANCELCOPY 0x0001 // almost like return FALSE but will not produce error
+#define EXISTFILE_SKIP 0x0002 // skip
+#define EXISTFILE_OVERWRITE 0x0003 // overwrite
+#define EXISTFILE_APPLY_ONCE 0x0000 // apply only once
+#define EXISTFILE_APPLY_TO_ALL 0x0100 // apply to all files with the same conflict
+
+#define READONLY_CANCELCOPY 0x0001
+#define READONLY_DELETE 0x0002
+#define READONLY_DELETEALL 0x0003
+
+typedef struct _FILECONFLICT
+{
+ LPCTSTR pszNameExisting;
+ LPCTSTR pszNameNew;
+} FILECONFLICT;
+
+
+#define CFO_DESTNOTEXIST 0x0000 // return FALSE to create destination or TRUE to cancel copy operation. param -pszDestionation
+#define CFO_FILEALREDYEXIST 0x0001 // return FALSE to fail with access denied, or EXISTFILE_XXX, param = (FILECONFLICT*)
+#define CFO_READONLY 0x0002 // return FALSE to fail, or RADONLY_XXX, param = (LPCTSTR)pszFileName
+
+
+
+
+
+
+// finished task operations code
+#define CFO_FAILED 0x0001
+#define CFO_SUCCESS 0x0002
+#define CFO_CANCELLED 0x0003
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#endif // NULLOSFT_MEDIALIBRARY_MLDISC_COPYFILES_INTERNAL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/copyprep.cpp b/Src/Plugins/Library/ml_disc/copyprep.cpp
new file mode 100644
index 00000000..7425ef39
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/copyprep.cpp
@@ -0,0 +1,426 @@
+#include "main.h"
+#include "./copyfiles.h"
+#include "./copyinternal.h"
+#include "./resource.h"
+#include "./settings.h"
+#include "../nu/trace.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+typedef struct _PREPDLG
+{
+ HFONT hfItalic;
+ HWND hActiveHelp;
+ HBITMAP hbmpLogo;
+ IAutoComplete *pac;
+ IACList2 *pacl2;
+ COPYDATA *pCopyData;
+ TCHAR szCurrentRoot[MAX_PATH];
+} PREPDLG;
+
+typedef struct _CALCDISKSIZE
+{
+ HWND hCallback;
+ DWORD dwError;
+ ULARGE_INTEGER bytesFree;
+ ULARGE_INTEGER bytesTotal;
+ TCHAR szRoot[MAX_PATH];
+} CALCDISKSIZE;
+
+#define PREPDLG_PROP TEXT("PREPDLG")
+#define GetPrepDlg(__hdlg) ((PREPDLG*)GetProp((__hdlg), PREPDLG_PROP))
+
+#define TID_UPDATEDISKSIZE 1985
+#define DELAY_UPDATEDISKSIZE 100
+
+static void DisplayFormatExample(HWND hdlg, INT nItemId)
+{
+ TCHAR szBuffer[MAX_PATH*2], szFormat[MAX_PATH] = {0};
+
+ Settings_ReadString(C_COPY, CF_TITLEFMT, szFormat, ARRAYSIZE(szFormat));
+ szBuffer[0] = TEXT('\0');
+ FormatFileName(szBuffer, ARRAYSIZE(szBuffer), szFormat, 10,
+ TEXT("U2"),
+ TEXT("The Joshua Tree"),
+ TEXT("Exit"),
+ TEXT("Rock"),
+ TEXT("1987"),
+ TEXT("U2"),
+ TEXT("u2_The_Joshua_Tree.Mp3"),
+ TEXT(""));
+ SetDlgItemText(hdlg, nItemId, szBuffer);
+}
+
+static DWORD WINAPI DiskFreeSpace_ThreadProc(LPVOID param)
+{
+ CALCDISKSIZE *pcs = (CALCDISKSIZE*)param;
+ if (!pcs) return 0;
+ pcs->dwError = 0;
+ SetLastError(0);
+ if (!GetDiskFreeSpaceEx(pcs->szRoot, &pcs->bytesFree, &pcs->bytesTotal, NULL))
+ pcs->dwError = GetLastError();
+ PostMessage(pcs->hCallback, CPM_UPDATEDISKSIZE, 0, (LPARAM)pcs);
+ return 0;
+}
+
+static void CopyPrepare_UpdateMessage(HWND hdlg)
+{
+ TCHAR szBuffer[MAX_PATH*2] = {0};
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+
+ szBuffer[0] = TEXT('\0');
+ if (ppd && ppd->pCopyData)
+ {
+ TCHAR szPath[MAX_PATH] = {0}, szFormat[256] = {0};
+ if (S_OK != Settings_ReadString(C_COPY, CF_PATH, szPath, ARRAYSIZE(szPath))) *szPath = TEXT('\0');
+ else CleanupDirectoryString(szPath);
+
+ if (1 == ppd->pCopyData->count)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPY_PREP_MESSAGE_SINGLE_FILE, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), szFormat, PathFindFileName(ppd->pCopyData->ppszFiles[0]), szPath);
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPY_PREP_MESSAGE_MULTIPLE_FILES, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), szFormat, ppd->pCopyData->count, szPath);
+ }
+ }
+ SetDlgItemText(hdlg, IDC_LBL_MESSAGE, szBuffer);
+}
+
+HBITMAP CopyFiles_LoadResourcePng(LPCTSTR pszResource)
+{
+ HBITMAP hbmp;
+ MLIMAGESOURCE src = { sizeof(MLIMAGESOURCE), };
+ src.lpszName = pszResource;
+ src.type = SRC_TYPE_PNG;
+ src.flags = 0;
+
+ src.hInst = WASABI_API_LNG_HINST;
+ hbmp = MLImageLoader_LoadDib(plugin.hwndLibraryParent, &src);
+ if(!hbmp)
+ {
+ src.hInst = WASABI_API_ORIG_HINST;
+ hbmp = MLImageLoader_LoadDib(plugin.hwndLibraryParent, &src);
+ }
+
+ DIBSECTION dibsec;
+
+ if (hbmp && sizeof(DIBSECTION) == GetObjectW(hbmp, sizeof(DIBSECTION), &dibsec) &&
+ BI_RGB == dibsec.dsBmih.biCompression && 1 == dibsec.dsBmih.biPlanes && 32 == dibsec.dsBm.bmBitsPixel)
+ {
+ MLIMAGEFILTERAPPLYEX filter = { sizeof(MLIMAGEFILTERAPPLYEX), };
+ filter.filterUID = MLIF_BLENDONBK_UID;
+ filter.cx = dibsec.dsBm.bmWidth;
+ filter.cy = dibsec.dsBm.bmHeight;
+ filter.bpp = dibsec.dsBm.bmBitsPixel;
+ filter.pData = (LPBYTE)dibsec.dsBm.bmBits;
+ filter.rgbBk = GetSysColor(COLOR_3DFACE);
+ MLImageFilter_ApplyEx(plugin.hwndLibraryParent, &filter);
+ }
+ return hbmp;
+}
+
+static INT_PTR CopyPrepare_OnInitDialog(HWND hdlg, HWND hFocus, LPARAM lParam)
+{
+ HWND hctrl;
+ PREPDLG *ppd = (PREPDLG*)calloc(1, sizeof(PREPDLG));
+ if (!ppd) return 0;
+
+ SetProp(hdlg, PREPDLG_PROP, ppd);
+ ppd->pCopyData = (COPYDATA*)lParam;
+
+ hctrl = GetDlgItem(hdlg, IDOK);
+ if (hctrl) SendMessageW(hdlg, WM_NEXTDLGCTL, (WPARAM)hctrl, (LPARAM)TRUE);
+ SendMessageW(hdlg, WM_COMMAND, MAKEWPARAM(IDC_BTN_OPTIONS, BN_CLICKED), (LPARAM)GetDlgItem(hdlg, IDC_BTN_OPTIONS));
+
+ hctrl = GetDlgItem(hdlg, IDC_LBL_EXAMPLE);
+ if (hctrl)
+ {
+ LOGFONT lf;
+ HFONT hf = (HFONT)SendMessage(hctrl, WM_GETFONT, 0, 0L);
+ if (NULL == hf) hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ if (GetObject(hf, sizeof(LOGFONT), &lf))
+ {
+ lf.lfItalic = TRUE;
+ ppd->hfItalic = CreateFontIndirect(&lf);
+ if (ppd->hfItalic)
+ {
+ UINT szIdList[] = { IDC_LBL_EXAMPLE_TITLE, IDC_LBL_EXAMPLE, IDC_LBL_FREE_TITLE, IDC_LBL_FREE, IDC_LBL_REQUIRED_TITLE, IDC_LBL_REQUIRED, };
+ for (int i = 0; i < sizeof(szIdList)/sizeof(szIdList[0]); i++) SendDlgItemMessage(hdlg, szIdList[i], WM_SETFONT, (WPARAM)ppd->hfItalic, FALSE);
+ }
+ }
+ }
+
+ if (ppd->pCopyData && ppd->pCopyData->pFSizes)
+ {
+ TCHAR szBuffer[128] = {0};
+ ULONGLONG total = 0;
+ for(int i = 0; i < ppd->pCopyData->count; i++) total += ppd->pCopyData->pFSizes[i];
+ StrFormatByteSize64(total, szBuffer, ARRAYSIZE(szBuffer));
+ SetDlgItemText(hdlg, IDC_LBL_REQUIRED, szBuffer);
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hdlg, IDC_LBL_REQUIRED_TITLE), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, IDC_LBL_REQUIRED), SW_HIDE);
+ }
+
+ if (ppd->pCopyData && ppd->pCopyData->hOwner)
+ {
+ RECT rw;
+ if (!GetWindowRect(ppd->pCopyData->hOwner, &rw)) SetRect(&rw, 0, 0, 0, 0);
+ if (hdlg && rw.left != rw.right)
+ {
+ RECT rw2;
+ GetWindowRect(hdlg, &rw2);
+ SetWindowPos(hdlg, HWND_TOP,
+ rw.left + ((rw.right - rw.left) - (rw2.right - rw2.left))/2,
+ rw.top + ((rw.bottom - rw.top) - (rw2.bottom - rw2.top))/2,
+ 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ }
+ }
+
+ CopyPrepare_UpdateMessage(hdlg);
+
+ SendMessage(hdlg, DM_REPOSITION, 0, 0L);
+
+ ppd->hbmpLogo = CopyFiles_LoadResourcePng(MAKEINTRESOURCE(IDB_FILECOPY));
+ if (ppd->hbmpLogo) SendDlgItemMessage(hdlg, IDC_PIC_LOGO, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)ppd->hbmpLogo);
+ else ShowWindow(GetDlgItem(hdlg, IDC_PIC_LOGO), SW_HIDE);
+
+ return FALSE;
+}
+
+static void CopyPrepare_OnDestroy(HWND hdlg)
+{
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+ RemoveProp(hdlg, PREPDLG_PROP);
+ if (ppd)
+ {
+ if (ppd->hActiveHelp) DestroyWindow(ppd->hActiveHelp);
+ if (ppd->hfItalic) DeleteObject(ppd->hfItalic);
+ if (ppd->pacl2) ppd->pacl2->Release();
+ if (ppd->pac) ppd->pac->Release();
+
+ if (ppd->hbmpLogo)
+ {
+ HBITMAP hbmp = (HBITMAP)SendDlgItemMessage(hdlg, IDC_PIC_LOGO, STM_GETIMAGE, IMAGE_BITMAP, 0L);
+ if (hbmp != ppd->hbmpLogo) DeleteObject(hbmp);
+ DeleteObject(ppd->hbmpLogo);
+ }
+ free(ppd);
+ }
+}
+static void CopyPrepare_OnParentNotify(HWND hdlg, UINT uMsg, LPARAM lParam)
+{
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+ if (ppd && WM_DESTROY == uMsg && ppd->hActiveHelp && ppd->hActiveHelp == (HWND)lParam)
+ ppd->hActiveHelp = NULL;
+}
+
+static void CALLBACK CopyPrepare_OnUpdateDiskSizeTimer(HWND hdlg, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ KillTimer(hdlg, idEvent);
+
+ CopyPrepare_UpdateMessage(hdlg);
+
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+ if (!ppd) return;
+ CALCDISKSIZE *pcs = (CALCDISKSIZE*)calloc(1, sizeof(CALCDISKSIZE));
+ if (!pcs) return;
+
+ pcs->hCallback = hdlg;
+
+ if (S_OK == Settings_ReadString(C_COPY, CF_PATH, pcs->szRoot, ARRAYSIZE(pcs->szRoot)))
+ {
+ PathStripToRoot(pcs->szRoot);
+
+ if (TEXT('\0') != *pcs->szRoot &&
+ (TEXT('\0') == *ppd->szCurrentRoot ||
+ CSTR_EQUAL != CompareString(STRCOMP_INVARIANT, NORM_IGNORECASE, ppd->szCurrentRoot, -1, pcs->szRoot, -1)))
+ {
+ DWORD threadId;
+ SetDlgItemText(hdlg, IDC_LBL_FREE, WASABI_API_LNGSTRINGW(IDS_CALCULATING));
+ HANDLE ht = CreateThread(NULL, 0, DiskFreeSpace_ThreadProc, pcs, 0, &threadId);
+ if (NULL != ht)
+ {
+ CloseHandle(ht);
+ StringCchCopy(ppd->szCurrentRoot, ARRAYSIZE(ppd->szCurrentRoot), pcs->szRoot);
+ return;
+ }
+ SetDlgItemText(hdlg, IDC_LBL_FREE, TEXT(""));
+ }
+ }
+ if (TEXT('\0') == *pcs->szRoot)
+ {
+ pcs->dwError = ERROR_INVALID_NAME;
+ StringCchCopy(ppd->szCurrentRoot, ARRAYSIZE(ppd->szCurrentRoot), pcs->szRoot);
+ PostMessage(pcs->hCallback, CPM_UPDATEDISKSIZE, 0, (LPARAM)pcs);
+ return;
+ }
+ free(pcs);
+
+
+}
+
+static void CopyPrepare_OnUpdateDiskSizeResult(HWND hdlg, CALCDISKSIZE *pcs)
+{
+ if (!pcs) return;
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+ if (ppd && CSTR_EQUAL == CompareString(STRCOMP_INVARIANT, NORM_IGNORECASE, ppd->szCurrentRoot, -1, pcs->szRoot, -1))
+ {
+ TCHAR szBuffer[128] = {0};
+ szBuffer[0] = TEXT('\0');
+
+ if (ERROR_SUCCESS == pcs->dwError) StrFormatByteSize64(pcs->bytesFree.QuadPart, szBuffer, ARRAYSIZE(szBuffer));
+ else WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN, szBuffer, sizeof(szBuffer));
+
+ SetDlgItemText(hdlg, IDC_LBL_FREE, szBuffer);
+ }
+ free(pcs);
+}
+
+
+static void CopyPrepare_OnOptionsClick(HWND hdlg)
+{
+ RECT rw, rw2;
+ BOOL bEnable;
+ INT height;
+
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+
+ HWND hctrl = GetDlgItem(hdlg, IDC_GRP_OPTIONS);
+ if (!hctrl || !GetWindowRect(hctrl, &rw)) return;
+
+ GetWindowRect(hdlg, &rw2);
+ OffsetRect(&rw, -rw2.left, -rw2.top);
+
+ if (WS_DISABLED & GetWindowLongPtrW(hctrl, GWL_STYLE))
+ {
+ height = rw.bottom + 8;
+ bEnable = TRUE;
+ Settings_SetDirectoryCtrl(C_COPY, CF_PATH, hdlg, IDC_EDT_PATH);
+ Settings_SetCheckBox(C_COPY, CF_ADDTOMLDB, hdlg, IDC_CHK_ADDTOMLDB);
+ Settings_SetCheckBox(C_COPY, CF_USETITLEFMT, hdlg, IDC_CHK_CUSTOMNAME);
+ Settings_SetDlgItemText(C_COPY, CF_TITLEFMT, hdlg, IDC_EDT_NAMEFORMAT);
+ SetDlgItemText(hdlg, IDC_BTN_OPTIONS, WASABI_API_LNGSTRINGW(IDS_OPTIONS_HIDE));
+
+ if (ppd && NULL == ppd->pac)
+ {
+ HRESULT hr;
+ hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete, (LPVOID*)&ppd->pac);
+ if (S_OK == hr)
+ {
+ IAutoComplete2 *pac2;
+ if (SUCCEEDED(ppd->pac->QueryInterface(IID_IAutoComplete2, (LPVOID*)&pac2)))
+ {
+ pac2->SetOptions(ACO_AUTOSUGGEST | ACO_AUTOAPPEND | 0x00000020/*ACF_UPDOWNKEYDROPSLIST*/);
+ pac2->Release();
+ }
+
+ hr = CoCreateInstance(CLSID_ACListISF, NULL, CLSCTX_INPROC_SERVER, IID_IACList2, (LPVOID*)&ppd->pacl2);
+ if (S_OK == hr) ppd->pacl2->SetOptions(ACLO_FILESYSDIRS);
+ }
+ if(ppd->pac) ppd->pac->Init(GetDlgItem(hdlg, IDC_EDT_PATH), ppd->pacl2, NULL, NULL);
+ }
+ }
+ else
+ {
+ height = rw.top;
+ bEnable = FALSE;
+ SetDlgItemText(hdlg, IDC_BTN_OPTIONS, WASABI_API_LNGSTRINGW(IDS_OPTIONS_SHOW));
+ }
+
+ SetWindowPos(hdlg, NULL, 0, 0, rw2.right - rw2.left, height, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);
+ EnableWindow(hctrl, bEnable);
+
+ UINT szIdList[] = { IDC_EDT_PATH, IDC_BTN_BROWSE, IDC_CHK_ADDTOMLDB,
+ IDC_CHK_CUSTOMNAME, IDC_EDT_NAMEFORMAT, IDC_BTN_HELP, };
+
+ for (int i = 0; i < sizeof(szIdList)/sizeof(szIdList[0]); i++) EnableWindow(GetDlgItem(hdlg, szIdList[i]), bEnable);
+ if (bEnable && BST_UNCHECKED == IsDlgButtonChecked(hdlg, IDC_CHK_CUSTOMNAME))
+ {
+ EnableWindow(GetDlgItem(hdlg, IDC_EDT_NAMEFORMAT), FALSE);
+ ShowWindow(GetDlgItem(hdlg, IDC_LBL_EXAMPLE), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, IDC_LBL_EXAMPLE_TITLE), SW_HIDE);
+ }
+}
+
+static INT_PTR CopyPrepare_OnHelp(HWND hdlg, HELPINFO *phi)
+{
+ PREPDLG *ppd = GetPrepDlg(hdlg);
+ if (ppd && 0 == (WS_DISABLED & GetWindowLongPtrW(GetDlgItem(hdlg, IDC_GRP_OPTIONS), GWL_STYLE)))
+ {
+ if (ppd->hActiveHelp) SetWindowPos(ppd->hActiveHelp, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ else ppd->hActiveHelp = MLDisc_ShowHelp(hdlg, MAKEINTRESOURCE(IDS_COPY_FILENAME_FORMAT_TITLE),
+ MAKEINTRESOURCE(IDS_COPY_FILENAME_FORMAT_CAPTION), MAKEINTRESOURCE(IDS_COPY_FILENAME_FORMAT), HF_ALLOWRESIZE);
+ SetWindowLongPtrW(hdlg, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+INT_PTR CALLBACK CopyPrepare_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return CopyPrepare_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ case WM_DESTROY: CopyPrepare_OnDestroy(hdlg); break;
+ case WM_PARENTNOTIFY: CopyPrepare_OnParentNotify(hdlg, LOWORD(wParam), lParam); break;
+ case WM_HELP: return CopyPrepare_OnHelp(hdlg, (HELPINFO*)lParam);
+ case CPM_UPDATEDISKSIZE: CopyPrepare_OnUpdateDiskSizeResult(hdlg, (CALCDISKSIZE*)lParam); break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hdlg, LOWORD(wParam));
+ break;
+ case IDC_BTN_BROWSE: if (HIWORD(wParam) == BN_CLICKED) Settings_BrowseForFolder(C_COPY, CF_PATH, hdlg, IDC_EDT_PATH); break;
+ case IDC_BTN_OPTIONS: if (HIWORD(wParam) == BN_CLICKED) CopyPrepare_OnOptionsClick(hdlg); break;
+ case IDC_CHK_ADDTOMLDB: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_COPY, CF_ADDTOMLDB, hdlg, IDC_CHK_ADDTOMLDB); break;
+ case IDC_CHK_CUSTOMNAME:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ Settings_FromCheckBox(C_COPY, CF_USETITLEFMT, hdlg, IDC_CHK_CUSTOMNAME);
+ BOOL bEnable = (BST_UNCHECKED != IsDlgButtonChecked(hdlg, IDC_CHK_CUSTOMNAME));
+ EnableWindow(GetDlgItem(hdlg, IDC_EDT_NAMEFORMAT), bEnable);
+ ShowWindow(GetDlgItem(hdlg, IDC_LBL_EXAMPLE_TITLE), (bEnable) ? SW_SHOWNA : SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, IDC_LBL_EXAMPLE), (bEnable) ? SW_SHOWNA : SW_HIDE);
+
+ }
+ break;
+
+ case IDC_EDT_PATH:
+ if (EN_CHANGE == HIWORD(wParam))
+ {
+ Settings_FromDirectoryCtrl(C_COPY, CF_PATH, hdlg, IDC_EDT_PATH);
+ SetTimer(hdlg, TID_UPDATEDISKSIZE, DELAY_UPDATEDISKSIZE, CopyPrepare_OnUpdateDiskSizeTimer);
+ }
+ break;
+ case IDC_EDT_NAMEFORMAT:
+ if (EN_CHANGE == HIWORD(wParam))
+ {
+ Settings_FromDlgItemText(C_COPY, CF_TITLEFMT, hdlg, IDC_EDT_NAMEFORMAT);
+ DisplayFormatExample(hdlg, IDC_LBL_EXAMPLE);
+ }
+ break;
+
+ case IDC_BTN_HELP:
+ if (HIWORD(wParam) == BN_CLICKED)
+ {
+ HELPINFO hi = {sizeof(HELPINFO), };
+ hi.dwContextId = HELPINFO_WINDOW;
+ hi.iCtrlId = IDC_EDT_NAMEFORMAT;
+ hi.hItemHandle = GetDlgItem(hdlg, IDC_EDT_NAMEFORMAT);
+ hi.iContextType = 0;
+ hi.MousePos.x = 0; hi.MousePos.y = 0;
+ SendMessageW(hdlg, WM_HELP, 0, (LPARAM)&hi);
+ }
+ break;
+ }
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/copyprogress.cpp b/Src/Plugins/Library/ml_disc/copyprogress.cpp
new file mode 100644
index 00000000..36bd36a7
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/copyprogress.cpp
@@ -0,0 +1,360 @@
+#include "main.h"
+#include "./copyfiles.h"
+#include "./copyinternal.h"
+#include "./resource.h"
+#include "./settings.h"
+#include "../nu/trace.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+typedef struct _PROGDLG
+{
+ COPYDATA *pCopyData;
+ HBITMAP hbmpLogo;
+} PROGDLG;
+
+#define PROGDLG_PROP TEXT("PROGDLG")
+#define GetProgDlg(__hdlg) ((PROGDLG*)GetProp((__hdlg), PROGDLG_PROP))
+
+#define SetControlText(__hwnd, __ctrlId, __pszText)\
+ SetDlgItemText((__hwnd), (__ctrlId), (IS_INTRESOURCE(__pszText) ? WASABI_API_LNGSTRINGW((UINT)(UINT_PTR)(__pszText)) : (__pszText)))
+
+#define SetTaskText(__hwnd, __pszText) SetControlText(__hwnd, IDC_LBL_TASK, __pszText)
+#define SetOperationText(__hwnd, __pszText) SetControlText(__hwnd, IDC_LBL_OPERATION, __pszText)
+
+
+static INT_PTR CopyProgress_OnInitDialog(HWND hdlg, HWND hFocus, LPARAM lParam)
+{
+ HWND hctrl;
+ PROGDLG *ppd = (PROGDLG*)calloc(1, sizeof(PROGDLG));
+ if (!ppd) return 0;
+
+ SetProp(hdlg, PROGDLG_PROP, ppd);
+ ppd->pCopyData = (COPYDATA*)lParam;
+ CopyFiles_AddRef(ppd->pCopyData);
+ if (ppd->pCopyData && ppd->pCopyData->hOwner)
+ {
+ RECT rw;
+ if (!GetWindowRect(ppd->pCopyData->hOwner, &rw)) SetRect(&rw, 0, 0, 0, 0);
+ if (hdlg && rw.left != rw.right)
+ {
+ RECT rw2;
+ GetWindowRect(hdlg, &rw2);
+ SetWindowPos(hdlg, HWND_TOP,
+ rw.left + ((rw.right - rw.left) - (rw2.right - rw2.left))/2,
+ rw.top + ((rw.bottom - rw.top) - (rw2.bottom - rw2.top))/2,
+ 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ }
+ }
+
+ hctrl = GetDlgItem(hdlg, IDC_PRG_TOTAL);
+ if (NULL != hctrl)
+ {
+ SendMessage(hctrl, PBM_SETRANGE32, (WPARAM)0, (LPARAM)100);
+ SendMessage(hctrl, PBM_SETPOS, (WPARAM)0, 0L);
+ SendMessage(hctrl, PBM_SETSTEP, (WPARAM)1, 0L);
+ }
+
+ SetTaskText(hdlg, MAKEINTRESOURCE(IDS_COPY_TASK_PREPARE));
+ SetOperationText(hdlg, TEXT(""));
+
+ SendMessage(hdlg, DM_REPOSITION, 0, 0L);
+
+ ppd->hbmpLogo = CopyFiles_LoadResourcePng(MAKEINTRESOURCE(IDB_FILECOPY));
+ if (ppd->hbmpLogo) SendDlgItemMessage(hdlg, IDC_PIC_LOGO, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)ppd->hbmpLogo);
+ else ShowWindow(GetDlgItem(hdlg, IDC_PIC_LOGO), SW_HIDE);
+
+ return FALSE;
+}
+
+static void CopyProgress_OnDestroy(HWND hdlg)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+ RemoveProp(hdlg, PROGDLG_PROP);
+ if (ppd)
+ {
+ if (ppd->pCopyData) CopyFiles_Release(ppd->pCopyData);
+
+ if (ppd->hbmpLogo)
+ {
+ HBITMAP hbmp = (HBITMAP)SendDlgItemMessage(hdlg, IDC_PIC_LOGO, STM_GETIMAGE, IMAGE_BITMAP, 0L);
+ if (hbmp != ppd->hbmpLogo) DeleteObject(hbmp);
+ DeleteObject(ppd->hbmpLogo);
+ }
+
+ free(ppd);
+ }
+}
+
+static void ShowErrorBox(HWND hdlg)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+ if (!ppd || !ppd->pCopyData) return;
+
+
+ TCHAR szBuffer[2048] = {0}, szFormat[256] = {0}, szUnknown[64] = {0};
+ LPTSTR pszMessage;
+
+ if (ERROR_REQUEST_ABORTED == ppd->pCopyData->errorCode) return; // ignore user aborts
+
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
+ ppd->pCopyData->errorCode, 0, (LPTSTR)&pszMessage, 0, NULL);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN, szUnknown, ARRAYSIZE(szUnknown));
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPY_ERROR_MESSAGE, szFormat, ARRAYSIZE(szFormat));
+
+ StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), szFormat,
+ ppd->pCopyData->szDestination,
+ (ppd->pCopyData->errorMsgId) ? WASABI_API_LNGSTRINGW(ppd->pCopyData->errorMsgId) : szUnknown,
+ ppd->pCopyData->errorCode,
+ (pszMessage) ? pszMessage : szUnknown);
+
+ MessageBox(hdlg, szBuffer, WASABI_API_LNGSTRINGW(IDS_COPY_ERROR_CAPTION), MB_OK | MB_ICONERROR);
+ if (pszMessage) LocalFree(pszMessage);
+}
+
+static void CopyProgress_OnDisplayNextFile(HWND hdlg, INT iFile, INT iCount)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+ if (!ppd || !ppd->pCopyData) return;
+ SetOperationText(hdlg, PathFindFileName(ppd->pCopyData->ppszFiles[iFile]));
+
+}
+
+
+static INT_PTR CopyProgress_OnDestiantionNotExist(HWND hdlg, LPCTSTR pszDestination)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+ if (!ppd || !ppd->pCopyData) return FALSE;
+ QUESTIONBOX qb = {0};
+ TCHAR szFormat[MAX_PATH] = {0}, szMessage[MAX_PATH*2] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DESTINATION_NOT_EXIST_FORMAT, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szMessage, ARRAYSIZE(szMessage), szFormat, pszDestination);
+
+ qb.hParent =hdlg;
+ qb.pszIcon = IDI_QUESTION;
+ qb.pszTitle = MAKEINTRESOURCE(IDS_CONFIRM_CREATE_DESTINATION);
+ qb.pszMessage = szMessage;
+ qb.pszBtnOkText = MAKEINTRESOURCE(IDS_YES);
+ qb.pszBtnCancelText = MAKEINTRESOURCE(IDS_NO);
+ qb.uBeepType = MB_ICONEXCLAMATION;
+ qb.uFlags = QBF_DEFAULT_OK | QBF_SETFOREGROUND | QBF_BEEP;
+ return (IDCANCEL == MLDisc_ShowQuestionBox(&qb));
+}
+
+static INT_PTR CopyProgress_OnReadOnly(HWND hdlg, LPCTSTR pszFile)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+ if (!ppd || !ppd->pCopyData) return FALSE;
+ QUESTIONBOX qb = {0};
+ TCHAR szFormat[MAX_PATH] = {0}, szMessage[MAX_PATH*2] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_READONLY_FILE_DELETE_FORMAT, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szMessage, ARRAYSIZE(szMessage), szFormat, pszFile);
+
+ qb.hParent =hdlg;
+ qb.pszIcon = IDI_QUESTION;
+ qb.pszTitle = MAKEINTRESOURCE(IDS_CONFIRM_FILE_DELETE);
+ qb.pszMessage = szMessage;
+ qb.pszBtnOkText = MAKEINTRESOURCE(IDS_YES);
+ qb.pszBtnCancelText = MAKEINTRESOURCE(IDS_CANCEL);
+ qb.pszCheckboxText = MAKEINTRESOURCE(IDS_APPLY_TO_ALL_FILES);
+ qb.uBeepType = MB_ICONEXCLAMATION;
+ qb.uFlags = QBF_DEFAULT_OK | QBF_SETFOREGROUND | QBF_BEEP | QBF_SHOW_CHECKBOX;
+
+ switch(MLDisc_ShowQuestionBox(&qb))
+ {
+ case IDCANCEL: return READONLY_CANCELCOPY;
+ case IDOK: return (qb.checkboxChecked) ? READONLY_DELETEALL : READONLY_DELETE;
+ }
+
+ return FALSE;
+}
+
+static LPTSTR FormatFileInfo(LPTSTR pszBuffer, size_t cchBufferMax, LPCTSTR pszFilePath)
+{
+ HANDLE hFile;
+ HRESULT hr;
+ BY_HANDLE_FILE_INFORMATION fi;
+
+ pszBuffer[0] = TEXT('\0');
+
+ hFile = CreateFile(pszFilePath, FILE_READ_ATTRIBUTES | FILE_READ_EA,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL);
+
+ if (INVALID_HANDLE_VALUE != hFile &&
+ GetFileInformationByHandle(hFile, &fi))
+ {
+ TCHAR szTemp[1024] = {0}, szKeyword[64] = {0};
+ SYSTEMTIME st = {0};
+
+ LONGLONG fsize = (LONGLONG)(((__int64)fi.nFileSizeHigh<< 32) | fi.nFileSizeLow);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_SIZE, szKeyword, ARRAYSIZE(szKeyword));
+ hr = StringCchPrintfEx(pszBuffer, cchBufferMax, &pszBuffer, &cchBufferMax, STRSAFE_IGNORE_NULLS, TEXT("\n %s: %s"), szKeyword, StrFormatByteSize64(fsize, szTemp, ARRAYSIZE(szTemp)));
+
+ if (S_OK == hr && FileTimeToSystemTime(&fi.ftCreationTime, &st) &&
+ GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szTemp, ARRAYSIZE(szTemp)))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_CREATED, szKeyword, ARRAYSIZE(szKeyword));
+ hr = StringCchPrintfEx(pszBuffer, cchBufferMax, &pszBuffer, &cchBufferMax, STRSAFE_IGNORE_NULLS, TEXT("\n %s: %s"), szKeyword, szTemp);
+ if (S_OK == hr && GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szTemp, ARRAYSIZE(szTemp)))
+ hr = StringCchPrintfEx(pszBuffer, cchBufferMax, &pszBuffer, &cchBufferMax, STRSAFE_IGNORE_NULLS, TEXT(", %s"), szTemp);
+
+ }
+
+
+ if (S_OK == hr && FileTimeToSystemTime(&fi.ftLastWriteTime, &st) &&
+ GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szTemp, ARRAYSIZE(szTemp)))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_MODIFIED, szKeyword, ARRAYSIZE(szKeyword));
+ hr = StringCchPrintfEx(pszBuffer, cchBufferMax, &pszBuffer, &cchBufferMax, STRSAFE_IGNORE_NULLS, TEXT("\n %s: %s"), szKeyword, szTemp);
+ if (S_OK == hr && GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szTemp, ARRAYSIZE(szTemp)))
+ hr = StringCchPrintfEx(pszBuffer, cchBufferMax, &pszBuffer, &cchBufferMax, STRSAFE_IGNORE_NULLS, TEXT(", %s"), szTemp);
+
+ }
+ if (S_OK == hr)
+ {
+ hr = StringCchCopyEx(pszBuffer, cchBufferMax, TEXT("\n\n"), &pszBuffer, &cchBufferMax, STRSAFE_IGNORE_NULLS);
+ }
+ }
+ else hr = S_FALSE;
+
+ if (S_OK != hr) pszBuffer[0] = TEXT('\0');
+
+ if (INVALID_HANDLE_VALUE != hFile) CloseHandle(hFile);
+ return pszBuffer;
+
+}
+static INT_PTR CopyProgress_OnFileAlreadyExist(HWND hdlg, FILECONFLICT *pConflict)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+ if (!ppd || !ppd->pCopyData) return FALSE;
+ QUESTIONBOX qb = {0};
+ TCHAR szFormat[128] = {0}, szMessage[MAX_PATH*2] = {0}, szPath[MAX_PATH] = {0}, szFileInfo1[128] = {0}, szFileInfo2[128] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_FILE_REPLACE_FORMAT, szFormat, ARRAYSIZE(szFormat));
+
+ StringCchCopy(szPath, ARRAYSIZE(szPath), pConflict->pszNameExisting);
+ LPTSTR pszFileName = PathFindFileName(szPath);
+ if (pszFileName && pszFileName > szPath) *(pszFileName - 1) = TEXT('\0');
+
+ FormatFileInfo(szFileInfo1, ARRAYSIZE(szFileInfo1), pConflict->pszNameExisting);
+ FormatFileInfo(szFileInfo2, ARRAYSIZE(szFileInfo2), pConflict->pszNameNew);
+
+ StringCchPrintf(szMessage, ARRAYSIZE(szMessage), szFormat, szPath, pszFileName, szFileInfo1, szFileInfo2);
+
+ qb.hParent = hdlg;
+ qb.pszIcon = IDI_QUESTION;
+ qb.pszTitle = MAKEINTRESOURCE(IDS_CONFIRM_FILE_REPLACE);
+ qb.pszMessage = szMessage;
+
+ qb.pszBtnExtraText = MAKEINTRESOURCE(IDS_SKIP);
+ qb.pszBtnOkText = MAKEINTRESOURCE(IDS_OVERWRITE);
+ qb.pszBtnCancelText = MAKEINTRESOURCE(IDS_CANCEL);
+ qb.pszCheckboxText = MAKEINTRESOURCE(IDS_APPLY_TO_ALL_FILES);
+ qb.uBeepType = MB_ICONEXCLAMATION;
+
+ qb.uFlags = QBF_DEFAULT_OK | QBF_SETFOREGROUND | QBF_BEEP | QBF_SHOW_CHECKBOX | QBF_SHOW_EXTRA_BUTTON;
+
+ INT_PTR qbr = MLDisc_ShowQuestionBox(&qb);
+ INT r = 0;
+ switch(qbr)
+ {
+ case IDCANCEL: r = EXISTFILE_CANCELCOPY; break;
+ case IDOK: r = EXISTFILE_OVERWRITE; break;
+ case IDC_BTN_EXTRA1: r = EXISTFILE_SKIP; break;
+ }
+
+ if (qb.checkboxChecked) r |= EXISTFILE_APPLY_TO_ALL;
+ return r;
+}
+
+static INT_PTR CopyProgress_OnCopyNotify(HWND hdlg, UINT uTask, UINT uOperation, LPARAM lParam)
+{
+ switch(uTask)
+ {
+ case CFT_INITIALIZING:
+
+ switch(uOperation)
+ {
+ case CFO_INIT:
+ SetTaskText(hdlg, MAKEINTRESOURCE(IDS_COPY_TASK_PREPARE));
+ SetOperationText(hdlg, TEXT(""));
+ break;
+ case CFO_CACLSIZE:
+ SetOperationText(hdlg, MAKEINTRESOURCE(IDS_COPY_OP_CALCULATESIZE));
+ break;
+ case CFO_CHECKDESTINATION:
+ SetOperationText(hdlg, MAKEINTRESOURCE(IDS_COPY_OP_CHECKDESTINATION));
+ break;
+ }
+ break;
+ case CFT_COPYING:
+ switch(uOperation)
+ {
+ case CFO_INIT:
+ SetTaskText(hdlg, MAKEINTRESOURCE(IDS_COPY_TASK_COPY));
+ SetOperationText(hdlg, TEXT(""));
+ break;
+ case CFO_NEXTFILE:
+ CopyProgress_OnDisplayNextFile(hdlg, LOWORD(lParam), HIWORD(lParam));
+ break;
+ case CFO_PROGRESS:
+ SendDlgItemMessage(hdlg, IDC_PRG_TOTAL, PBM_SETPOS, (WPARAM)lParam, 0L);
+ break;
+ }
+ break;
+ case CFT_FINISHED:
+ SetTaskText(hdlg, MAKEINTRESOURCE(IDS_COPY_TASK_FINISHED));
+ switch(uOperation)
+ {
+ case CFO_SUCCESS: SetOperationText(hdlg, MAKEINTRESOURCE(IDS_COMPLETED)); break;
+ case CFO_CANCELLED: SetOperationText(hdlg, MAKEINTRESOURCE(IDS_CANCELLED)); break;
+ case CFO_FAILED:
+ SetOperationText(hdlg, MAKEINTRESOURCE(IDS_FAILED));
+ ShowErrorBox(hdlg);
+ break;
+ }
+ DestroyWindow(hdlg);
+ break;
+ case CFT_CONFLICT:
+ switch(uOperation)
+ {
+ case CFO_DESTNOTEXIST: return CopyProgress_OnDestiantionNotExist(hdlg, (LPCTSTR)lParam);
+ case CFO_FILEALREDYEXIST: return CopyProgress_OnFileAlreadyExist(hdlg, (FILECONFLICT*)lParam);
+ case CFO_READONLY: return CopyProgress_OnReadOnly(hdlg, (LPCTSTR)lParam);
+ }
+ break;
+ }
+ return FALSE;
+}
+INT_PTR CALLBACK CopyProgress_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ PROGDLG *ppd = GetProgDlg(hdlg);
+
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return CopyProgress_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ case WM_DESTROY: CopyProgress_OnDestroy(hdlg); break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ case IDCANCEL:
+ if (ppd && ppd->pCopyData)
+ {
+ SetOperationText(hdlg, MAKEINTRESOURCE(IDS_CANCELLING));
+ SendDlgItemMessage(hdlg, IDCANCEL, BM_SETSTATE, (WPARAM)TRUE, 0L);
+ EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);
+ CopyFiles_CancelCopy(ppd->pCopyData);
+ }
+ else DestroyWindow(hdlg);
+ break;
+ }
+ case CFM_NOTIFY: MSGRESULT(hdlg, CopyProgress_OnCopyNotify(hdlg, LOWORD(wParam), HIWORD(wParam), lParam));
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/discInfo.cpp b/Src/Plugins/Library/ml_disc/discInfo.cpp
new file mode 100644
index 00000000..9aff366d
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/discInfo.cpp
@@ -0,0 +1,328 @@
+#include "main.h"
+#include "discinfo.h"
+#include "resource.h"
+#include <strsafe.h>
+
+// indices
+#define DD_PARSED 0x0000
+#define DD_INDEX_MEDIUM_TYPE 0x0001
+#define DD_INDEX_MEDIUM_FORMAT 0x0002
+#define DD_INDEX_MEDIUM 0x0003
+#define DD_INDEX_PROTECTED_DVD 0x0004
+#define DD_INDEX_ERASABLE 0x0005
+#define DD_INDEX_TRACKS_NUM 0x0006
+#define DD_INDEX_SECTOR_USED 0x0007
+#define DD_INDEX_SECTOR_FREE 0x0008
+
+#define TEXT_UNKNOWN L"Unknown"
+#define TEXT_TRUE L"Yes"
+#define TEXT_FALSE L"No"
+
+// MediumType Values
+#define PRIMOSDK_SILVER 0x00000301 /* A disc that is not recordable. It may be a stamped (silver) disc or a gold (recordable) disc that has been recorded Disc-At-Once. */
+#define PRIMOSDK_COMPLIANTGOLD 0x00000302 /* A gold disc or rewritable disc that contains data but remains open, allowing the appending of additional data. */
+#define PRIMOSDK_OTHERGOLD 0x00000303 /* A gold disc to which it is not possible for PrimoSDK to append additional data. */
+#define PRIMOSDK_BLANK 0x00000304 /* A blank gold disc or blank rewritable disc. */
+
+// MediumFormat Values
+#define PRIMOSDK_B1 0x000000B1 /* Blank disc */
+#define PRIMOSDK_D1 0x000000D1 /* Data Mode 1 DAO (e.g. most data CD-ROMs or typical DOS games) */
+#define PRIMOSDK_D2 0x000000D2 /* Kodak Photo CD: Data multisession Mode 2 TAO */
+#define PRIMOSDK_D3 0x000000D3 /* Gold Data Mode 1: Data multisession Mode 1, closed */
+#define PRIMOSDK_D4 0x000000D4 /* Gold Data Mode 2: Data multisession Mode 2, closed */
+#define PRIMOSDK_D5 0x000000D5 /* Data Mode 2 DAO (silver mastered from Corel or Toast gold) */
+#define PRIMOSDK_D6 0x000000D6 /* CDRFS: Fixed packet (from Sony packet-writing solution) */
+#define PRIMOSDK_D7 0x000000D7 /* Packet writing */
+#define PRIMOSDK_D8 0x000000D8 /* Gold Data Mode 1: Data multisession Mode 1, open */
+#define PRIMOSDK_D9 0x000000D9 /* Gold Data Mode 2: Data multisession Mode 2, open */
+#define PRIMOSDK_A1 0x000000A1 /* Audio DAO/SAO/TAO (like most silver music discs) or closed gold audio */
+#define PRIMOSDK_A2 0x000000A2 /* Audio Gold disc with session not closed (TAO or SAO) */
+#define PRIMOSDK_A3 0x000000A3 /* First type of Enhanced CD (aborted) */
+#define PRIMOSDK_A4 0x000000A4 /* CD Extra, Blue Book standard */
+#define PRIMOSDK_A5 0x000000A5 /* Audio TAO with session not written (in-progress compilation) */
+#define PRIMOSDK_M1 0x000000E1 /* First track data, others audio */
+#define PRIMOSDK_M2 0x000000E2 /* Mixed-mode made TAO */
+#define PRIMOSDK_M3 0x000000E3 /* Kodak Portfolio (as per the Kodak standard) */
+#define PRIMOSDK_M4 0x000000E4 /* Video CD (as the White Book standard) */
+#define PRIMOSDK_M5 0x000000E5 /* CD-i (as the Green Book standard) */
+#define PRIMOSDK_M6 0x000000E6 /* PlayStation (Sony games) */
+#define PRIMOSDK_F1 0x000000F1 /* Obsolete */
+#define PRIMOSDK_F2 0x000000F2 /* Obsolete for restricted overwrite DVD (DLA DVD-RW) */
+#define PRIMOSDK_F3 0x000000F3 /* Completed (non-appendable) DVD (DVD-ROM or closed recordable) */
+#define PRIMOSDK_F4 0x000000F4 /* Incremental DVD with appendable zone (DLA DVD-R and DVD+RW) */
+#define PRIMOSDK_F8 0x000000F8 /* Appendable DVD of any type (single border or multiborder) */
+#define PRIMOSDK_FA 0x000000FA /* DVD-RAM cartridge */
+#define PRIMOSDK_GENERICCD 0x000000C1 /* Other type of CD. */
+
+// Medium and Unit Values
+#define PRIMOSDK_CDROM 0x00000201 /* CD-ROM */
+#define PRIMOSDK_CDR 0x00000202 /* CD-R */
+#define PRIMOSDK_CDRW 0x00000203 /* CD-RW */
+#define PRIMOSDK_DVDR 0x00000204 /* DVD-R */
+#define PRIMOSDK_DVDROM 0x00000205 /* DVD-ROM (any type) */
+#define PRIMOSDK_DVDRAM 0x00000206 /* DVD-RAM */
+#define PRIMOSDK_DVDRW 0x00000207 /* DVD-RW */
+#define PRIMOSDK_DVDPRW 0x00000209 /* DVD+RW */
+#define PRIMOSDK_DVDPR 0x00000210 /* DVD+R */
+#define PRIMOSDK_DDCDROM 0x00000211 /* Double-density CD-ROM */
+#define PRIMOSDK_DDCDR 0x00000212 /* Double-density CD-R */
+#define PRIMOSDK_DDCDRW 0x00000213 /* Double-density CD-RW */
+#define PRIMOSDK_DVDPR9 0x00000214 /* dual-layer DVD+R */
+#define PRIMOSDK_OTHER 0x00000220 /* other types */
+
+
+int mediumTypeText[] = {IDS_STAMPED_DISC_OR_RECORDABLE_THAT_HAS_BEEN_RECORDED,
+ IDS_REWRITEABLE_DISC_HAS_DATA_BUT_KEPT_OPEN_FOR_APPEND,
+ IDS_REWRITEABLE_DISC_NOT_POSSIBLE_TO_APPEND_DATA,
+ IDS_BLANK_REWRITEABLE_DISC,
+ };
+
+const wchar_t *mediumText[] = {L"CD-ROM", L"CD-R", L"CD-RW", L"DVD-ROM",
+ L"DVD-R",L"DVD-RW", L"DVD+R", L"DVD+RW", L"DVD-RAM",
+ L"DDCD-ROM", L"DDCD-R", L"DDCD-RW", L"DL DVD+R"};
+
+int mediumFormatText[] = { IDS_MEDIA_BLANK_DISC,
+ IDS_MEDIA_DATA_MODE_1_DAO,
+ IDS_MEDIA_KODAK_PHOTO_CD,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_1_CLOSED,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_2_CLOSED,
+ IDS_MEDIA_DATA_MODE_2_DAO,
+ IDS_MEDIA_CDRFS,
+ IDS_MEDIA_PACKET_WRITING,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_1_OPEN,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_2_OPEN,
+ IDS_MEDIA_AUDIO_DAO_SAO_TAO,
+ IDS_MEDIA_AUDIO_REWRITEABLE_DISC_WITH_SESSION_NOT_CLOSED,
+ IDS_MEDIA_FIRST_TYPE_OF_ENHANCED_CD_ABORTED,
+ IDS_MEDIA_CD_EXTRA,
+ IDS_MEDIA_AUDIO_TAO_WITH_SESSION_NOT_WRITTEN,
+ IDS_MEDIA_FIRST_TRACK_DATA_OTHERS_AUDIO,
+ IDS_MEDIA_MIXED_MODE_MADE_TAO,
+ IDS_MEDIA_KODAK_PORTFOLIO,
+ IDS_MEDIA_VIDEO_CD,
+ IDS_MEDIA_CDi,
+ IDS_MEDIA_PLAYSTATION_SONY_GAMES,
+ IDS_MEDIA_OBSOLETE,
+ IDS_MEDIA_OBSOLETE_FOR_RESTRICTED_OVERWRITE_DVD,
+ IDS_MEDIA_DVDROM_OR_CLOSED_RECORDABLE,
+ IDS_MEDIA_INCREMENTAL_DVD_WITH_APPENDABLE_ZONE,
+ IDS_MEDIA_APPENDABLE_DVD_OF_ANY_TYPE,
+ IDS_MEDIA_DVDRAM_CARTRIDGE,
+ IDS_MEDIA_CD_OTHER_TYPE
+ };
+
+DiscInfo::DiscInfo(void)
+{
+ serialNum = 0;
+ memset(buffer, 0, sizeof(buffer));
+ strData = NULL;
+ ResetData();
+}
+
+DiscInfo::DiscInfo(const wchar_t *info)
+{
+ serialNum = 0;
+ memset(buffer, 0, sizeof(buffer));
+ strData = NULL;
+ SetStringInfo(info);
+}
+
+
+DiscInfo::~DiscInfo(void)
+{
+ ResetData();
+}
+
+void DiscInfo::ResetData(void)
+{
+ if (strData) free(strData);
+ strData = NULL;
+
+ data[DD_PARSED]= FALSE;
+ for (int i = 1; i < DISC_DATA_COUNT; i++) data[i] = -1;
+}
+
+BOOL DiscInfo::SetStringInfo(const wchar_t *info)
+{
+ ResetData();
+
+ int strLen = lstrlenW(info) + 1;
+ strData = (wchar_t*) malloc(strLen * sizeof(wchar_t));
+ StringCchCopyW(strData, strLen, info);
+
+ const wchar_t *start = info;
+ const wchar_t *end = info;
+ BOOL cont;
+ int i = 1;
+ do
+ {
+ while(end[0] != L';' && end[0] != 0x00) end = CharNextW(end);
+ cont = (end[0] == L';');
+
+ data[i++] = _wtoi(start);
+
+ if(cont)
+ {
+ end = CharNextW(end);
+ start = end;
+ }
+ }
+ while(cont && i < DISC_DATA_COUNT);
+
+ data[DD_PARSED] = (i == DISC_DATA_COUNT);
+ return data[DD_PARSED];
+}
+const wchar_t* DiscInfo::GetStringInfo(void)
+{
+ return strData;
+}
+DWORD DiscInfo::GetMedium(void)
+{
+ return data[DD_INDEX_MEDIUM];
+}
+DWORD DiscInfo::GetMediumType(void)
+{
+ return data[DD_INDEX_MEDIUM_TYPE];
+}
+DWORD DiscInfo::GetMediumFormat(void)
+{
+ return data[DD_INDEX_MEDIUM_FORMAT];
+}
+BOOL DiscInfo::GetProtectedDVD(void)
+{
+ return data[DD_INDEX_PROTECTED_DVD];
+}
+BOOL DiscInfo::GetErasable(void)
+{
+ return data[DD_INDEX_ERASABLE];
+}
+DWORD DiscInfo::GetTracksNumber(void)
+{
+ return data[DD_INDEX_TRACKS_NUM];
+}
+DWORD DiscInfo::GetSectorsUsed(void)
+{
+ return data[DD_INDEX_SECTOR_USED];
+}
+DWORD DiscInfo::GetSectorsFree(void)
+{
+ return data[DD_INDEX_SECTOR_FREE];
+}
+
+BOOL DiscInfo::GetRecordable(void)
+{
+ return (data[DD_INDEX_MEDIUM_TYPE] == PRIMOSDK_COMPLIANTGOLD || data[DD_INDEX_MEDIUM_TYPE] == PRIMOSDK_BLANK);
+}
+
+int DiscInfo::GetSerialNumber(void)
+{
+ return serialNum;
+}
+
+void DiscInfo::SetSerialNumber(int serialNumber)
+{
+ serialNum = serialNumber;
+}
+
+const wchar_t* DiscInfo::GetMediumText(void)
+{
+ int index = -1;
+ switch(data[DD_INDEX_MEDIUM])
+ {
+ case PRIMOSDK_CDROM: index = 0; break;
+ case PRIMOSDK_CDR: index = 1; break;
+ case PRIMOSDK_CDRW: index = 2; break;
+ case PRIMOSDK_DVDR: index = 4; break;
+ case PRIMOSDK_DVDROM: index = 3; break;
+ case PRIMOSDK_DVDRAM: index = 8; break;
+ case PRIMOSDK_DVDRW: index = 5; break;
+ case PRIMOSDK_DVDPRW: index = 7; break;
+ case PRIMOSDK_DVDPR: index = 6; break;
+ case PRIMOSDK_DDCDROM: index = 9; break;
+ case PRIMOSDK_DDCDR: index = 10; break;
+ case PRIMOSDK_DDCDRW: index = 11; break;
+ case PRIMOSDK_DVDPR9: index = 12; break;
+ default: return TEXT_UNKNOWN;
+ }
+ return mediumText[index];
+}
+const wchar_t* DiscInfo::GetMediumTypeText(void)
+{
+ static wchar_t tmp[256];
+ int index = -1;
+ switch(data[DD_INDEX_MEDIUM_TYPE])
+ {
+ case PRIMOSDK_SILVER: index = 0; break;
+ case PRIMOSDK_COMPLIANTGOLD: index = 1; break;
+ case PRIMOSDK_OTHERGOLD: index = 2; break;
+ case PRIMOSDK_BLANK: index = 3; break;
+ default: return WASABI_API_LNGSTRINGW_BUF(plugin.hDllInstance,IDS_UNKNOWN,tmp,256);
+ }
+ return WASABI_API_LNGSTRINGW_BUF(plugin.hDllInstance,mediumTypeText[index],tmp,256);
+}
+const wchar_t* DiscInfo::GetMediumFormatText(void)
+{
+ static wchar_t tmpM[256];
+ int index = -1;
+ switch(data[DD_INDEX_MEDIUM_FORMAT])
+ {
+ case PRIMOSDK_B1: index = 0; break;
+ case PRIMOSDK_D1: index = 1; break;
+ case PRIMOSDK_D2: index = 2; break;
+ case PRIMOSDK_D3: index = 3; break;
+ case PRIMOSDK_D4: index = 4; break;
+ case PRIMOSDK_D5: index = 5; break;
+ case PRIMOSDK_D6: index = 6; break;
+ case PRIMOSDK_D7: index = 7; break;
+ case PRIMOSDK_D8: index = 8; break;
+ case PRIMOSDK_D9: index = 9; break;
+ case PRIMOSDK_A1: index = 10; break;
+ case PRIMOSDK_A2: index = 11; break;
+ case PRIMOSDK_A3: index = 12; break;
+ case PRIMOSDK_A4: index = 13; break;
+ case PRIMOSDK_A5: index = 14; break;
+ case PRIMOSDK_M1: index = 15; break;
+ case PRIMOSDK_M2: index = 16; break;
+ case PRIMOSDK_M3: index = 17; break;
+ case PRIMOSDK_M4: index = 18; break;
+ case PRIMOSDK_M5: index = 19; break;
+ case PRIMOSDK_M6: index = 20; break;
+ case PRIMOSDK_F1: index = 21; break;
+ case PRIMOSDK_F2: index = 22; break;
+ case PRIMOSDK_F3: index = 23; break;
+ case PRIMOSDK_F4: index = 24; break;
+ case PRIMOSDK_F8: index = 25; break;
+ case PRIMOSDK_FA: index = 26; break;
+ case PRIMOSDK_GENERICCD: index = 27; break;
+ default: return WASABI_API_LNGSTRINGW_BUF(plugin.hDllInstance,IDS_UNKNOWN,tmpM,256);
+ }
+ return WASABI_API_LNGSTRINGW_BUF(plugin.hDllInstance,mediumFormatText[index],tmpM,256);
+}
+const wchar_t* DiscInfo::GetProtectedDVDText(void)
+{
+ return data[DD_INDEX_PROTECTED_DVD] ? TEXT_TRUE : TEXT_FALSE ;
+}
+const wchar_t* DiscInfo::GetErasableText(void)
+{
+ return data[DD_INDEX_ERASABLE] ? TEXT_TRUE: TEXT_FALSE ;
+}
+const wchar_t* DiscInfo::GetTracksNumberText(void)
+{
+ StringCchPrintfW(buffer, TEXT_BUFFER_SIZE, L"%d", data[DD_INDEX_TRACKS_NUM]);
+ return buffer;
+}
+const wchar_t* DiscInfo::GetSectorsUsedText(void)
+{
+ StringCchPrintfW(buffer, TEXT_BUFFER_SIZE, L"%d", data[DD_INDEX_SECTOR_USED]);
+ return buffer;
+}
+const wchar_t* DiscInfo::GetSectorsFreeText(void)
+{
+ StringCchPrintfW(buffer, TEXT_BUFFER_SIZE, L"%d", data[DD_INDEX_SECTOR_FREE]);
+ return buffer;
+}
+
+const wchar_t* DiscInfo::GetRecordableText(void)
+{
+ return GetRecordable() ? TEXT_TRUE : TEXT_FALSE;
+}
diff --git a/Src/Plugins/Library/ml_disc/discInfo.h b/Src/Plugins/Library/ml_disc/discInfo.h
new file mode 100644
index 00000000..89f6d30e
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/discInfo.h
@@ -0,0 +1,61 @@
+#ifndef NULLSOFT_DISCINFO_HEADER
+#define NULLSOFT_DISCINFO_HEADER
+
+#include <windows.h>
+
+
+// disc data array size (see decalrations in discInfo.cpp)
+#define DISC_DATA_COUNT 0x0009
+
+#define TEXT_BUFFER_SIZE 64
+
+
+
+class DiscInfo
+{
+public:
+ DiscInfo(void);
+ DiscInfo(const wchar_t *info);
+ ~DiscInfo(void);
+
+public:
+
+ BOOL SetStringInfo(const wchar_t *info);
+ const wchar_t* GetStringInfo(void);
+
+ DWORD GetMedium(void);
+ DWORD GetMediumType(void);
+ DWORD GetMediumFormat(void);
+ BOOL GetProtectedDVD(void);
+ BOOL GetErasable(void);
+ DWORD GetTracksNumber(void);
+ DWORD GetSectorsUsed(void);
+ DWORD GetSectorsFree(void);
+ void SetSerialNumber(int serialNumber);
+ int GetSerialNumber(void);
+
+ BOOL GetRecordable(void);
+
+ const wchar_t* GetMediumText(void);
+ const wchar_t* GetMediumTypeText(void);
+ const wchar_t* GetMediumFormatText(void);
+ const wchar_t* GetProtectedDVDText(void);
+ const wchar_t* GetErasableText(void);
+ const wchar_t* GetTracksNumberText(void);
+ const wchar_t* GetSectorsUsedText(void);
+ const wchar_t* GetSectorsFreeText(void);
+
+ const wchar_t* GetRecordableText(void);
+
+protected:
+ void ResetData(void);
+
+private:
+ wchar_t *strData;
+ int serialNum;
+ wchar_t buffer[TEXT_BUFFER_SIZE];
+ DWORD data[DISC_DATA_COUNT];
+
+};
+
+#endif // NULLSOFT_DISCINFO_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/drive.cpp b/Src/Plugins/Library/ml_disc/drive.cpp
new file mode 100644
index 00000000..e38e9d4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/drive.cpp
@@ -0,0 +1,138 @@
+#include "./main.h"
+#include "./drive.h"
+#include "./resource.h"
+//#include <primosdk.h>
+#include <strsafe.h>
+
+static LPCWSTR pszBusType[] =
+{
+ L"ATAPI",
+ L"SCSI",
+ L"1394",
+ L"USB",
+ L"USB2"
+};
+
+static LPCWSTR pszType[] =
+{
+ L"CD-ROM",
+ L"CD-R",
+ L"CD-RW",
+ L"DVD-ROM",
+ L"DVD-R",
+ L"DVD-RW",
+ L"DVD+R",
+ L"DVD+RW",
+ L"DVD-RAM",
+ L"DDCD-ROM",
+ L"DDCD-R",
+ L"DDCD-RW",
+ L"DL DVD+R",
+ L"DL DVD-R",
+ L"BD-RW",
+ L"BD-R",
+ L"BD-ROM",
+ L"HDDVD-RW",
+ L"HDDVD-R",
+ L"HDDVD-ROM",
+};
+
+static wchar_t buffer[64];
+
+LPCWSTR Drive_GetBusTypeString(DWORD nBusType)
+{
+ int index = -1;
+#if 0
+ switch (nBusType)
+ {
+ case PRIMOSDK_ATAPI: index = 0; break;
+ case PRIMOSDK_SCSI: index = 1; break;
+ case PRIMOSDK_1394: index = 2; break;
+ case PRIMOSDK_USB: index = 3; break;
+ case PRIMOSDK_USB2: index = 4; break;
+ }
+#endif
+ return (-1 != index) ? pszBusType[index] :
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN, buffer, sizeof(buffer)/sizeof(wchar_t));
+}
+
+LPCWSTR Drive_GetTypeString(DWORD nType)
+{
+ int index = -1;
+#if 0
+ switch (nType)
+ {
+ case PRIMOSDK_CDROM: index = 0; break;
+ case PRIMOSDK_CDR: index = 1; break;
+ case PRIMOSDK_CDRW: index = 2; break;
+ case PRIMOSDK_DVDROM: index = 3; break;
+ case PRIMOSDK_DVDR: index = 4; break;
+ case PRIMOSDK_DVDRW: index = 5; break;
+ case PRIMOSDK_DVDPR: index = 6; break;
+ case PRIMOSDK_DVDPRW: index = 7; break;
+ case PRIMOSDK_DVDRAM: index = 8; break;
+ case PRIMOSDK_DDCDROM: index = 9; break;
+ case PRIMOSDK_DDCDR: index = 10; break;
+ case PRIMOSDK_DDCDRW: index = 11; break;
+ case PRIMOSDK_DVDPR9: index = 12; break;
+ case PRIMOSDK_DVDR9: index = 13; break;
+ case PRIMOSDK_BDRE: index = 14; break;
+ case PRIMOSDK_BDR: index = 15; break;
+ case PRIMOSDK_BDROM: index = 16; break;
+ case PRIMOSDK_HDDVDRW: index = 17; break;
+ case PRIMOSDK_HDDVDR: index = 18; break;
+ case PRIMOSDK_HDDVDROM: index = 19; break;
+ }
+#endif
+ return (-1 != index) ? pszType[index] :
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN, buffer, sizeof(buffer)/sizeof(wchar_t));
+}
+
+BOOL Drive_IsRecorderType(DWORD nType)
+{
+#if 0
+ switch(nType)
+ {
+ case PRIMOSDK_CDR:
+ case PRIMOSDK_CDRW:
+ case PRIMOSDK_DVDR:
+ case PRIMOSDK_DVDRW:
+ case PRIMOSDK_DVDPR:
+ case PRIMOSDK_DVDPRW:
+ case PRIMOSDK_DVDRAM:
+ case PRIMOSDK_DDCDR:
+ case PRIMOSDK_DDCDRW:
+ case PRIMOSDK_DVDPR9:
+ case PRIMOSDK_DVDR9:
+ case PRIMOSDK_BDRE:
+ case PRIMOSDK_BDR:
+ case PRIMOSDK_HDDVDRW:
+ case PRIMOSDK_HDDVDR:
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+BOOL Drive_IsRecorder(CHAR cLetter)
+{
+#if 0
+ wchar_t info[128] = {0};
+ wchar_t name[] = L"cda://X.cda";
+ DWORD result;
+ BOOL reloaded = FALSE;
+
+ name[6] = cLetter;
+
+ for(;;)
+ {
+ result = getFileInfoW(name, L"cdinfo", info, sizeof(info)/sizeof(wchar_t));
+ if (result || reloaded || !getFileInfoW(name, L"reloadsonic", NULL, 0)) break;
+ reloaded = TRUE;
+ }
+
+ return (result) ? Drive_IsRecorderType(_wtoi(info)) : FALSE;
+#else
+ return FALSE;
+#endif
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/drive.h b/Src/Plugins/Library/ml_disc/drive.h
new file mode 100644
index 00000000..3bfe5b92
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/drive.h
@@ -0,0 +1,16 @@
+#ifndef NULLSOFT_MLDISC_DRIVE_HEADER
+#define NULLSOFT_MLDISC_DRIVE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+LPCWSTR Drive_GetTypeString(DWORD nType);
+LPCWSTR Drive_GetBusTypeString(DWORD nBusType);
+BOOL Drive_IsRecorderType(DWORD nType);
+BOOL Drive_IsRecorder(CHAR cLetter);
+
+#endif // NULLSOFT_MLDISC_DRIVE_HEADER
+
diff --git a/Src/Plugins/Library/ml_disc/driveListBox.cpp b/Src/Plugins/Library/ml_disc/driveListBox.cpp
new file mode 100644
index 00000000..8f5fbdf3
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/driveListBox.cpp
@@ -0,0 +1,255 @@
+#include "main.h"
+#include ".\DriveListBox.h"
+#include "resource.h"
+#include "..\..\General\gen_ml\graphics.h"
+#include <strsafe.h>
+
+DriveListBox::DriveListBox(int controlId)
+{
+ m_hwnd = NULL;
+ m_parentHwnd= NULL;
+ hInstance = NULL;
+ bmpNormal = NULL;
+ bmpSelected = NULL;
+ driveResId = NULL;
+ bgndResId = NULL;
+
+ this->controlId = controlId;
+
+ SetRect(&rcItem, 0,0,0,68); // hardcoded height
+
+ clrNormalBG = RGB(0,0,0);
+ clrSelected1 = RGB(0,0,0);
+ clrSelected2 = RGB(255,255,255);
+ clrTextSel = RGB(255,255,255);
+ clrTextNorm = RGB(0,255,0);
+}
+
+DriveListBox::~DriveListBox(void)
+{
+ DestroyImages();
+}
+
+void DriveListBox::DestroyImages(void)
+{
+ if (bmpNormal) DeleteObject(bmpNormal);
+ bmpNormal = NULL;
+
+ if (bmpSelected) DeleteObject(bmpSelected);
+ bmpSelected = NULL;
+}
+
+void DriveListBox::SetColors(COLORREF clrNormalBG, COLORREF clrSelected1, COLORREF clrSelected2, COLORREF clrTextSel, COLORREF clrTextNorm)
+{
+ this->clrNormalBG = clrNormalBG;
+ this->clrSelected1 = clrSelected1;
+ this->clrSelected2 = clrSelected2;
+ this->clrTextSel = clrTextSel;
+ this->clrTextNorm = clrTextNorm;
+ ReloadImages();
+}
+
+void DriveListBox::SetImages(HINSTANCE hInstance, int bgndResId, int driveResId)
+{
+ this->hInstance = hInstance;
+ this->bgndResId = bgndResId;
+ this->driveResId = driveResId;
+ ReloadImages();
+}
+
+HWND DriveListBox::GetHWND(void)
+{
+ return m_hwnd;
+}
+
+void DriveListBox::ReloadImages(void)
+{
+ DestroyImages();
+ if (!hInstance) return;
+
+ HBITMAP bmpBck = NULL, bmpDrive = NULL;
+ if (bgndResId)
+ {
+ bmpBck = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(bgndResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpBck) bmpBck = PatchBitmapColors24(bmpBck, clrSelected1, clrSelected2, Filter1);
+ }
+ if (driveResId)
+ {
+ bmpDrive = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(driveResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ }
+
+ CreateBitmaps(bmpBck, bmpDrive);
+ if (bmpBck) DeleteObject(bmpBck);
+ if (bmpDrive) DeleteObject(bmpDrive);
+}
+
+void DriveListBox::CreateBitmaps(HBITMAP bmpBck, HBITMAP bmpDrive)
+{
+ if (rcItem.right == 0 || rcItem.bottom == 0) return;
+
+ HBITMAP bmpDriveMask = NULL;
+ if (bmpDrive) bmpDriveMask = CreateBitmapMask(bmpDrive, RGB(255, 0, 255));
+
+ HDC hdc = GetDC(m_hwnd);
+ HBITMAP bmp;
+
+ for (int i = 0; i < 2; i++)
+ {
+ bmp = CreateCompatibleBitmap(hdc, rcItem.right, rcItem.bottom);
+ HDC memDstDC = CreateCompatibleDC (hdc);
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ HBITMAP obmp1 = (HBITMAP)SelectObject(memDstDC, bmp);
+ HBITMAP obmp2 = (HBITMAP)SelectObject(memSrcDC, bmpBck);
+
+ if (i == 0 )
+ {
+ for (int i = 0; i < rcItem.right; i++)
+ {
+ BitBlt(memDstDC, i, 0, 2, rcItem.bottom, memSrcDC, 0, 0, SRCCOPY);
+ }
+ }
+ else
+ {
+ HBRUSH hb = CreateSolidBrush(clrNormalBG);
+ FillRect(memDstDC, &rcItem, hb);
+ DeleteObject(hb);
+ }
+
+ BITMAP bm;
+ GetObject(bmpDrive, sizeof(BITMAP), &bm);
+ RECT r1 = {
+ max(2, (rcItem.right - bm.bmWidth) / 2),
+ max(2, (rcItem.bottom - 16 - bm.bmHeight) / 2),
+ min(rcItem.right - 4, bm.bmWidth),
+ min(rcItem.bottom -18, bm.bmHeight)
+ };
+ SelectObject(memSrcDC, bmpDriveMask);
+ BitBlt(memDstDC, r1.left, r1.top, r1.right, r1.bottom, memSrcDC, 0,0, SRCAND);
+
+ SelectObject(memSrcDC, bmpDrive);
+ BitBlt(memDstDC, r1.left, r1.top, r1.right, r1.bottom, memSrcDC, 0,0, SRCPAINT);
+
+ SelectObject(memDstDC, obmp1);
+ SelectObject(memSrcDC, obmp2);
+
+ DeleteDC(memDstDC);
+ DeleteDC(memSrcDC);
+ if (i == 0) bmpSelected = bmp;
+ else bmpNormal = bmp;
+ }
+
+ ReleaseDC(m_hwnd, hdc);
+ DeleteObject(bmpDriveMask);
+}
+
+void DriveListBox::Init(HWND hwnd)
+{
+ m_hwnd = hwnd;
+ m_parentHwnd = GetParent(hwnd);
+}
+
+int DriveListBox::HandleMsgProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_DRAWITEM:
+ if (wParam == (WPARAM)controlId) DrawItem((DRAWITEMSTRUCT *)lParam);
+ break;
+ case WM_MEASUREITEM:
+ if (wParam == (WPARAM)controlId && MeasureItem((LPMEASUREITEMSTRUCT)lParam)) return 1;
+ break;
+
+ case WM_CTLCOLORLISTBOX:
+ SetBkColor((HDC)wParam, clrNormalBG);
+ return NULL;
+ }
+ return FALSE;
+}
+
+void DriveListBox::DrawItem(LPDRAWITEMSTRUCT di)
+{
+ if(di->CtlType == ODT_LISTBOX)
+ {
+ if (di->itemID == -1) return;
+
+ RECT r;
+ r=di->rcItem;
+
+ if (!bmpSelected || !bmpNormal || ((r.right - r.left) != rcItem.right))
+ {
+ SetRect(&rcItem, 0,0,r.right - r.left, rcItem.bottom);
+ ReloadImages();
+ }
+
+ HBITMAP bmp;
+ int color;
+ if (di->itemState & ODS_SELECTED)
+ {
+ bmp = bmpSelected;
+ color = clrTextSel;
+ }
+ else
+ {
+ bmp = bmpNormal;
+ color = clrTextNorm;
+ }
+
+ RECT rc;
+ GetClientRect(di->hwndItem, &rc);
+
+ HRGN rgnW = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
+ // HRGN rgn = CreateRoundRectRgn(r.left +2, r.top + 2, r.right - 2, r.bottom -2, 10, 8);
+ HRGN rgn = CreateRectRgn(r.left +2, r.top + 2, r.right - 2, r.bottom -2);
+
+ CombineRgn(rgn, rgn, rgnW, RGN_AND);
+ SelectClipRgn(di->hDC, rgn);
+ DeleteObject(rgn);
+ DeleteObject(rgnW);
+
+ if (bmp)
+ {
+ HDC hdcbmp = CreateCompatibleDC(di->hDC);
+ HBITMAP obmp = (HBITMAP)SelectObject(hdcbmp,bmp);
+ StretchBlt(di->hDC,
+ r.left,
+ r.top,
+ rcItem.right,
+ rcItem.bottom,
+ hdcbmp,
+ 0,
+ 0,
+ rcItem.right,
+ rcItem.bottom,
+ SRCCOPY);
+ SelectObject(hdcbmp,obmp);
+ DeleteDC(hdcbmp);
+ }
+
+ InflateRect(&r, -2, -2);
+
+ if ( (di->itemState & ODS_SELECTED) && GetFocus() == di->hwndItem)
+ {
+ DrawFocusRect(di->hDC, &r);
+ }
+
+ SetBkMode(di->hDC, TRANSPARENT);
+ SetTextColor(di->hDC, color);
+
+ RECT textRect = {r.left + 2, r.bottom - 20, r.right - 2, r.bottom - 6};
+
+ wchar_t str[256] = {0};
+ INT nType;
+ nType = 0xFFFF & ((DWORD)di->itemData >> 16);
+ StringCchPrintfW(str, 256, WASABI_API_LNGSTRINGW(IDS_X_DRIVE_X),
+ (nType) ? Drive_GetTypeString(nType) : L"",
+ (CHAR)(0xFF & di->itemData));
+ DrawTextW(di->hDC, str,-1,&textRect,DT_BOTTOM|DT_SINGLELINE|DT_CENTER);
+ }
+ return;
+}
+
+int DriveListBox::MeasureItem(LPMEASUREITEMSTRUCT mi)
+{
+ mi->itemHeight = rcItem.bottom;
+ return 1;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/driveListBox.h b/Src/Plugins/Library/ml_disc/driveListBox.h
new file mode 100644
index 00000000..95d0c4e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/driveListBox.h
@@ -0,0 +1,50 @@
+#ifndef NULLSOFT_DRIVE_COMBOBOX_HEADER
+#define NULLSOFT_DRIVE_COMBOBOX_HEADER
+
+#include "windows.h"
+
+class DriveListBox
+{
+public:
+ DriveListBox(int controlId);
+ ~DriveListBox(void);
+
+public:
+ void SetColors(COLORREF clrNormalBG, COLORREF clrSelected1, COLORREF clrSelected2, COLORREF clrTextSel, COLORREF clrTextNorm);
+ void SetImages(HINSTANCE hInstance, int bgndResId, int driveResId);
+ void Init(HWND hwnd);
+ int HandleMsgProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+ void ReloadImages(void);
+ HWND GetHWND(void);
+
+private:
+ void DestroyImages(void);
+ void CreateBitmaps(HBITMAP bmpBck, HBITMAP bmpDrive);
+
+ void DrawItem(LPDRAWITEMSTRUCT di);
+ int MeasureItem(LPMEASUREITEMSTRUCT mi);
+
+private:
+
+ HWND m_hwnd, m_parentHwnd;
+
+ HINSTANCE hInstance;
+
+ HBITMAP bmpNormal;
+ HBITMAP bmpSelected;
+
+ int driveResId;
+ int bgndResId;
+
+ RECT rcItem;
+
+ int controlId;
+
+ COLORREF clrNormalBG;
+ COLORREF clrSelected1;
+ COLORREF clrSelected2;
+ COLORREF clrTextSel;
+ COLORREF clrTextNorm;
+};
+
+#endif // NULLSOFT_DRIVE_COMBOBOX_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/drivemngr.cpp b/Src/Plugins/Library/ml_disc/drivemngr.cpp
new file mode 100644
index 00000000..b2d87fcc
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/drivemngr.cpp
@@ -0,0 +1,1910 @@
+#include "main.h"
+#include "./drivemngr.h"
+#include "./primosdk_helper.h"
+#include "./resource.h"
+#include "../nu/trace.h"
+#include "./dbt.h"
+#include "./spti.h"
+#include <setupapi.h>
+
+#include <imapi.h>
+#include <strsafe.h>
+
+#define LISTENER_CLASSNAME L"MLDISCLISTENER"
+#define LISTENER_WINDOWNAME L""
+
+#define POLLMEDIUMCHANGE_INTERVAL 2000
+#define POLLMEDIUMVALIDATE_INTERVAL 6000
+
+#define DMS_SUSPENDED 0x0000
+#define DMS_ACTIVE 0x0001
+
+#define WM_EX_QUIT (WM_APP + 1)
+
+typedef struct _MEDIUMINFO_I
+{
+ UINT msLastPolled; // last time medium info was polled
+ UINT serialNumber; // medium serialnumber
+} MEDIUMINFO_I;
+
+typedef struct _DRIVEINFO_I
+{
+ char cLetter; // drive letter
+ INT deviceNumber; // system assigned device number (unique till next reboot)
+ BOOL bMediumInserted; // if TRUE mediumInfo contains valid data
+ CHAR cMode; // drive mode
+ DWORD dwType; // drive type
+ LPWSTR pszDevName; // device name
+ HANDLE hThread; // device info thread
+ DWORD dwThreadId;
+ MEDIUMINFO_I mediumInfo;
+} DRIVEINFO_I;
+
+typedef struct _DRIVEMNGR
+{
+ HWND hwndListener;
+ DMNPROC callback;
+ UINT fState;
+ CRITICAL_SECTION csLock;
+
+ DRIVEINFO_I *pDrives;
+ INT nCount;
+ INT nAlloc;
+
+ HANDLE hPollingThread;
+ DWORD dwPollingThread;
+} DRIVEMNGR;
+
+typedef struct _DEVICEINFO
+{
+ CHAR cLetter;
+ LPWSTR pszDevName;
+ WCHAR szTargetPath[128];
+ WCHAR szVolumeName[64];
+ DWORD dwType;
+ INT deviceNumber;
+ INT opCode;
+} DEVICEINFO;
+
+static DRIVEMNGR *pMngr = NULL;
+
+
+static void CALLBACK APC_CheckDrives(ULONG_PTR param);
+static void CALLBACK APC_IsMediumChanged(ULONG_PTR param);
+
+static void CALLBACK APC_GetUnitInfo(ULONG_PTR param);
+static void CALLBACK APC_GetUnitInfo2(ULONG_PTR param);
+static void CALLBACK APC_GetDiscInfoEx(ULONG_PTR param);
+static void CALLBACK APC_GetDiscInfo2(ULONG_PTR param);
+static void CALLBACK APC_GetTitle(ULONG_PTR param);
+static void CALLBACK APC_DriveScan(ULONG_PTR param);
+static void CALLBACK APC_GetMCIInfo(ULONG_PTR param);
+static void CALLBACK APC_GetIMAPIInfo(ULONG_PTR param);
+static void CALLBACK APC_Eject(ULONG_PTR param);
+
+static DWORD CALLBACK InfoThread(LPVOID param);
+
+static DWORD CALLBACK PollingThread(LPVOID param);
+
+static void CALLBACK PollMediumInfo(ULONG_PTR param);
+
+static LRESULT WINAPI ListenerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+static CHAR CheckLetter(CHAR cLetter)
+{
+ if (cLetter < 'A' || cLetter > 'Z')
+ {
+ if (cLetter >= 'a' && cLetter <= 'z') return (cLetter - 0x20);
+ return 0;
+ }
+ return cLetter;
+}
+
+static LPCWSTR GetDeviceName(CHAR cLetter)
+{
+ LPCWSTR pszDevName;
+ if (!pMngr) return NULL;
+ pszDevName = NULL;
+
+ EnterCriticalSection(&pMngr->csLock);
+ for (int i = 0; i < pMngr->nCount; i++)
+ {
+ if (pMngr->pDrives[i].cLetter == cLetter)
+ {
+ pszDevName = pMngr->pDrives[i].pszDevName;
+ break;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ return pszDevName;
+}
+
+static BOOL IsPollingRequired(void)
+{
+ HKEY hKey;
+ LONG result;
+ BOOL bAutoRunEnabled;
+
+ bAutoRunEnabled = FALSE;
+
+ result = RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\Cdrom"), &hKey);
+ if (ERROR_SUCCESS == result)
+ {
+ DWORD value;
+ DWORD size;
+ size = sizeof(DWORD);
+ result = RegQueryValueEx(hKey, TEXT("AutoRun"), NULL, NULL, (LPBYTE)&value, &size);
+ if (ERROR_SUCCESS == result) bAutoRunEnabled = (0 != value);
+
+ RegCloseKey(hKey);
+ }
+ return !bAutoRunEnabled;
+
+}
+static CHAR Drive_LetterFromMask(ULONG unitmask)
+{
+ char i;
+ for (i = 0; i < 26; ++i)
+ {
+ if (unitmask & 0x1) break;
+ unitmask = unitmask >> 1;
+ }
+ return (i + 'A');
+}
+
+static BOOL Drive_Add(DEVICEINFO *pDevInfo)
+{
+ DRIVEINFO_I *pDrive;
+ if (!pMngr) return FALSE;
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ INT index, opCode;
+
+ opCode = 0;
+ for (index = 0; index < pMngr->nCount && pMngr->pDrives[index].cLetter != pDevInfo->cLetter; index++);
+ if (index != pMngr->nCount)
+ {
+ pDrive = &pMngr->pDrives[index];
+ if (pDrive->deviceNumber != pDevInfo->deviceNumber || pDrive->dwType != pDevInfo->dwType) opCode = 1;
+ }
+ else
+ {
+ if (pMngr->nCount == pMngr->nAlloc)
+ {
+ LPVOID data;
+ data = realloc(pMngr->pDrives, sizeof(DRIVEINFO_I)*(pMngr->nCount + 2));
+ if (!data)
+ {
+ LeaveCriticalSection(&pMngr->csLock);
+ return FALSE;
+ }
+ pMngr->pDrives = (DRIVEINFO_I*)data;
+ pMngr->nAlloc += 2;
+ }
+ pDrive = &pMngr->pDrives[pMngr->nCount];
+ pMngr->nCount++;
+
+ ZeroMemory(pDrive, sizeof(DRIVEINFO_I));
+ pDrive->cLetter = pDevInfo->cLetter;
+ opCode = 2;
+ }
+
+ if (opCode)
+ {
+ pDrive->deviceNumber = pDevInfo->deviceNumber;
+ pDrive->dwType = pDevInfo->dwType;
+ if (pDrive->pszDevName) free(pDrive->pszDevName);
+ pDrive->pszDevName = _wcsdup(pDevInfo->pszDevName);
+ }
+
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (opCode && pMngr->callback) pMngr->callback((2 == opCode) ? DMW_DRIVEADDED : DMW_DRIVECHANGED, pDevInfo->cLetter);
+
+ return TRUE;
+}
+
+static BOOL Drive_Remove(CHAR cLetter)
+{
+ INT index;
+ BOOL bReportChanges;
+ if (!pMngr) return FALSE;
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ bReportChanges = FALSE;
+ index = pMngr->nCount;
+ while (index-- && pMngr->pDrives[index].cLetter != cLetter);
+
+ if (-1 != index)
+ {
+ if (pMngr->pDrives[index].pszDevName) free(pMngr->pDrives[index].pszDevName);
+ if (index != pMngr->nCount - 1) MoveMemory(&pMngr->pDrives[index], &pMngr->pDrives[index + 1], sizeof(DRIVEINFO_I)*(pMngr->nCount - index));
+ pMngr->nCount--;
+ bReportChanges = TRUE;
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (bReportChanges && pMngr->callback) pMngr->callback(DMW_DRIVEREMOVED, cLetter);
+ return TRUE;
+}
+
+static HRESULT QueueInfoAPC(CHAR cLetter, PAPCFUNC pfnAPC, ULONG_PTR param)
+{
+ DWORD *pdwThreadId(NULL);
+ HRESULT hr(S_FALSE);
+ HANDLE *phThread(NULL);
+ static HANDLE hDrivesInfoThread = NULL;
+
+ if (NULL == pMngr) return E_FAIL;
+
+ EnterCriticalSection(&pMngr->csLock);
+ if (cLetter)
+ {
+ INT index = pMngr->nCount;
+ while (index-- && pMngr->pDrives[index].cLetter != cLetter);
+ if (-1 != index)
+ {
+ phThread = &pMngr->pDrives[index].hThread;
+ pdwThreadId = &pMngr->pDrives[index].dwThreadId;
+ }
+ }
+ else
+ {
+ phThread = &hDrivesInfoThread;
+ }
+
+ if (phThread)
+ {
+ if (!*phThread)
+ {
+ DWORD tid;
+ *phThread = CreateThread(NULL, 0, InfoThread, NULL, 0, &tid);
+ if (pdwThreadId) *pdwThreadId = tid;
+ Sleep(100);
+ }
+ if (*phThread)
+ {
+ if (0 == QueueUserAPC(pfnAPC, *phThread, param))
+ {
+ TRACE_LINE(TEXT("queue user apc failed"));
+ }
+ else hr = S_OK;
+ }
+ }
+
+ LeaveCriticalSection(&pMngr->csLock);
+
+ return hr;
+}
+
+static BOOL Medium_Add(CHAR cLetter, DWORD serial)
+{
+ INT index;
+ if (!pMngr) return FALSE;
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ index = pMngr->nCount;
+ while (index-- && pMngr->pDrives[index].cLetter != cLetter);
+ if (-1 != index)
+ {
+ pMngr->pDrives[index].bMediumInserted = TRUE;
+ pMngr->pDrives[index].mediumInfo.msLastPolled = 0;
+ pMngr->pDrives[index].mediumInfo.serialNumber = serial;
+ }
+
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (-1 != index)
+ {
+ if (-1 == serial) QueueInfoAPC(cLetter, APC_IsMediumChanged, (ULONG_PTR)cLetter);
+ if (pMngr->callback) pMngr->callback(DMW_MEDIUMARRIVED, cLetter);
+ }
+
+ return TRUE;
+}
+
+static BOOL Medium_Remove(CHAR cLetter)
+{
+ INT index;
+ if (!pMngr) return FALSE;
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ index = pMngr->nCount;
+ while (index-- && pMngr->pDrives[index].cLetter != cLetter);
+ if (-1 != index) pMngr->pDrives[index].bMediumInserted = FALSE;
+
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (-1 != index && pMngr->callback) pMngr->callback(DMW_MEDIUMREMOVED, cLetter);
+
+ return TRUE;
+}
+
+BOOL DriveManager_Initialize(DMNPROC DMNProc, BOOL bSuspended)
+{
+ WNDCLASSW wc = {0};
+ HINSTANCE hInstance;
+
+ if (pMngr || !DMNProc) return FALSE;
+
+ pMngr = (DRIVEMNGR*)calloc(1, sizeof(DRIVEMNGR));
+ if (!pMngr) return FALSE;
+
+ hInstance = GetModuleHandle(NULL);
+
+ if (!GetClassInfoW(hInstance, LISTENER_CLASSNAME, &wc))
+ {
+ wc.hInstance = hInstance;
+ wc.lpfnWndProc = ListenerWndProc;
+ wc.lpszClassName = LISTENER_CLASSNAME;
+ if (!RegisterClassW(&wc))
+ {
+ DriveManager_Uninitialize(0);
+ return FALSE;
+ }
+ }
+ pMngr->hwndListener = CreateWindowW(LISTENER_CLASSNAME, LISTENER_WINDOWNAME, WS_DISABLED, 0,0,0,0, HWND_DESKTOP, NULL, hInstance, 0L);
+ if (!pMngr->hwndListener)
+ {
+ DriveManager_Uninitialize(0);
+ return FALSE;
+ }
+ InitializeCriticalSection(&pMngr->csLock);
+ pMngr->callback = DMNProc;
+
+ return TRUE;
+}
+
+BOOL DriveManager_Uninitialize(INT msExitWaitTime)
+{
+ if (pMngr)
+ {
+ WNDCLASSW wc;
+ HINSTANCE hInstance;
+
+ if (pMngr->hwndListener) DestroyWindow(pMngr->hwndListener);
+
+ hInstance = GetModuleHandle(NULL);
+ if (GetClassInfoW(hInstance, LISTENER_CLASSNAME, &wc)) UnregisterClassW(LISTENER_CLASSNAME, hInstance);
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ for (int index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].hThread)
+ {
+ PostThreadMessage(pMngr->pDrives[index].dwThreadId, WM_EX_QUIT, 1, 0);
+ INT result = WaitForSingleObject(pMngr->pDrives[index].hThread, msExitWaitTime);
+ if (WAIT_TIMEOUT == result) TerminateThread(pMngr->pDrives[index].hThread, 1);
+ CloseHandle(pMngr->pDrives[index].hThread);
+ pMngr->pDrives[index].hThread = NULL;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (pMngr->hPollingThread)
+ {
+ PostThreadMessage(pMngr->dwPollingThread, WM_EX_QUIT, 1, 0);
+ INT result = WaitForSingleObject(pMngr->hPollingThread, msExitWaitTime);
+ if (WAIT_TIMEOUT == result) TerminateThread(pMngr->hPollingThread, 1);
+ CloseHandle(pMngr->hPollingThread);
+ pMngr->hPollingThread = NULL;
+ }
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ if (pMngr->pDrives)
+ {
+ free(pMngr->pDrives);
+ }
+
+ DRIVEMNGR *managerInstance = pMngr;
+ pMngr = NULL;
+
+ LeaveCriticalSection(&managerInstance->csLock);
+ DeleteCriticalSection(&managerInstance->csLock);
+
+ free(managerInstance);
+
+ PrimoSDKHelper_Uninitialize();
+
+ }
+ return TRUE;
+}
+
+BOOL DriveManager_Suspend(void)
+{
+ if (!pMngr) return FALSE;
+
+ pMngr->fState = DMS_SUSPENDED;
+ if (pMngr->hPollingThread)
+ {
+ PostThreadMessage(pMngr->dwPollingThread, WM_EX_QUIT, 1, 0);
+ pMngr->hPollingThread = NULL;
+ }
+ return TRUE;
+}
+
+BOOL DriveManager_Update(BOOL bAsync)
+{
+ if (bAsync) return (QueueInfoAPC(0, APC_DriveScan, 0) && QueueInfoAPC(0, PollMediumInfo, 0));
+ else
+ {
+ APC_DriveScan(0);
+ QueueInfoAPC(0, PollMediumInfo, 0);
+ }
+ return TRUE;
+}
+
+BOOL DriveManager_Resume(BOOL bUpdate)
+{
+ if (!pMngr) return FALSE;
+ pMngr->fState = DMS_ACTIVE;
+
+ EnterCriticalSection(&pMngr->csLock);
+ for (int index =0; index < pMngr->nCount; index++) pMngr->pDrives[index].mediumInfo.msLastPolled = 0;
+ LeaveCriticalSection(&pMngr->csLock);
+
+ APC_DriveScan(0);
+ QueueInfoAPC(0, PollMediumInfo, 0);
+
+ if (NULL == pMngr->hPollingThread && IsPollingRequired())
+ {
+ pMngr->hPollingThread = CreateThread(NULL, 0, PollingThread, NULL, 0, &pMngr->dwPollingThread);
+ }
+
+ return TRUE;
+}
+
+BOOL DriveManager_SetDriveMode(CHAR cLetter, CHAR cMode)
+{
+ BOOL report;
+ INT index;
+
+ index = -1;
+ report = FALSE;
+ cLetter = CheckLetter(cLetter);
+
+ if (pMngr && cLetter)
+ {
+ EnterCriticalSection(&pMngr->csLock);
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == cLetter)
+ {
+ if (pMngr->pDrives[index].cMode != cMode)
+ {
+ pMngr->pDrives[index].cMode = cMode;
+ report = TRUE;
+ }
+ break;
+ }
+ }
+ if (index == pMngr->nCount) index = -1;
+ LeaveCriticalSection(&pMngr->csLock);
+ if (report && pMngr->callback) pMngr->callback(DMW_MODECHANGED, MAKEWORD(cLetter, cMode));
+ }
+
+ return (-1 != index);
+}
+
+CHAR DriveManager_GetDriveMode(CHAR cLetter)
+{
+ CHAR result;
+
+ result = DM_MODE_ERROR;
+ cLetter = CheckLetter(cLetter);
+
+ if (pMngr && cLetter)
+ {
+ INT index;
+ EnterCriticalSection(&pMngr->csLock);
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == cLetter)
+ {
+ result = pMngr->pDrives[index].cMode;
+ break;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+ }
+ return result;
+}
+
+DWORD DriveManager_GetDriveType(CHAR cLetter)
+{
+ DWORD type;
+
+ type = DRIVE_TYPE_UNKNOWN | DRIVE_CAP_UNKNOWN;
+ cLetter = CheckLetter(cLetter);
+
+ if (pMngr && cLetter)
+ {
+ INT index;
+ EnterCriticalSection(&pMngr->csLock);
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == cLetter)
+ {
+ type = pMngr->pDrives[index].dwType;
+ break;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+ }
+ return type;
+}
+
+BOOL DriveManager_IsMediumInserted(CHAR cLetter)
+{
+ BOOL result;
+
+ result = FALSE;
+ cLetter = CheckLetter(cLetter);
+
+ if (pMngr && cLetter)
+ {
+ INT index;
+ EnterCriticalSection(&pMngr->csLock);
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == cLetter)
+ {
+ result = pMngr->pDrives[index].bMediumInserted;
+ break;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+ }
+ return result;
+}
+
+INT DriveManager_GetDriveList(CHAR *pLetters, INT cchSize)
+{
+ INT r = 0;
+ if (!pLetters || !pMngr) return -1;
+ EnterCriticalSection(&pMngr->csLock);
+ for (int index =0; index < pMngr->nCount; index++)
+ {
+ *pLetters = pMngr->pDrives[index].cLetter;
+ pLetters++;
+ cchSize--;
+ r++;
+ if (0 == cchSize) break;
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+ return r;
+}
+
+static BOOL QueueInfoJob(PAPCFUNC pfnAPC, DM_NOTIFY_PARAM *pHeader)
+{
+ BOOL result(TRUE);
+ if (!pMngr || !pHeader) result = FALSE;
+
+ if (result)
+ {
+ HANDLE hProc = GetCurrentProcess();
+ pHeader->hReserved = 0;
+ result = (BOOL)DuplicateHandle(hProc, GetCurrentThread(), hProc, &pHeader->hReserved,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ if (!result) pHeader->hReserved = 0;
+ }
+
+ if (result)
+ {
+ CHAR cLetter = CheckLetter(pHeader->cLetter);
+ result = (cLetter && S_OK == QueueInfoAPC(cLetter, pfnAPC, (ULONG_PTR)pHeader));
+ }
+
+ if(!result && pHeader && pHeader->fnFree)
+ {
+ if (pHeader->hReserved) CloseHandle(pHeader->hReserved);
+ pHeader->fnFree(pHeader);
+ }
+ return result;
+}
+
+BOOL DriveManager_GetUnitInfo(DM_UNITINFO_PARAM *puip)
+{
+ return QueueInfoJob(APC_GetUnitInfo, (DM_NOTIFY_PARAM*)puip);
+}
+
+BOOL DriveManager_GetUnitInfo2(DM_UNITINFO2_PARAM *puip)
+{
+ return QueueInfoJob(APC_GetUnitInfo2, (DM_NOTIFY_PARAM*)puip);
+}
+
+BOOL DriveManager_GetDiscInfoEx(DM_DISCINFOEX_PARAM *pdip)
+{
+ return QueueInfoJob(APC_GetDiscInfoEx, (DM_NOTIFY_PARAM*)pdip);
+}
+BOOL DriveManager_GetDiscInfo2(DM_DISCINFO2_PARAM *pdip)
+{
+ return QueueInfoJob(APC_GetDiscInfo2, (DM_NOTIFY_PARAM*)pdip);
+}
+
+BOOL DriveManager_QueryTitle(DM_TITLE_PARAM *pdtp)
+{
+ return QueueInfoJob(APC_GetTitle, (DM_NOTIFY_PARAM*)pdtp);
+}
+
+BOOL DriveManager_GetMCIInfo(DM_MCI_PARAM *pmcip)
+{
+ return QueueInfoJob(APC_GetMCIInfo, (DM_NOTIFY_PARAM*)pmcip);
+}
+
+BOOL DriveManager_GetIMAPIInfo(DM_IMAPI_PARAM *pIMAPI)
+{
+ return QueueInfoJob(APC_GetIMAPIInfo, (DM_NOTIFY_PARAM*)pIMAPI);
+}
+BOOL DriveManager_Eject(CHAR cLetter, INT nCmd)
+{
+ if (!pMngr) return FALSE;
+ CHAR cLetter1 = CheckLetter(cLetter);
+
+ return (cLetter1 && QueueInfoAPC(cLetter1, APC_Eject, (ULONG_PTR)MAKELONG(cLetter, nCmd)));
+}
+
+BOOL DriveManager_IsUnitReady(CHAR cLetter, BOOL *pbReady)
+{
+ BYTE sc, asc, ascq;
+
+ BOOL bSuccess;
+ HANDLE hDevice;
+
+ *pbReady = FALSE;
+ hDevice = CreateFileW(GetDeviceName(cLetter), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
+
+ if (INVALID_HANDLE_VALUE == hDevice) return FALSE;
+
+ bSuccess = SPTI_TestUnitReady(hDevice, &sc, &asc, &ascq, 3);
+ if (!bSuccess)
+ {
+ if (ERROR_SEM_TIMEOUT == GetLastError()) bSuccess = TRUE;
+ }
+ else if (0x00 == sc || (0x02 == sc && 0x3A == asc)) *pbReady = TRUE;
+
+ CloseHandle(hDevice);
+
+ return bSuccess;
+}
+
+static BOOL GetVolumeNameForVolumeMountPoint_DL(LPCWSTR lpszVolumeMountPoint, LPWSTR lpszVolumeName, DWORD cchBufferLength)
+{
+ static BOOL (WINAPI *func)(LPCWSTR, LPWSTR, DWORD) = NULL;
+ if (!func)
+ {
+ UINT prevErrorMode;
+ HMODULE hModule;
+ prevErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ hModule = LoadLibraryW(L"Kernel32.dll");
+ SetErrorMode(prevErrorMode);
+ if (hModule)
+ {
+ func = (BOOL (WINAPI*)(LPCWSTR, LPWSTR, DWORD))GetProcAddress(hModule, "GetVolumeNameForVolumeMountPointW");
+ FreeLibrary(hModule);
+ }
+ }
+ return (func) ? func(lpszVolumeMountPoint, lpszVolumeName, cchBufferLength) : FALSE;
+}
+
+static DWORD GetDeviceNames(DEVICEINFO *pDevInfo, INT count)
+{
+ HANDLE hDevInfo;
+ SP_DEVICE_INTERFACE_DATA spiData;
+ SP_DEVICE_INTERFACE_DETAIL_DATA_W *pspiDetailData;
+ DWORD dwErrorCode, dwReqSize, dwDetailSize;
+ wchar_t volume[128], szDosName[] = L"X:\\", szDosName1[] = L"X:\\";
+
+ if (!pDevInfo || !count) return ERROR_INVALID_DATA;
+
+ for (int i = 0; i < count; i++)
+ {
+ szDosName[0] = pDevInfo[i].cLetter;
+ GetVolumeNameForVolumeMountPoint_DL(szDosName, pDevInfo[i].szVolumeName, sizeof(pDevInfo[i].szVolumeName)/sizeof(wchar_t));
+ szDosName1[0] = pDevInfo[i].cLetter;
+ QueryDosDeviceW(szDosName1, pDevInfo[i].szTargetPath, sizeof(pDevInfo[i].szTargetPath)/sizeof(wchar_t));
+ }
+
+ hDevInfo = SetupDiGetClassDevs((LPGUID)&CdRomClassGuid, NULL, NULL, (DIGCF_PRESENT | DIGCF_INTERFACEDEVICE));
+ if (INVALID_HANDLE_VALUE == hDevInfo) return GetLastError();
+
+ dwDetailSize = 0;
+ pspiDetailData = NULL;
+ spiData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+ dwErrorCode = 0;
+
+ for (int index = 0; !dwErrorCode; index++)
+ {
+ BOOL bResult = SetupDiEnumDeviceInterfaces(hDevInfo, 0, (LPGUID)&CdRomClassGuid, index, &spiData);
+ if (!bResult)
+ {
+ dwErrorCode = GetLastError();
+ break;
+ }
+
+ bResult = SetupDiGetDeviceInterfaceDetailW(hDevInfo, &spiData, NULL, 0, &dwReqSize, NULL);
+ if (!bResult)
+ {
+ dwErrorCode = GetLastError();
+ if (ERROR_INSUFFICIENT_BUFFER != dwErrorCode) break;
+ dwErrorCode = 0;
+ }
+ dwReqSize += 2*sizeof(wchar_t);
+ if (dwReqSize > dwDetailSize)
+ {
+ LPVOID data;
+ data = realloc(pspiDetailData, dwReqSize);
+ if (!data) { dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; break; }
+ pspiDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA*)data;
+ dwDetailSize = dwReqSize;
+ }
+
+ pspiDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
+ bResult = SetupDiGetDeviceInterfaceDetailW(hDevInfo, &spiData, pspiDetailData, dwDetailSize, NULL, NULL);
+ if (!bResult)
+ {
+ dwErrorCode = GetLastError();
+ break;
+ }
+
+ INT cchName;
+ cchName = lstrlenW(pspiDetailData->DevicePath);
+ pspiDetailData->DevicePath[cchName] = L'\\';
+ pspiDetailData->DevicePath[cchName + 1] = 0x00;
+
+ if(GetVolumeNameForVolumeMountPoint_DL(pspiDetailData->DevicePath, volume, sizeof(volume)/sizeof(wchar_t)))
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (!pDevInfo[i].pszDevName && 0 == lstrcmpW(volume, pDevInfo[i].szVolumeName))
+ {
+ pDevInfo[i].pszDevName = (LPWSTR)calloc((cchName + 1), sizeof(wchar_t));
+ if (pDevInfo[i].pszDevName) StringCchCopyNW(pDevInfo[i].pszDevName, cchName + 1, pspiDetailData->DevicePath, cchName);
+ break;
+ }
+ }
+ }
+ }
+ if (pspiDetailData) free(pspiDetailData);
+ SetupDiDestroyDeviceInfoList(hDevInfo);
+
+ for (int i = 0; i < count; i++)
+ {
+ if (!pDevInfo[i].pszDevName)
+ {
+ wchar_t szDevName[] = L"\\\\.\\x:";
+ szDevName[4] = pDevInfo[i].cLetter;
+ pDevInfo[i].pszDevName = (LPWSTR)calloc(sizeof(szDevName) + 2, sizeof(wchar_t));
+ if (pDevInfo[i].pszDevName) StringCbCopyW(pDevInfo[i].pszDevName, sizeof(szDevName) + 2, szDevName);
+ }
+ }
+
+ return dwErrorCode;
+}
+
+static void GetDeviceCaps(DEVICEINFO *pDevInfo, INT count)
+{
+ for( int i = 0; i < count; i++)
+ {
+ pDevInfo[i].dwType = ((pDevInfo[i].dwType & 0x0000FFFF) | DRIVE_CAP_UNKNOWN);
+ }
+
+ // TODO come back to this later on, but for the moment not seeing any noticeable issues
+ // with disabling this and instead and instead it helps prevent random trk****.tmp
+ // files being generated and also seems to fix the crash on start people have here
+ /*IDiscMaster *pdm;
+ IDiscRecorder *pdr;
+ IEnumDiscRecorders *per;
+ ULONG nActual;
+ HRESULT hr = CoCreateInstance(CLSID_MSDiscMasterObj, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IID_IDiscMaster, (void**)&pdm);
+ if (SUCCEEDED(hr))
+ {
+ // TODO determine why this is causing trk*.tmp files to be created when called
+ // which ends up spamming the %temp% folder everytime Winamp starts :o(
+ hr = pdm->Open();
+ if (SUCCEEDED(hr))
+ {
+ IEnumDiscMasterFormats *pef;
+ hr = pdm->EnumDiscMasterFormats(&pef);
+ if (SUCCEEDED(hr))
+ {
+ IID pFormats[2];
+ hr = pef->Next(sizeof(pFormats)/sizeof(IID), pFormats, &nActual);
+ if (SUCCEEDED(hr))
+ {
+ while(nActual--) { if (IID_IRedbookDiscMaster == pFormats[nActual]) break; }
+ if (nActual != ((ULONG)-1))
+ {
+ IRedbookDiscMaster *pdf;
+ hr = pdm->SetActiveDiscMasterFormat(IID_IRedbookDiscMaster, (void**)&pdf);
+ if (SUCCEEDED(hr))
+ {
+ pdf->Release();
+ hr = pdm->EnumDiscRecorders(&per);
+ if (SUCCEEDED(hr))
+ {
+ while (S_OK== per->Next(1, &pdr, &nActual) && nActual > 0)
+ {
+ BSTR bstrPath;
+ hr = pdr->GetPath(&bstrPath);
+ if (SUCCEEDED(hr))
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (0 == lstrcmpW(pDevInfo[i].szTargetPath, bstrPath))
+ {
+ LONG type;
+ if (SUCCEEDED(pdr->GetRecorderType(&type)))
+ {
+ pDevInfo[i].dwType &= 0x0000FFFF;
+ switch(type)
+ {
+ case RECORDER_CDR: pDevInfo[i].dwType |= DRIVE_CAP_R; break;
+ case RECORDER_CDRW: pDevInfo[i].dwType |= DRIVE_CAP_RW; break;
+ }
+ }
+ break;
+ }
+ }
+ if (bstrPath) SysFreeString(bstrPath);
+ }
+ pdr->Release();
+ }
+ per->Release();
+ }
+ }
+ }
+ }
+ pef->Release();
+ }
+ pdm->Close();
+ }
+ pdm->Release();
+ }
+ else
+ {
+ }*/
+}
+
+static void Listener_OnDeviceChange(HWND hwnd, UINT nType, DWORD_PTR dwData)
+{
+ DEV_BROADCAST_HDR *phdr;
+
+ switch(nType)
+ {
+ case DBT_DEVICEARRIVAL:
+ phdr = (DEV_BROADCAST_HDR*)dwData;
+ if (DBT_DEVTYP_VOLUME == phdr->dbch_devicetype)
+ {
+ DEV_BROADCAST_VOLUME *pvol = (DEV_BROADCAST_VOLUME*)phdr;
+ if (DBTF_MEDIA == pvol->dbcv_flags) Medium_Add(Drive_LetterFromMask(pvol->dbcv_unitmask), (DWORD)-1);
+ else if (0 == pvol->dbcv_flags)
+ {
+ char root[] = "X:\\";
+ root[0] = Drive_LetterFromMask(pvol->dbcv_unitmask);
+ if (DRIVE_CDROM == GetDriveTypeA(root)) QueueInfoAPC(0, APC_CheckDrives, (ULONG_PTR)root[0]);
+ }
+ }
+ break;
+ case DBT_DEVICEREMOVECOMPLETE:
+ phdr = (DEV_BROADCAST_HDR*)dwData;
+ if (DBT_DEVTYP_VOLUME == phdr->dbch_devicetype)
+ {
+ DEV_BROADCAST_VOLUME *pvol = (DEV_BROADCAST_VOLUME*)phdr;
+ if (DBTF_MEDIA == pvol->dbcv_flags) Medium_Remove(Drive_LetterFromMask(pvol->dbcv_unitmask));
+ else if (0 == pvol->dbcv_flags)
+ {
+ char root[] = "X:\\";
+ root[0] = Drive_LetterFromMask(pvol->dbcv_unitmask);
+ if (DRIVE_CDROM == GetDriveTypeA(root)) Drive_Remove(root[0]);
+ }
+ }
+ break;
+ }
+}
+
+static DWORD CALLBACK InfoThread(LPVOID param)
+{
+ MSG msg;
+ DWORD start, status, timeout, result(0);
+ BOOL bComInit, run(TRUE);
+ HANDLE hTemp(NULL);
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
+ bComInit = ( S_OK == CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+
+ timeout = 20000; // 20 seconds delay
+ start = GetTickCount();
+
+ while(run)
+ {
+ DWORD elapsed = GetTickCount() - start;
+ if (elapsed < timeout)
+ status = MsgWaitForMultipleObjectsEx(0, NULL, timeout - elapsed,
+ QS_ALLINPUT, MWMO_WAITALL | MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
+ else status = WAIT_TIMEOUT;
+
+ switch(status)
+ {
+ case WAIT_FAILED:
+ if (bComInit) CoUninitialize();
+ return (DWORD)-1;
+ case WAIT_TIMEOUT:
+ if (NULL != pMngr)
+ {
+ EnterCriticalSection(&pMngr->csLock);
+ start = GetCurrentThreadId();
+ hTemp = NULL;
+
+ for (int i = pMngr->nCount - 1; i >= 0; i--)
+ {
+ if (pMngr->pDrives[i].dwThreadId == start)
+ {
+ pMngr->pDrives[i].dwThreadId = 0;
+ hTemp = pMngr->pDrives[i].hThread;
+ pMngr->pDrives[i].hThread = NULL;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ //while (WAIT_IO_COMPLETION == WaitForMultipleObjectsEx(0, NULL, TRUE, 0, TRUE));
+ while (SleepEx(0, TRUE) == WAIT_IO_COMPLETION);
+ }
+ result = 2;
+ run = FALSE;
+ break;
+ case WAIT_IO_COMPLETION: start = GetTickCount(); break;
+ case WAIT_OBJECT_0:
+ while (run && PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ switch(msg.message)
+ {
+ case WM_QUIT:
+ result = (DWORD)msg.wParam;
+ run = FALSE;
+ break;
+ case WM_EX_QUIT:
+ PostQuitMessage((INT)msg.wParam);
+ break;
+ default:
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (bComInit) CoUninitialize();
+ if (2 == result && hTemp) CloseHandle(hTemp);
+ hTemp = NULL;
+ return result;
+}
+
+static DWORD CALLBACK PollingThread(LPVOID param)
+{
+ MSG msg;
+ DWORD status, timeout;
+ BOOL bComInit;
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
+ bComInit = ( S_OK == CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+
+ timeout = POLLMEDIUMCHANGE_INTERVAL;
+
+ for(;;)
+ {
+ DWORD elapsed, start = GetTickCount();
+ while ((elapsed = GetTickCount() - start) < timeout)
+ {
+ status = MsgWaitForMultipleObjectsEx(0, NULL, timeout - elapsed,
+ QS_ALLINPUT, MWMO_WAITALL | MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
+ switch(status)
+ {
+ case WAIT_FAILED:
+ if (bComInit) CoUninitialize();
+ return (DWORD)-1;
+ case WAIT_TIMEOUT: PollMediumInfo(0); break;
+ case WAIT_OBJECT_0:
+ while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ switch(msg.message)
+ {
+ case WM_QUIT:
+ if (bComInit) CoUninitialize();
+ return (DWORD)msg.wParam;
+ case WM_EX_QUIT:
+ PostQuitMessage((INT)msg.wParam);
+ break;
+ default:
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+static void CALLBACK PollMediumInfo(ULONG_PTR param)
+{
+ char letters[32] = {0};
+ LPCWSTR pszDevName[32] = {0};
+ INT index, count;
+ if (!pMngr) return;
+
+ count = 0;
+ EnterCriticalSection(&pMngr->csLock);
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (DM_MODE_BURNING != pMngr->pDrives[index].cMode && DM_MODE_RIPPING != pMngr->pDrives[index].cMode)
+ {
+ letters[count] = pMngr->pDrives[index].cLetter;
+ pszDevName[count] = pMngr->pDrives[index].pszDevName;
+ count++;
+ }
+ }
+
+ LeaveCriticalSection(&pMngr->csLock);
+
+ while(count--)
+ {
+ HANDLE hDevice = CreateFileW(pszDevName[count], GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
+
+ if (INVALID_HANDLE_VALUE != hDevice)
+ {
+ BYTE sc, asc, ascq;
+ BOOL bReady, bReportChanges, bNeedRecheck;
+ DWORD ticks;
+
+ bReportChanges = FALSE;
+ bNeedRecheck = FALSE;
+
+ if(!SPTI_TestUnitReady(hDevice, &sc, &asc, &ascq, 2))
+ {
+ bReady = FALSE;
+ }
+ else bReady = (0x00 == sc || (0x02 == sc && 0x3A == asc));
+
+ CloseHandle(hDevice);
+
+ EnterCriticalSection(&pMngr->csLock);
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == letters[count])
+ {
+ ticks = GetTickCount();
+ if (pMngr->pDrives[index].bMediumInserted &&
+ (ticks - pMngr->pDrives[index].mediumInfo.msLastPolled) > POLLMEDIUMVALIDATE_INTERVAL) bNeedRecheck = TRUE;
+ pMngr->pDrives[index].mediumInfo.msLastPolled = ticks;
+
+ if (bReady && ((0x00 == sc) != pMngr->pDrives[index].bMediumInserted)) bReportChanges = TRUE;
+ break;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (bReportChanges)
+ {
+ if (0 == sc) Medium_Add(letters[count], (DWORD)-1);
+ else Medium_Remove(letters[count]);
+ }
+ else if (bNeedRecheck)
+ {
+ QueueInfoAPC(letters[count], APC_IsMediumChanged, (DWORD_PTR)letters[count]);
+ }
+
+ }
+ }
+}
+
+
+static LRESULT WINAPI ListenerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_DEVICECHANGE:
+ Listener_OnDeviceChange(hwnd, (UINT)wParam, (DWORD_PTR)lParam);
+ break;
+ }
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+static void CALLBACK APC_CheckDrives(ULONG_PTR param)
+{
+ INT index, result, count;
+ DWORD unitmask, dwOutput;
+ STORAGE_DEVICE_NUMBER sdn;
+ DEVICEINFO *pDevInfo;
+ BYTE buffer[4096] = {0};
+
+ if (!pMngr) return;
+
+ unitmask = (DWORD)param;
+ count = 0;
+ for (int i = 0; i < 26; i++) {if (0x1 & (unitmask >> i)) count++;}
+ if (!count) return;
+
+ pDevInfo = (DEVICEINFO*)calloc(count, sizeof(DEVICEINFO));
+ if (!pDevInfo) return;
+
+ index = 0;
+ for (int i = 0; i < 26; i++)
+ {
+ if (0x1 & unitmask)
+ {
+ pDevInfo[index].cLetter = (CHAR)(('A' + i));
+ index++;
+ if (index == count) break;
+ }
+ unitmask = unitmask >> 1;
+ }
+
+ GetDeviceNames(pDevInfo, count);
+
+ for (int i = 0; i < count; i++)
+ {
+ HANDLE hDevice = CreateFileW(pDevInfo[i].pszDevName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
+
+ if (INVALID_HANDLE_VALUE != hDevice)
+ {
+ ZeroMemory(&sdn, sizeof(STORAGE_DEVICE_NUMBER));
+ result = DeviceIoControl(hDevice, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER), &dwOutput, NULL);
+ pDevInfo->deviceNumber = (result) ? sdn.DeviceNumber : -1;
+
+ ZeroMemory(&buffer, sizeof(buffer)/sizeof(BYTE));
+ result = DeviceIoControl(hDevice, IOCTL_STORAGE_GET_MEDIA_TYPES_EX, NULL, 0, buffer, sizeof(buffer)/sizeof(BYTE), &dwOutput, NULL);
+ if (result && buffer && (FILE_DEVICE_DVD & ((GET_MEDIA_TYPES*)buffer)->DeviceType)) pDevInfo[i].dwType = DRIVE_TYPE_DVD;
+ else pDevInfo[i].dwType = DRIVE_TYPE_CD;
+
+ CloseHandle(hDevice);
+ }
+ }
+
+ GetDeviceCaps(pDevInfo, count);
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ for (int i = 0; i < count; i++)
+ {
+ pDevInfo[i].opCode = 0;
+ for (index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == pDevInfo[i].cLetter)
+ {
+ if (-1 == pMngr->pDrives[index].deviceNumber || pMngr->pDrives[index].deviceNumber != pDevInfo[i].deviceNumber)
+ pDevInfo[i].opCode = 1;
+ break;
+ }
+ }
+ if (pMngr->nCount == index) pDevInfo[i].opCode = 2;
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ for (int i = 0; i < count; i++)
+ {
+ if (pDevInfo[i].opCode) Drive_Add(&pDevInfo[i]);
+ }
+
+ if (pDevInfo)
+ {
+ for (int i = 0; i<count; i++)
+ {
+ if (pDevInfo[i].pszDevName) free(pDevInfo[i].pszDevName);
+ }
+ free(pDevInfo);
+ }
+}
+
+static void CALLBACK APC_IsMediumChanged(ULONG_PTR param)
+{
+ INT opCode;
+ DWORD serial;
+
+ wchar_t devname[4] = L"X:\\";
+
+ if (!pMngr) return;
+
+ opCode = 0;
+ devname[0] = (char)(0xFF & param);
+ if (devname[0])
+ {
+ BOOL result;
+
+ serial = 0;
+ result = GetVolumeInformationW(devname, NULL, 0, &serial, NULL, NULL, NULL, 0);
+ if (!result) serial = 0; // perhaps this is empty recordable disc
+
+ EnterCriticalSection(&pMngr->csLock);
+
+ for (INT index =0; index < pMngr->nCount; index++)
+ {
+ if (pMngr->pDrives[index].cLetter == (char)param )
+ {
+ pMngr->pDrives[index].mediumInfo.msLastPolled = GetTickCount();
+ if (!pMngr->pDrives[index].bMediumInserted && result) opCode = 0x02;
+ else if (pMngr->pDrives[index].mediumInfo.serialNumber != serial)
+ {
+ if (-1 == pMngr->pDrives[index].mediumInfo.serialNumber) pMngr->pDrives[index].mediumInfo.serialNumber = serial;
+ else opCode = 0x03;
+ }
+ break;
+ }
+ }
+ LeaveCriticalSection(&pMngr->csLock);
+
+ if (0x01 & opCode) Medium_Remove((char)param);
+ if (0x02 & opCode) Medium_Add((char)param, serial);
+ }
+}
+
+static void CALLBACK APC_AsyncOp_Complete(ULONG_PTR param)
+{
+ DM_NOTIFY_PARAM *phdr = (DM_NOTIFY_PARAM*)param;
+ if (phdr->hReserved)
+ {
+ CloseHandle(phdr->hReserved);
+ phdr->hReserved = NULL;
+ }
+
+ if (phdr->callback)
+ {
+ if (phdr->uMsg)
+ {
+ if (IsWindow((HWND)phdr->callback)) SendMessageW((HWND)phdr->callback, phdr->uMsg, (WPARAM)DMW_OPCOMPLETED, (LPARAM)phdr);
+ }
+ else ((DMNPROC)phdr->callback)(DMW_OPCOMPLETED, (INT_PTR)param);
+ }
+
+ if (phdr->fnFree)
+ {
+ phdr->fnFree(phdr);
+ }
+}
+
+static void AsycOp_Complete(DM_NOTIFY_PARAM *param)
+{
+ if (param) QueueUserAPC(APC_AsyncOp_Complete, param->hReserved, (ULONG_PTR)param);
+}
+
+static void CALLBACK APC_GetUnitInfo(ULONG_PTR param)
+{
+ DWORD unit;
+ DM_UNITINFO_PARAM *puip;
+ puip = (DM_UNITINFO_PARAM*)param;
+
+ puip->header.opCode = DMOP_UNITINFO;
+
+ unit = CheckLetter(puip->header.cLetter);
+
+ if (!unit || (!PrimoSDKHelper_IsInitialized() && !PrimoSDKHelper_Initialize())) puip->header.result = PRIMOSDK_CMDSEQUENCE;
+ else
+ {
+ DWORD ready = 0;
+ CHAR buffer[512] = {0};
+
+ puip->header.result = PrimoSDKHelper_UnitInfo(&unit, &puip->dwType,
+ ((DMF_DESCRIPTION & puip->header.fFlags) || (DMF_FIRMWARE & puip->header.fFlags)) ? (BYTE*)buffer: NULL,
+ (DMF_READY & puip->header.fFlags) ? &ready : NULL);
+
+ if (PRIMOSDK_OK == puip->header.result)
+ {
+ if (DMF_READY & puip->header.fFlags) puip->bReady = (0 != ready);
+
+ if (DMF_DESCRIPTION & puip->header.fFlags)
+ {
+ INT len = lstrlenA(buffer);
+ if (len > 5) len -= 5;
+ if (!puip->pszDesc || puip->cchDesc < (len + 1)) puip->cchDesc = -(len + 1);
+ else
+ {
+ StringCchCopyNA(puip->pszDesc, puip->cchDesc, buffer, len);
+ puip->cchDesc = len;
+ }
+ }
+ if (DMF_FIRMWARE & puip->header.fFlags)
+ {
+ LPSTR p;
+ INT len = lstrlenA(buffer);
+ p = buffer + (len - ((len > 5) ? 4 : 0));
+ if (!puip->pszFirmware || puip->cchFirmware < 4) puip->cchFirmware = -4;
+ else
+ {
+ StringCchCopyA(puip->pszFirmware, puip->cchFirmware, p);
+ puip->cchFirmware = 4;
+ }
+ }
+ }
+ }
+ AsycOp_Complete(&puip->header);
+}
+
+static void CALLBACK APC_GetUnitInfo2(ULONG_PTR param)
+{
+ DWORD unit;
+ DM_UNITINFO2_PARAM *puip;
+
+ puip = (DM_UNITINFO2_PARAM*)param;
+ puip->header.opCode = DMOP_UNITINFO2;
+ unit = CheckLetter(puip->header.cLetter);
+
+ if (!unit || (!PrimoSDKHelper_IsInitialized() && !PrimoSDKHelper_Initialize())) puip->header.result = PRIMOSDK_CMDSEQUENCE;
+ else
+ {
+ BOOL bReady;
+ DWORD szTypes[32], rfu;
+
+ if (DriveManager_IsUnitReady((char)unit, &bReady) && !bReady)
+ {
+ SleepEx(1000, TRUE);
+ QueueUserAPC(APC_GetUnitInfo2, GetCurrentThread(), param);
+ return;
+ }
+
+ puip->header.result = PrimoSDKHelper_UnitInfo2(&unit, szTypes, &puip->dwClassId, &puip->dwBusType, &rfu);
+ if (PRIMOSDK_OK == puip->header.result)
+ {
+ if (DMF_TYPES & puip->header.fFlags)
+ {
+ INT len;
+ for (len = 0; szTypes[len] != 0xFFFFFFFF; len++);
+
+ if (!puip->pdwTypes || puip->nTypes < len) puip->nTypes = -len;
+ else
+ {
+ puip->nTypes = len;
+ if (len) CopyMemory(puip->pdwTypes, szTypes, sizeof(DWORD)*len);
+ }
+ }
+ }
+ }
+ AsycOp_Complete(&puip->header);
+}
+static void CALLBACK APC_GetDiscInfoEx(ULONG_PTR param)
+{
+ DWORD unit;
+ DM_DISCINFOEX_PARAM *pdip;
+
+ pdip = (DM_DISCINFOEX_PARAM*)param;
+ pdip->header.opCode = DMOP_DISCINFO;
+ unit = CheckLetter(pdip->header.cLetter);
+
+ if (!unit || (!PrimoSDKHelper_IsInitialized() && !PrimoSDKHelper_Initialize())) pdip->header.result = PRIMOSDK_CMDSEQUENCE;
+ else
+ {
+ BOOL bReady;
+ DWORD dwFlags, dwErasable;
+
+ if (DriveManager_IsUnitReady((char)unit, &bReady) && !bReady)
+ {
+ SleepEx(1000, TRUE);
+ QueueUserAPC(APC_GetDiscInfoEx, GetCurrentThread(), param);
+ return;
+ }
+
+ dwFlags = (DMF_DRIVEMODE_TAO & pdip->header.fFlags);
+ pdip->header.result = PrimoSDKHelper_DiscInfoEx(&unit, dwFlags,
+ (DMF_MEDIUMTYPE & pdip->header.fFlags) ? &pdip->dwMediumType : NULL,
+ (DMF_MEDIUMFORMAT & pdip->header.fFlags) ? &pdip->dwMediumFormat : NULL,
+ &dwErasable,
+ (DMF_TRACKS & pdip->header.fFlags) ? &pdip->dwTracks: NULL,
+ (DMF_USED & pdip->header.fFlags) ? &pdip->dwUsed : NULL,
+ (DMF_FREE & pdip->header.fFlags) ? &pdip->dwFree : NULL);
+
+ if (PRIMOSDK_OK == pdip->header.result) pdip->bErasable = (0 != dwErasable);
+ }
+
+ AsycOp_Complete(&pdip->header);
+}
+
+static void CALLBACK APC_GetDiscInfo2(ULONG_PTR param)
+{
+ DWORD unit;
+ DM_DISCINFO2_PARAM *pdip;
+
+ pdip = (DM_DISCINFO2_PARAM*)param;
+ pdip->header.opCode = DMOP_DISCINFO2;
+ unit = CheckLetter(pdip->header.cLetter);
+
+ if (!unit || (!PrimoSDKHelper_IsInitialized() && !PrimoSDKHelper_Initialize())) pdip->header.result = PRIMOSDK_CMDSEQUENCE;
+ else
+ {
+ DWORD rfu, medium, protectedDVD, flags;
+
+ BOOL bReady;
+ if (DriveManager_IsUnitReady((char)unit, &bReady) && !bReady)
+ {
+ SleepEx(1000, TRUE);
+ QueueUserAPC(APC_GetDiscInfo2, GetCurrentThread(), param);
+ return;
+ }
+
+ pdip->header.result = PrimoSDKHelper_DiscInfo2(&unit,
+ (DMF_MEDIUM & pdip->header.fFlags) ? &pdip->dwMedium : (DMF_MEDIUMEX & pdip->header.fFlags) ? &medium : NULL,
+ (DMF_PROTECTEDDVD & pdip->header.fFlags) ? &protectedDVD : NULL,
+ (DMF_PACKETWRITTEN & pdip->header.fFlags) ? &flags : NULL,
+ (DMF_MEDIUMEX & pdip->header.fFlags) ? &pdip->dwMediumEx : NULL,
+ &rfu);
+ if (PRIMOSDK_OK == pdip->header.result)
+ {
+ if (DMF_PROTECTEDDVD & pdip->header.fFlags) pdip->bProtectedDVD = (0 != protectedDVD);
+ if (DMF_PACKETWRITTEN & pdip->header.fFlags) pdip->bPacketWritten = (0 != (PRIMOSDK_PACKETWRITTEN & protectedDVD));
+ }
+
+ }
+ AsycOp_Complete(&pdip->header);
+}
+
+static void CALLBACK APC_GetTitle(ULONG_PTR param)
+{
+ CHAR cLetter;
+ DM_TITLE_PARAM *pdtp;
+
+ pdtp = (DM_TITLE_PARAM*)param;
+ pdtp->header.opCode = DMOP_TITLE;
+ cLetter = CheckLetter(pdtp->header.cLetter);
+
+ pdtp->header.result = PRIMOSDK_CMDSEQUENCE;
+ if (cLetter && pdtp->pszTitle)
+ {
+ wchar_t name[] = L"X:\\";
+ MCIDEVICEID devId;
+ MCI_OPEN_PARMS op = {0};
+ MCI_GENERIC_PARMS gp = {0};
+ MCI_STATUS_PARMS sp = {0};
+
+ name[0] = cLetter;
+
+ op.lpstrDeviceType = (LPWSTR)MCI_DEVTYPE_CD_AUDIO;
+ op.lpstrElementName = name;
+
+ if (!mciSendCommandW(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT, (DWORD_PTR)&op))
+ {
+ HRESULT hr;
+
+ devId = op.wDeviceID;
+ sp.dwItem = MCI_STATUS_MEDIA_PRESENT;
+ INT present = (!mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)&sp)) ? (BOOL)sp.dwReturn : 0;
+
+ if (present)
+ {
+ INT nTracks;
+ BOOL bAudio;
+ wchar_t szVolume[256] = {0};
+ // check if we have at least one audio track
+ sp.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
+ nTracks = (!mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)&sp)) ? (INT)sp.dwReturn : -1;
+ bAudio = FALSE;
+
+ if (nTracks > 0)
+ {
+ sp.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
+ for (sp.dwTrack = 1; sp.dwTrack <= (UINT)nTracks && !bAudio; sp.dwTrack++)
+ {
+ mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR)&sp);
+ bAudio = (MCI_CDA_TRACK_AUDIO == sp.dwReturn);
+ }
+ if (bAudio) WASABI_API_LNGSTRINGW_BUF(IDS_CD_AUDIO, szVolume, sizeof(szVolume)/sizeof(wchar_t));
+ else
+ {
+ INT result;
+ wchar_t devname[4] = L"X:\\";
+ devname[0] = cLetter;
+ result = GetVolumeInformationW(devname, szVolume, sizeof(szVolume)/sizeof(wchar_t), NULL, NULL, NULL, NULL, 0);
+ if (!result) WASABI_API_LNGSTRINGW_BUF(IDS_DISC_DATA, szVolume, sizeof(szVolume)/sizeof(wchar_t));
+ }
+ }
+ else WASABI_API_LNGSTRINGW_BUF(IDS_DISC_BLANK, szVolume, sizeof(szVolume)/sizeof(wchar_t));
+
+ hr = StringCchPrintfW(pdtp->pszTitle, pdtp->cchTitle, L"%s (%c:)", szVolume, cLetter);
+ }
+ else
+ {
+ INT nDriveType, nDriveCap;
+ DWORD type;
+ wchar_t szDriveType[32] = {0}, szDriveCap[64] = {0};
+
+ type = DriveManager_GetDriveType(cLetter);
+ if ((DRIVE_TYPE_UNKNOWN | DRIVE_CAP_UNKNOWN) == type) type = DRIVE_TYPE_CD;
+
+ nDriveCap = ((DRIVE_CAP_R | DRIVE_CAP_RW) & type) ? IDS_RECORDER_CAP : IDS_DRIVE_CAP;
+ nDriveType = (IDS_DRIVE_CAP == nDriveCap && (DRIVE_TYPE_DVD & type)) ? IDS_DVD : IDS_CD;
+
+ WASABI_API_LNGSTRINGW_BUF(nDriveType, szDriveType, sizeof(szDriveType)/sizeof(wchar_t));
+ WASABI_API_LNGSTRINGW_BUF(nDriveCap, szDriveCap, sizeof(szDriveCap)/sizeof(wchar_t));
+ hr = StringCchPrintfW(pdtp->pszTitle, pdtp->cchTitle, L"%s %s (%C:)", szDriveType, szDriveCap, cLetter);
+ }
+ pdtp->header.result = hr;
+ mciSendCommandW(devId, MCI_CLOSE, MCI_WAIT, (DWORD_PTR)&gp);
+ }
+ }
+ AsycOp_Complete(&pdtp->header);
+}
+
+static void CALLBACK APC_DriveScan(ULONG_PTR param)
+{
+ char i;
+ char root[] = "A:\\";
+ DWORD unitmask;
+ DEVICEINFO di = {0};
+
+ /// detect drives
+ unitmask = GetLogicalDrives();
+
+ di.deviceNumber = -1;
+ di.dwType = DRIVE_TYPE_CD;
+
+ for (i = 0; i < 26; ++i)
+ {
+ if (0x1 & (unitmask >> i))
+ {
+ root[0] = ('A' + i);
+ if(DRIVE_CDROM != GetDriveTypeA(root)) unitmask &= ~(1 << i);
+ else
+ {
+ di.cLetter = root[0];
+ Drive_Add(&di);
+ }
+ }
+ }
+ APC_CheckDrives((ULONG_PTR)unitmask);
+}
+
+#define MAX_TEST_ATTEMPT 20
+
+static void CALLBACK APC_Eject(ULONG_PTR param)
+{
+ INT nCmd;
+ CHAR cLetter;
+ BYTE sc(0), asc(0), ascq(0);
+
+ nCmd = HIWORD(param);
+ cLetter = CheckLetter((CHAR)param);
+
+ if (cLetter && DM_MODE_READY == DriveManager_GetDriveMode(cLetter))
+ {
+ BOOL bSuccess;
+ HANDLE hDevice;
+
+ hDevice = CreateFileW(GetDeviceName(cLetter), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
+ if (INVALID_HANDLE_VALUE != hDevice)
+ {
+ DWORD dwOutput;
+ LARGE_INTEGER start, finish;
+
+ bSuccess = SPTI_TestUnitReady(hDevice, &sc, &asc, &ascq, 3);
+ if (!bSuccess && ERROR_SEM_TIMEOUT == GetLastError())
+ {
+ bSuccess = TRUE;
+ sc = 0xFF;
+ }
+
+ if (bSuccess && (0 == sc || (0x02 == sc && 0x3A == asc)))
+ {
+ INT opCode;
+ opCode = (DM_EJECT_REMOVE == nCmd || 0x00 == sc || (0x3A == asc && 0x01 == ascq)) ?
+ IOCTL_STORAGE_EJECT_MEDIA : IOCTL_STORAGE_LOAD_MEDIA;
+
+ QueryPerformanceCounter(&start);
+ bSuccess = DeviceIoControl(hDevice, opCode, NULL, 0, NULL, 0, &dwOutput, NULL);
+ QueryPerformanceCounter(&finish);
+
+ if (bSuccess && DM_EJECT_CHANGE == nCmd && 0x00 != sc && 0x00 == ascq)
+ {
+ finish.QuadPart -= start.QuadPart;
+
+ if (finish.QuadPart < freq.QuadPart && (finish.QuadPart*100000 / freq.QuadPart) < 200)
+ {
+ // test unit redy
+ INT i;
+ sc = 0x02; asc = 0x04; ascq = 0x01;
+ for (i = 0; i < MAX_TEST_ATTEMPT && 0x02 == sc && 0x04 == asc && 0x01 == ascq; i++)
+ {
+ Sleep(50);
+ if (!SPTI_TestUnitReady(hDevice, &sc, &asc, &ascq, 3) && ERROR_SEM_TIMEOUT == GetLastError())
+ i = MAX_TEST_ATTEMPT;
+ }
+ if (i < MAX_TEST_ATTEMPT && 0x02 == sc && 0x3A ==asc)
+ {
+ DeviceIoControl(hDevice, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dwOutput, NULL);
+ }
+ sc = 0x00;
+ }
+ }
+ }
+ CloseHandle(hDevice);
+ }
+ else bSuccess = FALSE;
+
+ if (!bSuccess)
+ { // we can try MCI
+
+ }
+ else if (0x00 != sc && !(0x02 == sc && 0x3A == asc))
+ {
+ SleepEx(200, TRUE);
+ QueueUserAPC(APC_Eject, GetCurrentThread(), param);
+ return;
+ }
+ }
+}
+
+static void CALLBACK APC_GetMCIInfo(ULONG_PTR param)
+{
+ CHAR cLetter;
+ MCI_OPEN_PARMS op = {0};
+ DM_MCI_PARAM *pmcip;
+
+ pmcip = (DM_MCI_PARAM*)param;
+ pmcip->header.opCode = DMOP_MCIINFO;
+ cLetter = CheckLetter(pmcip->header.cLetter);
+
+ pmcip->header.result = PRIMOSDK_CMDSEQUENCE;
+ if (cLetter)
+ {
+ wchar_t name[] = L"X:\\";
+ MCIDEVICEID devId;
+ MCI_INFO_PARMS ip = {0};
+ MCI_GENERIC_PARMS gp = {0};
+ MCI_STATUS_PARMS sp = {0};
+
+ name[0] = cLetter;
+
+ op.lpstrDeviceType = (LPWSTR)MCI_DEVTYPE_CD_AUDIO;
+ op.lpstrElementName = name;
+
+ if (!mciSendCommandW(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT, (DWORD_PTR)&op))
+ {
+ WCHAR buffer[512] = {0};
+ INT nMaxTracks = pmcip->nTracks;
+
+ devId = op.wDeviceID;
+
+ if ((DMF_TRACKCOUNT | DMF_TRACKSINFO) & pmcip->header.fFlags)
+ {
+ sp.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
+ pmcip->nTracks = (!mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)&sp)) ? (INT)sp.dwReturn : -1;
+ }
+ if (DMF_READY & pmcip->header.fFlags)
+ {
+ sp.dwItem = MCI_STATUS_READY;
+ pmcip->bReady = (!mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)&sp)) ? (BOOL)sp.dwReturn : 0;
+ }
+ if (DMF_MODE & pmcip->header.fFlags)
+ {
+ sp.dwItem = MCI_STATUS_MODE;
+ pmcip->uMode = (!mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)&sp)) ? (UINT)sp.dwReturn : 0;
+ }
+ if (DMF_MEDIUMPRESENT & pmcip->header.fFlags)
+ {
+ sp.dwItem = MCI_STATUS_MEDIA_PRESENT;
+ pmcip->bMediumPresent = (!mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)&sp)) ? (BOOL)sp.dwReturn : 0;
+ }
+ if (DMF_MEDIUMUID & pmcip->header.fFlags)
+ {
+ ip.dwRetSize = sizeof(buffer)/sizeof(wchar_t);
+ ip.lpstrReturn= buffer;
+ if (!mciSendCommandW(devId, MCI_INFO, MCI_WAIT | MCI_INFO_MEDIA_IDENTITY, (DWORD_PTR)&ip))
+ {
+ INT len;
+ len = lstrlenW(ip.lpstrReturn);
+ if (S_OK == StringCchCopyW(pmcip->pszMediumUID, pmcip->cchMediumUID, ip.lpstrReturn))
+ {
+ pmcip->cchMediumUID = len;
+ }
+ else pmcip->cchMediumUID = 0 - (len + 1);
+ }
+ else pmcip->cchMediumUID = -1;
+ }
+ if (DMF_MEDIUMUPC & pmcip->header.fFlags)
+ {
+ ip.dwCallback = NULL;
+ ip.dwRetSize = sizeof(buffer)/sizeof(wchar_t);
+ ip.lpstrReturn = buffer;
+ if (!mciSendCommandW(devId, MCI_INFO, MCI_WAIT | MCI_INFO_MEDIA_UPC, (DWORD_PTR)&ip))
+ {
+ INT len;
+ len = lstrlenW(ip.lpstrReturn);
+ if (S_OK == StringCchCopyW(pmcip->pszMediumUPC, pmcip->cchMediumUPC, ip.lpstrReturn))
+ {
+ pmcip->cchMediumUPC = len;
+ }
+ else pmcip->cchMediumUPC = 0 - (len + 1);
+ }
+ else pmcip->cchMediumUPC = -1;
+ }
+
+ if (DMF_TRACKSINFO & pmcip->header.fFlags)
+ {
+ MCI_SET_PARMS setp;
+
+ if (nMaxTracks < pmcip->nTracks) pmcip->nTracks = (0 - pmcip->nTracks);
+ else
+ {
+ INT prevPos(0), length(0);
+ setp.dwTimeFormat = MCI_FORMAT_MILLISECONDS;
+ mciSendCommandW(devId, MCI_SET, MCI_WAIT | MCI_SET_TIME_FORMAT, (DWORD_PTR)&setp);
+
+ for (int i = pmcip->nTracks; i > 0; i--)
+ {
+ sp.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
+ sp.dwTrack = i;
+ mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR)&sp);
+ BOOL bAudio = (MCI_CDA_TRACK_AUDIO == sp.dwReturn);
+
+ sp.dwItem = MCI_STATUS_POSITION;
+ sp.dwTrack = i;
+ mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR)&sp);
+
+ if (i != pmcip->nTracks) length = prevPos - (INT)sp.dwReturn;
+ prevPos = (INT)sp.dwReturn;
+
+ if (i == pmcip->nTracks)
+ {
+ sp.dwItem = MCI_STATUS_LENGTH;
+ sp.dwTrack = i;
+ mciSendCommandW(devId, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR)&sp);
+ length = (INT)sp.dwReturn;
+ }
+
+ pmcip->pTracks[i- 1] = (0x7FFFFFF & length) | ((bAudio) ? 0x80000000 : 0);
+ }
+
+ setp.dwTimeFormat = MCI_FORMAT_TMSF;
+ mciSendCommandW(devId, MCI_SET, MCI_WAIT | MCI_SET_TIME_FORMAT, (DWORD_PTR)&setp);
+ }
+ }
+
+ mciSendCommandW(devId, MCI_CLOSE, MCI_WAIT, (DWORD_PTR)&gp);
+ pmcip->header.result = PRIMOSDK_OK;
+ }
+ }
+
+ AsycOp_Complete(&pmcip->header);
+}
+
+static void CALLBACK APC_GetIMAPIInfo(ULONG_PTR param)
+{
+ CHAR cLetter;
+ BOOL bReady;
+ HRESULT hr(S_FALSE);
+ IDiscMaster *pdm;
+ IDiscRecorder *pdr;
+ IEnumDiscRecorders *per;
+ ULONG nActual;
+
+ wchar_t szDevName[] = L"X:\\";
+ wchar_t szTargetName[128] = {0};
+ DM_IMAPI_PARAM *pIMAPI;
+
+ pIMAPI = (DM_IMAPI_PARAM*)param;
+ cLetter = CheckLetter(pIMAPI->header.cLetter);
+
+ if (DriveManager_IsUnitReady(cLetter, &bReady) && !bReady)
+ {
+ SleepEx(1000, TRUE);
+ QueueUserAPC(APC_GetIMAPIInfo, GetCurrentThread(), param);
+ return;
+ }
+
+ pIMAPI->header.opCode = DMOP_IMAPIINFO;
+
+ pIMAPI->bRecorder = FALSE;
+ pIMAPI->header.result = (DWORD)E_INVALIDARG;
+
+ szDevName[0] = cLetter;
+ if (cLetter && QueryDosDeviceW(szDevName, szTargetName, sizeof(szTargetName)/sizeof(wchar_t)))
+ {
+ hr = CoCreateInstance(CLSID_MSDiscMasterObj, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IID_IDiscMaster, (void**)&pdm);
+ if (SUCCEEDED(hr))
+ {
+ hr = pdm->Open();
+ if (SUCCEEDED(hr))
+ {
+ IEnumDiscMasterFormats *pef;
+ hr = pdm->EnumDiscMasterFormats(&pef);
+ if (SUCCEEDED(hr))
+ {
+ IID pFormats[2];
+ hr = pef->Next(sizeof(pFormats)/sizeof(IID), pFormats, &nActual);
+ if (SUCCEEDED(hr))
+ {
+ while(nActual--) { if (IID_IRedbookDiscMaster == pFormats[nActual]) break; }
+ if (nActual != ((ULONG)-1))
+ {
+ IRedbookDiscMaster *pdf;
+ hr = pdm->SetActiveDiscMasterFormat(IID_IRedbookDiscMaster, (void**)&pdf);
+ if (SUCCEEDED(hr))
+ {
+ pdf->Release();
+ hr = pdm->EnumDiscRecorders(&per);
+ if (SUCCEEDED(hr))
+ {
+ while (S_OK== per->Next(1, &pdr, &nActual) && nActual > 0)
+ {
+ BSTR bstrPath;
+ hr = pdr->GetPath(&bstrPath);
+ if (SUCCEEDED(hr))
+ {
+ if (0 == lstrcmp(szTargetName, bstrPath))
+ {
+ pIMAPI->bRecorder = TRUE;
+ if ((DMF_BASEPNPID & pIMAPI->header.fFlags) && FAILED(pdr->GetBasePnPID(&pIMAPI->bstrBasePnPID))) pIMAPI->bstrBasePnPID = NULL;
+ if ((DMF_DISPLAYNAMES & pIMAPI->header.fFlags) && FAILED(pdr->GetDisplayNames(&pIMAPI->bstrVendorID, &pIMAPI->bstrProductID, &pIMAPI->bstrRevision)))
+ {
+ pIMAPI->bstrVendorID = NULL;
+ pIMAPI->bstrProductID = NULL;
+ pIMAPI->bstrRevision = NULL;
+ }
+ if (DMF_PATH & pIMAPI->header.fFlags)
+ {
+ pIMAPI->bstrPath = bstrPath;
+ bstrPath = NULL;
+ }
+ if ((DMF_DRIVESTATE & pIMAPI->header.fFlags) && FAILED(pdr->GetRecorderState(&pIMAPI->ulDriveState))) pIMAPI->ulDriveState = (ULONG)-1;
+ if ((DMF_DRIVETYPE & pIMAPI->header.fFlags) && FAILED(pdr->GetRecorderType(&pIMAPI->fDriveType))) pIMAPI->fDriveType = 0;
+ if ((DMF_QUERYMEDIATYPE | DMF_QUERYMEDIAINFO) & pIMAPI->header.fFlags)
+ {
+ BOOL bTypeOk(FALSE), bInfoOk(FALSE);
+ if (SUCCEEDED(pdr->OpenExclusive()))
+ {
+ if (0 == (DMF_QUERYMEDIATYPE & pIMAPI->header.fFlags) ||
+ SUCCEEDED(pdr->QueryMediaType(&pIMAPI->fMediaType, &pIMAPI->fMediaFlags))) bTypeOk = TRUE;
+ if (0 == (DMF_QUERYMEDIAINFO & pIMAPI->header.fFlags) ||
+ SUCCEEDED(pdr->QueryMediaInfo(&pIMAPI->bSessions, &pIMAPI->bLastTrack, &pIMAPI->ulStartAddress,
+ &pIMAPI->ulNextWritable, &pIMAPI->ulFreeBlocks))) bInfoOk = TRUE;
+ pdr->Close();
+ }
+
+
+ if (!bTypeOk)
+ {
+ pIMAPI->fMediaType = -1;
+ pIMAPI->fMediaFlags = -1;
+ }
+ if (!bInfoOk)
+ {
+ pIMAPI->bLastTrack = 0;
+ pIMAPI->bSessions = 0;
+ pIMAPI->ulFreeBlocks = 0;
+ pIMAPI->ulNextWritable = 0;
+ pIMAPI->ulStartAddress = 0;
+ }
+
+
+ }
+ break;
+ }
+ if (bstrPath) SysFreeString(bstrPath);
+ }
+ pdr->Release();
+ }
+ per->Release();
+ }
+ }
+ }
+ }
+ pef->Release();
+ }
+ pdm->Close();
+ }
+ pdm->Release();
+ }
+ }
+ pIMAPI->header.result = hr;
+ AsycOp_Complete(&pIMAPI->header);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/drivemngr.h b/Src/Plugins/Library/ml_disc/drivemngr.h
new file mode 100644
index 00000000..c5e9c2e9
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/drivemngr.h
@@ -0,0 +1,239 @@
+#ifndef NULLSOFT_MLDISC_DRIVEMANAGER_HEADER
+#define NULLSOFT_MLDISC_DRIVEMANAGER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+//#include "../primo/obj_primo.h"
+
+// drive types
+
+#define DRIVE_TYPE_UNKNOWN 0x00000000
+#define DRIVE_TYPE_CD 0x00000010
+#define DRIVE_TYPE_DVD 0x00000020
+#define DRIVE_CAP_UNKNOWN 0x80000000
+#define DRIVE_CAP_R 0x00010000
+#define DRIVE_CAP_RW 0x00020000
+#define DRIVE_CDR (DRIVE_TYPE_CD | DRIVE_CAP_R )
+#define DRIVE_CDRW (DRIVE_TYPE_CD | DRIVE_CAP_RW)
+#define DRIVE_DVDR (DRIVE_TYPE_DVD | DRIVE_CAP_R)
+#define DRIVE_DVDRW (DRIVE_TYPE_DVD | DRIVE_CAP_RW)
+
+#define DMW_DRIVEADDED 0x0001 // param contains drive letter
+#define DMW_DRIVEREMOVED 0x0002 // param contains drive letter
+#define DMW_DRIVECHANGED 0x0003 // param contains drive letter
+#define DMW_MEDIUMARRIVED 0x0004 // param contains drive letter
+#define DMW_MEDIUMREMOVED 0x0005 // param contains drive letter
+#define DMW_OPCOMPLETED 0x0006 // one of the async opetations completed
+
+#define DMW_MODECHANGED 0x0010 // LOWORD(param) = MAKEWORD(cLetter, cMode)
+
+typedef void* HDRVMNGR;
+
+typedef struct _DM_NOTIFY_PARAM DM_NOTIFY_PARAM;
+
+typedef void (CALLBACK *DMNPROC)(WORD/*wCode*/, INT_PTR/*param*/);
+typedef void (CALLBACK *DMFREEPROC)(DM_NOTIFY_PARAM *phdr);
+
+
+#define DM_EJECT_REMOVE 0
+#define DM_EJECT_LOAD 1
+#define DM_EJECT_CHANGE 2
+
+// valid with DM_UNITINFO_PARAM
+#define DMF_DESCRIPTION 0x00000001
+#define DMF_FIRMWARE 0x00000002
+#define DMF_READY 0x00000004
+
+// valid with DM_UNITINFO2_PARAM
+#define DMF_TYPES 0x00000001
+
+// valid with DM_DISCINFOEX_PARAM
+#define DMF_DRIVEMODE_DAO 0x00000000
+#define DMF_DRIVEMODE_TAO 0x00010000
+#define DMF_MEDIUMTYPE 0x00000001
+#define DMF_MEDIUMFORMAT 0x00000002
+#define DMF_TRACKS 0x00000004
+#define DMF_USED 0x00000008
+#define DMF_FREE 0x00000010
+
+// valid with DM_DISCINFO2_PARAM
+#define DMF_MEDIUM 0x00000001
+#define DMF_PROTECTEDDVD 0x00000002
+#define DMF_PACKETWRITTEN 0x00000004
+#define DMF_MEDIUMEX 0x00000008
+
+// valid with DM_FANCYTITLE_PARAM
+#define DMF_VOLUMELABEL 0x00000001 // volume label
+#define DMF_CDTEXT 0x00000002 // if set and medium inserted will try to get info from cdtext
+#define DMF_CDDB 0x00000004 // if set and medium inserted will try to get info from gracenote
+#define DMF_DRIVEDESCRIPTION 0x00000010 // will use PrimoSDK to get drive info
+
+// valid with DM_MCI_PARAM
+#define DMF_READY 0x00000004 //
+#define DMF_MEDIUMPRESENT 0x00000002 //
+#define DMF_MODE 0x00000001 //
+#define DMF_TRACKCOUNT 0x00000008 //
+#define DMF_TRACKSINFO 0x00000010 //
+#define DMF_MEDIUMUID 0x00000020 //
+#define DMF_MEDIUMUPC 0x00000040 //
+
+// valid with DM_IMAPI_PARAM
+#define DMF_BASEPNPID 0x00000001
+#define DMF_DISPLAYNAMES 0x00000002
+#define DMF_PATH 0x00000004
+#define DMF_DRIVESTATE 0x00000008
+#define DMF_DRIVETYPE 0x00000010
+#define DMF_QUERYMEDIATYPE 0x00000020
+#define DMF_QUERYMEDIAINFO 0x00000040
+
+
+// Operation Codes
+#define DMOP_GENERAL 0x0000
+#define DMOP_UNITINFO 0x0001
+#define DMOP_UNITINFO2 0x0002
+#define DMOP_DISCINFO 0x0003
+#define DMOP_DISCINFO2 0x0004
+#define DMOP_TITLE 0x0005
+#define DMOP_MCIINFO 0x0006
+#define DMOP_IMAPIINFO 0x0007
+
+// Drive modes
+#define DM_MODE_ERROR ((CHAR)(0 - 1))
+
+#define DM_MODE_READY 0x00
+#define DM_MODE_BURNING 0x01
+#define DM_MODE_RIPPING 0x02
+#define DM_MODE_COPYING 0x03
+
+
+typedef struct _DM_NOTIFY_PARAM
+{
+ INT_PTR callback; // pointer to the callback. If uMsg != 0 callback is HWND, otherwise it is DMNPROC
+ UINT uMsg; // specify message code to post notification. if 0 callback points to DMNPROC.
+ CHAR cLetter; // drive letter.
+ UINT fFlags; // DMF_XXX
+ DWORD result; // result code. Set by async func.
+ WORD opCode; // completed opCode (DMOP_XXX). Set by async func.
+ DMFREEPROC fnFree; // you can specify function that need to be called to free data
+ HANDLE hReserved; // reserved;
+} DM_NOTIFY_PARAM;
+
+
+typedef struct _DM_UNITINFO_PARAM
+{
+ DM_NOTIFY_PARAM header;
+ DWORD dwType; // unit type
+ BOOL bReady; // unit ready flag
+ LPSTR pszDesc; // pointer to the buffer with unit description.
+ INT cchDesc; // [in] length of the decription buffer in chars. [out] number of characters written. If error value is negative and show minimum required buffer
+ LPSTR pszFirmware; // pointer to the buffer with FirmWare ( firmware version is always 4 chars)
+ INT cchFirmware; // [in] length of the firmware buffer in chars. [out] number of characters written. If error value is negative and show minimum required buffer
+} DM_UNITINFO_PARAM;
+
+typedef struct _DM_UNITINFO2_PARAM
+{
+ DM_NOTIFY_PARAM header;
+ DWORD *pdwTypes; // unit types vector
+ INT nTypes; // vector length (in DWORDS)
+ DWORD dwClassId; // class identifier assigned to the unit.
+ DWORD dwBusType; // type of bus to which the device is connected.
+} DM_UNITINFO2_PARAM;
+
+
+typedef struct _DM_DISCINFOEX_PARAM
+{
+ DM_NOTIFY_PARAM header;
+ DWORD dwMediumType; // type of the medium.
+ DWORD dwMediumFormat; // format of the media
+ BOOL bErasable; //
+ DWORD dwTracks; // number of tracks in the disc.
+ DWORD dwUsed; // total number of sectors used on the disc.
+ DWORD dwFree; // total number of free sectors on the disc.
+
+} DM_DISCINFOEX_PARAM;
+
+typedef struct _DM_DISCINFO2_PARAM
+{
+ DM_NOTIFY_PARAM header;
+ DWORD dwMedium; // physical type of the media.
+ BOOL bProtectedDVD; // DVD containing copy-protected content.
+ BOOL bPacketWritten; // if the media is formatted by packet writing software.
+ DWORD dwMediumEx; // physical type of the medium.
+} DM_DISCINFO2_PARAM;
+
+
+typedef struct _DM_TITLE_PARAM
+{
+ DM_NOTIFY_PARAM header;
+ LPWSTR pszTitle;
+ INT cchTitle;
+
+} DM_TITLE_PARAM;
+
+typedef struct _DM_MCI_PARAM
+{
+ DM_NOTIFY_PARAM header;
+ BOOL bReady;
+ BOOL bMediumPresent;
+ UINT uMode;
+ DWORD* pTracks; // this contains track info (first bit set to '1' if track is audio, other bits track length
+ INT nTracks;
+ LPWSTR pszMediumUID;
+ INT cchMediumUID;
+ LPWSTR pszMediumUPC;
+ INT cchMediumUPC;
+} DM_MCI_PARAM;
+
+// you responsible for freeing all bstrs (use SysFreeString())
+typedef struct _DM_IMAPI_PARAM
+{
+ DM_NOTIFY_PARAM header; // header result contains HRESULT
+ BOOL bRecorder; // Set to TRUE if IMAPI fond drive
+ BSTR bstrBasePnPID; // DMF_BASEPNPID
+ BSTR bstrVendorID; // DMF_DISPLAYNAMES
+ BSTR bstrProductID; // DMF_DISPLAYNAMES
+ BSTR bstrRevision; // DMF_DISPLAYNAMES
+ BSTR bstrPath; // DMF_PATH
+ ULONG ulDriveState; // DMF_DRIVESTATE
+ LONG fDriveType; // DMF_DRIVETYPE
+ LONG fMediaType; // DMF_QUERYMEDIATYPE
+ LONG fMediaFlags; // DMF_QUERYMEDIATYPE
+ BYTE bSessions; // DMF_QUERYMEDIAINFO
+ BYTE bLastTrack; // DMF_QUERYMEDIAINFO
+ ULONG ulStartAddress; // DMF_QUERYMEDIAINFO
+ ULONG ulNextWritable; // DMF_QUERYMEDIAINFO
+ ULONG ulFreeBlocks; // DMF_QUERYMEDIAINFO
+
+} DM_IMAPI_PARAM;
+
+BOOL DriveManager_Initialize(DMNPROC DMNProc, BOOL bSuspended);
+BOOL DriveManager_Uninitialize(INT msExitWaitTime);
+BOOL DriveManager_Suspend(void);
+BOOL DriveManager_Resume(BOOL bUpdate);
+INT DriveManager_GetDriveList(CHAR *pLetters, INT cchSize);
+BOOL DriveManager_Update(BOOL bAsync); // check all drives and discs
+BOOL DriveManager_SetDriveMode(CHAR cLetter, CHAR cMode);
+CHAR DriveManager_GetDriveMode(CHAR cLetter);
+DWORD DriveManager_GetDriveType(CHAR cLetter);
+BOOL DriveManager_Eject(CHAR cLetter, INT nCmd); // nCmd = DM_EJECT_XXX
+BOOL DriveManager_IsUnitReady(BOOL *pbReady);
+BOOL DriveManager_IsMediumInserted(CHAR cLetter);
+// PrimoSDK async calls
+BOOL DriveManager_GetUnitInfo(DM_UNITINFO_PARAM *puip);
+BOOL DriveManager_GetUnitInfo2(DM_UNITINFO2_PARAM *puip);
+BOOL DriveManager_GetDiscInfoEx(DM_DISCINFOEX_PARAM *pdip);
+BOOL DriveManager_GetDiscInfo2(DM_DISCINFO2_PARAM *pdip);
+
+BOOL DriveManager_QueryTitle(DM_TITLE_PARAM *pdtp);
+
+// MCI async
+BOOL DriveManager_GetMCIInfo(DM_MCI_PARAM *pmcip);
+
+//IMAPI async
+BOOL DriveManager_GetIMAPIInfo(DM_IMAPI_PARAM *pIMAPI);
+
+
+#endif //NULLSOFT_MLDISC_DRIVEMANAGER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/drives.cpp b/Src/Plugins/Library/ml_disc/drives.cpp
new file mode 100644
index 00000000..cdc29c27
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/drives.cpp
@@ -0,0 +1,243 @@
+#include "main.h"
+#include <Windows.h>
+#include "resource.h"
+#include "drives.h"
+#include <strsafe.h>
+
+//
+// UNIT TYPES AND MEDIA TYPES
+//
+#define PRIMOSDK_CDROM 0x00000201
+#define PRIMOSDK_CDR 0x00000202
+#define PRIMOSDK_CDRW 0x00000203
+#define PRIMOSDK_DVDR 0x00000204
+#define PRIMOSDK_DVDROM 0x00000205
+#define PRIMOSDK_DVDRAM 0x00000206
+#define PRIMOSDK_DVDRW 0x00000207
+#define PRIMOSDK_ROBOTICS 0x00000208
+#define PRIMOSDK_DVDPRW 0x00000209
+#define PRIMOSDK_DVDPR 0x00000210
+#define PRIMOSDK_DDCDROM 0x00000211
+#define PRIMOSDK_DDCDR 0x00000212
+#define PRIMOSDK_DDCDRW 0x00000213
+#define PRIMOSDK_DVDPR9 0x00000214
+#define PRIMOSDK_DVDR9 0x00000215
+
+//
+
+#define PRIMOSDK_OTHER 0x00000220
+
+// bus type
+#define PRIMOSDK_UNKNOWN 0
+#define PRIMOSDK_ATAPI 1
+#define PRIMOSDK_SCSI 2
+#define PRIMOSDK_1394 3
+#define PRIMOSDK_USB 4
+#define PRIMOSDK_USB2 5
+
+const wchar_t *typeText[] = {L"UNKNOWN", L"CD-ROM", L"CD-R", L"CD-RW", L"DVD-ROM", L"DVD-R", L"DVD-RW", L"DVD+R", L"DVD+RW", L"DVD-RAM", L"DDCD", L"DDCD-R", L"DDCD-RW", L"DL DVD+R", L"DL DVD-R"};
+const wchar_t *busText[] = {L"UNKNOWN", L"ATAPI", L"SCSI", L"1394", L"USB", L"USB2"};
+
+Drives::Drives(void)
+{
+}
+
+Drives::~Drives(void)
+{
+ Clear();
+}
+
+void Drives::AddDrive(wchar_t letter, unsigned int typeCode, wchar_t* description, const wchar_t *extInfo)
+{
+ OPTICAL_DRIVE drive;
+ drive.letter = (wchar_t)CharUpperW((wchar_t*)letter);
+ drive.typeCode = typeCode;
+ drive.modelInfo = (NULL == description) ? NULL : _wcsdup(description);
+ drive.busType = 0;
+ drive.disc = NULL;
+ if (extInfo)
+ { // extInfo format: bysType;typeCode1;typeCode2;...;typeCoden
+ const wchar_t *desc = extInfo;
+ drive.nTypeList = 0;
+
+ while(desc[0] != 0x00) {desc = CharNextW(desc); if (desc[0] == ';') drive.nTypeList ++;}
+ if (drive.nTypeList) drive.nTypeList;
+
+ drive.pTypeList = (int*) malloc(sizeof(int) * drive.nTypeList);
+ int *list = drive.pTypeList;
+ const wchar_t *start = extInfo;
+ const wchar_t *end = extInfo;
+ BOOL cont;
+ do
+ {
+ while(end[0] != ';' && end[0] != 0x00) end = CharNextW(end);
+
+ cont = (end[0] == ';') ;
+
+ if (start == extInfo)
+ drive.busType = _wtoi(start);
+ else
+ {
+ *list = _wtoi(start);
+ list++;
+ }
+
+ if(cont)
+ {
+ end = CharNextW(end);
+ start = end;
+ }
+ }
+ while(cont);
+ }
+ else
+ {
+ drive.nTypeList = 1;
+ drive.pTypeList = (int*) malloc(sizeof(int) * drive.nTypeList);
+ drive.pTypeList[0] = typeCode;
+ }
+
+ Map<wchar_t, OPTICAL_DRIVE>::MapPair insert_pair(drive.letter, drive);
+ driveList.insert(insert_pair);
+}
+
+void Drives::Clear(void)
+{
+ for (c_iter = driveList.begin(); c_iter != driveList.end(); c_iter++)
+ {
+ if (c_iter->second.modelInfo) free(c_iter->second.modelInfo);
+ if (c_iter->second.pTypeList) free(c_iter->second.pTypeList);
+ if (c_iter->second.disc) delete(c_iter->second.disc);
+ }
+ driveList.clear();
+}
+
+unsigned int Drives::GetCount(void)
+{
+ return (unsigned int)driveList.size();
+}
+
+const OPTICAL_DRIVE* Drives::GetFirst(void)
+{
+ c_iter = driveList.begin();
+ return (c_iter == driveList.end()) ? NULL : &c_iter->second;
+}
+
+const OPTICAL_DRIVE* Drives::GetNext(void)
+{
+ return (++c_iter == driveList.end()) ? NULL : &c_iter->second;
+}
+
+BOOL Drives::IsRecorder(const OPTICAL_DRIVE *drive)
+{
+ BOOL recorder = FALSE;
+ switch (drive->typeCode)
+ {
+ case PRIMOSDK_CDR:
+ case PRIMOSDK_CDRW:
+ case PRIMOSDK_DVDR:
+ case PRIMOSDK_DVDRW:
+ case PRIMOSDK_DVDPR:
+ case PRIMOSDK_DVDPRW:
+ case PRIMOSDK_DVDRAM:
+ case PRIMOSDK_DDCDR:
+ case PRIMOSDK_DDCDRW:
+ case PRIMOSDK_DVDPR9:
+ case PRIMOSDK_DVDR9:
+ recorder = TRUE;
+ break;
+ }
+ return recorder;
+}
+
+const wchar_t* Drives::GetTypeString(int typeCode)
+{
+ int index = 0;
+ switch (typeCode)
+ {
+ case PRIMOSDK_CDROM:
+ index = 1;
+ break;
+ case PRIMOSDK_CDR:
+ index = 2;
+ break;
+ case PRIMOSDK_CDRW:
+ index = 3;
+ break;
+ case PRIMOSDK_DVDROM:
+ index = 4;
+ break;
+ case PRIMOSDK_DVDR:
+ index = 5;
+ break;
+ case PRIMOSDK_DVDRW:
+ index = 6;
+ break;
+ case PRIMOSDK_DVDPR:
+ index = 7;
+ break;
+ case PRIMOSDK_DVDPRW:
+ index = 8;
+ break;
+ case PRIMOSDK_DVDRAM:
+ index = 9;
+ break;
+ case PRIMOSDK_DDCDROM:
+ index = 10;
+ break;
+ case PRIMOSDK_DDCDR:
+ index = 11;
+ break;
+ case PRIMOSDK_DDCDRW:
+ index = 12;
+ break;
+ case PRIMOSDK_DVDPR9:
+ index = 13;
+ break;
+ case PRIMOSDK_DVDR9:
+ index = 14;
+ break;
+ default:
+ static wchar_t tmp2[64];
+ return WASABI_API_LNGSTRINGW_BUF(plugin.hDllInstance,IDS_UNKNOWN,tmp2,64);
+ }
+ return typeText[index];
+}
+
+const wchar_t* Drives::GetBusString(int busCode)
+{
+ int index = 0;
+ switch (busCode)
+ {
+ case PRIMOSDK_ATAPI:
+ index = 1;
+ break;
+ case PRIMOSDK_SCSI:
+ index = 2;
+ break;
+ case PRIMOSDK_1394:
+ index = 3;
+ break;
+ case PRIMOSDK_USB:
+ index = 4;
+ break;
+ case PRIMOSDK_USB2:
+ index = 5;
+ break;
+ default:
+ static wchar_t tmp3[64];
+ return WASABI_API_LNGSTRINGW_BUF(plugin.hDllInstance,IDS_UNKNOWN,tmp3,64);
+ }
+ return busText[index];
+}
+
+const wchar_t* Drives::GetFormatedString(const OPTICAL_DRIVE *drv, wchar_t *buffer, size_t size, BOOL useFullName)
+{
+ StringCchPrintfW(buffer, size, WASABI_API_LNGSTRINGW(plugin.hDllInstance,IDS_X_DRIVE_BRACKET_X),
+ GetTypeString(drv->typeCode), drv->letter);
+ if (useFullName && drv->modelInfo)
+ {
+ StringCchPrintfW(buffer, size, L"%s - %s", buffer, drv->modelInfo);
+ }
+ return buffer;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/drives.h b/Src/Plugins/Library/ml_disc/drives.h
new file mode 100644
index 00000000..4984607e
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/drives.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOFT_DRIVEINFO_HEADER
+#define NULLSOFT_DRIVEINFO_HEADER
+
+#include "../nu/Map.h"
+#include ".\discinfo.h"
+
+
+typedef struct
+{
+ wchar_t letter;
+ int typeCode;
+ wchar_t* modelInfo;
+ int busType;
+ int *pTypeList; // all supported types
+ int nTypeList; // number of supported tpyes
+ DiscInfo *disc; // inserted disc info;
+} OPTICAL_DRIVE;
+
+#define MAX_FORMAT_DRIVE_STRING 512
+
+class Drives
+{
+public:
+ Drives(void);
+ ~Drives(void);
+
+public:
+ void AddDrive(wchar_t letter, unsigned int typeCode, wchar_t* description, const wchar_t *extInfo);
+ void Clear(void);
+ unsigned int GetCount(void);
+ const OPTICAL_DRIVE* GetFirst(void);
+ const OPTICAL_DRIVE* GetNext(void); // if returns NULL - means no more
+
+ static BOOL IsRecorder(const OPTICAL_DRIVE *drive);
+
+ static const wchar_t* GetTypeString(int typeCode);
+ static const wchar_t* GetBusString(int busCode);
+ static const wchar_t* GetFormatedString(const OPTICAL_DRIVE *drv, wchar_t *buffer, size_t size, BOOL useFullName = TRUE);
+private:
+ Map<wchar_t, OPTICAL_DRIVE> driveList;
+ Map<wchar_t, OPTICAL_DRIVE>::const_iterator c_iter;
+};
+
+#endif //NULLSOFT_DRIVEINFO_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/formatfilename.cpp b/Src/Plugins/Library/ml_disc/formatfilename.cpp
new file mode 100644
index 00000000..cca31507
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/formatfilename.cpp
@@ -0,0 +1,269 @@
+#include "main.h"
+#include "../nu/ns_wc.h"
+#include "../winamp/wa_ipc.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define TID_UNKNOWN 0
+#define TID_ARTIST 1
+#define TID_ALBUM 2
+#define TID_TITLE 3
+#define TID_GENRE 4
+#define TID_YEAR 5
+#define TID_TRACKARTIST 6
+#define TID_FILENAME 7
+#define TID_EXTENSION 8
+#define TID_DISC 9
+#define TID_DISCS 10
+
+#define TOKEN_ARTIST TEXT("<artist>")
+#define TOKEN_ALBUM TEXT("<album>")
+#define TOKEN_TITLE TEXT("<title>")
+#define TOKEN_GENRE TEXT("<genre>")
+#define TOKEN_YEAR TEXT("<year>")
+#define TOKEN_TRACKARTIST TEXT("<trackartist>")
+#define TOKEN_FILENAME TEXT("<filename>")
+#define TOKEN_EXTENSION TEXT("<extension>")
+#define TOKEN_DISC TEXT("<disc>")
+#define TOKEN_DISCS TEXT("<discs>")
+
+#define TOKEN_LEN_ARTIST 8
+#define TOKEN_LEN_ALBUM 7
+#define TOKEN_LEN_TITLE 7
+#define TOKEN_LEN_GENRE 7
+#define TOKEN_LEN_YEAR 6
+#define TOKEN_LEN_TRACKARTIST 13
+#define TOKEN_LEN_FILENAME 10
+#define TOKEN_LEN_EXTENSION 11
+#define TOKEN_LEN_DISC 6
+#define TOKEN_LEN_DISCS 7
+
+#define STRCOMP_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#define CHECK_TOKEN(token, format) (CSTR_EQUAL == CompareString(STRCOMP_INVARIANT, NORM_IGNORECASE, TOKEN_##token##, TOKEN_LEN_##token##, format, TOKEN_LEN_##token##))
+
+
+static TCHAR PathValidateChar(TCHAR cVal, BOOL bAllowBackSlash)
+{
+ switch(cVal)
+ {
+ case TEXT('\\'): if (!bAllowBackSlash) return TEXT('-');
+ break;
+ case TEXT('/'): if (!bAllowBackSlash) return TEXT('-');
+ return TEXT('\\');
+
+ case TEXT(':'): return TEXT('-');
+ case TEXT('*'): return TEXT('_');
+ case TEXT('?'): return TEXT('_');
+ case TEXT('\"'): return TEXT('\'');
+ case TEXT('<'): return TEXT('(');
+ case TEXT('>'): return TEXT(')');
+ case TEXT('|'): return TEXT('_');
+ }
+ return cVal;
+}
+
+void CleanupDirectoryString(LPTSTR pszDirectory)
+{
+ PathUnquoteSpaces(pszDirectory);
+ PathRemoveBlanks(pszDirectory);
+
+ LPTSTR pc = pszDirectory;
+ while (TEXT('\0') != *pc) { if (TEXT('/') == *pc) *pc = TEXT('\\'); pc++; }
+
+ if (pc > pszDirectory)
+ {
+ pc--;
+ while (pszDirectory != pc &&
+ (TEXT('.') == *pc || TEXT(' ') == *pc || TEXT('\\') == *pc))
+ {
+ *pc = TEXT('\0');
+ pc--;
+ }
+ }
+}
+
+LPWSTR GetExtensionString(LPWSTR pszBuffer, INT cchBufferMax, DWORD fourcc)
+{
+ char configExt[10] = { '\0', };
+ pszBuffer[0] = L'\0';
+ convertConfigItem cfi;
+ cfi.configfile = 0;
+ cfi.data = configExt;
+ cfi.format = fourcc;
+ cfi.item = "extension";
+ cfi.len = 10;
+ SENDWAIPC(plugin.hwndWinampParent, IPC_CONVERT_CONFIG_GET_ITEM, (WPARAM)&cfi);
+ if ('\0' != *configExt)
+ {
+ if (!MultiByteToWideCharSZ(CP_ACP, 0, configExt, -1, pszBuffer, cchBufferMax))
+ return NULL;
+ }
+ else
+ {
+ if (cchBufferMax < 5) return NULL;
+ pszBuffer[0] = (TCHAR)((fourcc) & 0xff);
+ pszBuffer[1] = (TCHAR)((fourcc >> 8) & 0xff);
+ pszBuffer[2] = (TCHAR)((fourcc >> 16) & 0xff);
+ pszBuffer[3] = TEXT('\0');
+ for (LPTSTR p = &pszBuffer[2]; p >= pszBuffer && TEXT(' ') == *p; p--) *p = TEXT('\0');
+ }
+ return pszBuffer;
+}
+
+// if trackno is 0xdeadbeef, or title is 0, they are both ignored
+// TODO: use ATF instead
+HRESULT FormatFileName(LPTSTR pszTextOut, INT cchTextMax, LPCTSTR pszFormat,
+ INT nTrackNo, LPCTSTR pszArtist,
+ LPCTSTR pszAlbum, LPCTSTR pszTitle,
+ LPCTSTR pszGenre, LPCTSTR pszYear,
+ LPCTSTR pszTrackArtist,
+ LPCTSTR pszFileName, LPCTSTR pszDisc)
+{
+ HRESULT hr = S_OK;
+ TCHAR szBuffer[MAX_PATH] = {0};
+ LPTSTR pszStart = pszTextOut + lstrlen(pszTextOut);
+
+ while (pszFormat && TEXT('\0') != *pszFormat)
+ {
+ int whichstr = TID_UNKNOWN;
+ if (*pszFormat == TEXT('#') && nTrackNo != 0xdeadbeef)
+ {
+ int cnt = 0;
+ while (pszFormat && *pszFormat == TEXT('#')) { pszFormat++; cnt++; }
+ if (cnt > 8) cnt = 8;
+
+ TCHAR szFormat[32] = {0};
+ hr = StringCchPrintf(szFormat, ARRAYSIZE(szFormat), TEXT("%%%02dd"), cnt);
+ if (S_OK == hr) StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), szFormat, nTrackNo);
+ if (S_OK == hr) StringCchCat(pszTextOut, cchTextMax, szBuffer);
+ if (S_OK != hr) return hr;
+ }
+ else if (pszArtist && CHECK_TOKEN(ARTIST, pszFormat)) whichstr = TID_ARTIST;
+ else if (pszAlbum && CHECK_TOKEN(ALBUM, pszFormat)) whichstr = TID_ALBUM;
+ else if (pszTitle && CHECK_TOKEN(TITLE, pszFormat)) whichstr = TID_TITLE;
+ else if (pszGenre && CHECK_TOKEN(GENRE, pszFormat)) whichstr = TID_GENRE;
+ else if (pszYear && CHECK_TOKEN(YEAR, pszFormat)) whichstr = TID_YEAR;
+ else if (pszTrackArtist && CHECK_TOKEN(TRACKARTIST, pszFormat)) whichstr = TID_TRACKARTIST;
+ else if (pszFileName && CHECK_TOKEN(FILENAME, pszFormat)) whichstr = TID_FILENAME;
+ else if (pszFileName && CHECK_TOKEN(EXTENSION, pszFormat)) whichstr = TID_EXTENSION;
+ else if (pszDisc && CHECK_TOKEN(DISC, pszFormat)) whichstr = TID_DISC;
+ else if (pszDisc && CHECK_TOKEN(DISCS, pszFormat)) whichstr = TID_DISCS;
+ else
+ {
+ INT l = lstrlen(pszTextOut);
+ pszTextOut += l;
+ cchTextMax -= l;
+ pszTextOut[0] = *pszFormat++;
+ if (cchTextMax < 2) return STRSAFE_E_INSUFFICIENT_BUFFER;
+ pszTextOut[0] = PathValidateChar(pszTextOut[0], TRUE);
+ if (TEXT('\\') == *pszTextOut)
+ {
+ // remove end spaces and dots
+ while (pszTextOut > pszStart &&
+ (TEXT('\\') == *(pszTextOut - 1) || TEXT(' ') == *(pszTextOut - 1) || TEXT('.') == *(pszTextOut - 1)))
+ {
+ pszTextOut--;
+ cchTextMax--;
+ }
+
+ if (pszTextOut == pszStart)
+ {
+ pszTextOut--;
+ cchTextMax--;
+ }
+ else *pszTextOut = TEXT('\\');
+ }
+ pszTextOut++;
+ cchTextMax--;
+ *pszTextOut = TEXT('\0');
+ if (S_OK != hr) return hr;
+ }
+ if (whichstr != TID_UNKNOWN)
+ {
+ LPCTSTR pszSrc = NULL;
+ int islow = IsCharLower(pszFormat[1]) && IsCharLower(pszFormat[2]);
+ int ishi = IsCharUpper(pszFormat[1]) && IsCharUpper(pszFormat[2]);
+ switch(whichstr)
+ {
+ case TID_ARTIST: pszSrc = pszArtist; pszFormat += TOKEN_LEN_ARTIST; break;
+ case TID_ALBUM: pszSrc = pszAlbum; pszFormat += TOKEN_LEN_ALBUM; break;
+ case TID_TITLE: pszSrc = pszTitle; pszFormat += TOKEN_LEN_TITLE; break;
+ case TID_GENRE: pszSrc = pszGenre; pszFormat += TOKEN_LEN_GENRE; break;
+ case TID_YEAR: pszSrc = pszYear; pszFormat += TOKEN_LEN_YEAR; break;
+ case TID_TRACKARTIST: pszSrc = pszTrackArtist; pszFormat += TOKEN_LEN_TRACKARTIST; break;
+ case TID_DISC:
+ if (pszDisc && *pszDisc && TEXT('\0') == *pszDisc) pszSrc = L"1";
+ else
+ {
+ // default to 1 when we've not got a proper value passed to us
+ int disc = _wtoi(pszDisc);
+ if(disc <= 0) disc = 1;
+ StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), L"%d", disc);
+ pszSrc = szBuffer;
+ }
+ pszFormat += TOKEN_LEN_DISC;
+ break;
+ case TID_DISCS:
+ if (pszDisc && *pszDisc && TEXT('\0') == *pszDisc) pszSrc = L"1";
+ else
+ {
+ LPTSTR pszTemp = wcschr((LPTSTR)pszDisc, L'/');
+ if(pszTemp == NULL) pszTemp = wcschr((LPTSTR)pszDisc, L'\\');
+ if(pszTemp == NULL)
+ {
+ pszTemp = (LPTSTR)pszDisc;
+ }
+ else
+ {
+ pszTemp = CharNext(pszTemp);
+ }
+ int disc = _wtoi(pszTemp);
+ if(disc <= 0) disc = 1;
+ StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), L"%d", disc);
+ pszSrc = szBuffer;
+ }
+ pszFormat += TOKEN_LEN_DISCS;
+ break;
+ case TID_FILENAME:
+ pszSrc = PathFindExtension(pszFileName);
+ if (TEXT('\0') == *pszSrc) pszSrc = pszFileName;
+ else
+ {
+ StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), pszFileName);
+ PathRemoveExtension(szBuffer);
+ pszSrc = szBuffer;
+ }
+ pszFormat += TOKEN_LEN_FILENAME;
+ break;
+ case TID_EXTENSION:
+ pszSrc = PathFindExtension(pszFileName); pszFormat += TOKEN_LEN_EXTENSION;
+ while (pszTextOut > pszStart && TEXT('.') == *(pszTextOut - 1))
+ {
+ pszTextOut--;
+ *pszTextOut = TEXT('\0');
+ cchTextMax++;
+ }
+ break;
+ }
+
+ INT l = lstrlen(pszTextOut);
+ pszTextOut += l;
+ cchTextMax -= l;
+
+ while (pszSrc && TEXT('\0') != *pszSrc && cchTextMax > 0)
+ {
+ pszTextOut[0] = pszSrc[0];
+ if (ishi) CharUpperBuffW(pszTextOut, 1);
+ else if (islow) CharLowerBuffW(pszTextOut, 1);
+ pszTextOut[0] = PathValidateChar(pszTextOut[0], FALSE);
+ cchTextMax--;
+ if (cchTextMax) pszTextOut++;
+ pszSrc++;
+ }
+ pszTextOut[0] = TEXT('\0');
+ if (0 == cchTextMax) return STRSAFE_E_INSUFFICIENT_BUFFER;
+ }
+ }
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/helpwnd.cpp b/Src/Plugins/Library/ml_disc/helpwnd.cpp
new file mode 100644
index 00000000..3bbce8be
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/helpwnd.cpp
@@ -0,0 +1,350 @@
+#include "main.h"
+#include "./resource.h"
+#include "../nu/trace.h"
+#include <strsafe.h>
+
+#define MLHELP_PROP TEXT("MLHELP")
+
+typedef struct __SIMPLEHELP
+{
+ LPCWSTR pszTitle;
+ LPCWSTR pszCaption;
+ LPCWSTR pszText;
+ HWND hOwner;
+ UINT uFlags;
+} SIMPLEHELP;
+
+typedef struct __MLHELP
+{
+ HWND hOwner;
+ UINT uFlags;
+ LONG width;
+ LONG height;
+} MLHELP;
+
+#define GetHelp(__hwnd) ((MLHELP*)GetProp((__hwnd), MLHELP_PROP))
+
+#define CLIENT_MIN_WIDTH 280
+#define CLIENT_MIN_HEIGHT 200
+#define CLIENT_MAX_WIDTH 800
+#define CLIENT_MAX_HEIGHT 600
+#define BORDER_SPACE 10
+
+static INT_PTR CALLBACK SimpleHelp_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+HWND MLDisc_ShowHelp(HWND hOwner, LPCWSTR pszWindowTitle, LPCWSTR pszCaption, LPCWSTR pszText, UINT uFlags)
+{
+ SIMPLEHELP help;
+ help.pszTitle = pszWindowTitle;
+ help.pszCaption = pszCaption;
+ help.pszText = pszText;
+ help.uFlags = uFlags;
+ help.hOwner = hOwner;
+
+ if (HF_DOMODAL & uFlags)
+ {
+ WASABI_API_DIALOGBOXPARAMW(IDD_SIMPLEHELP, hOwner, SimpleHelp_DialogProc, (LPARAM)&help);
+ return NULL;
+ }
+
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_SIMPLEHELP, hOwner, SimpleHelp_DialogProc, (LPARAM)&help);
+}
+
+static BOOL FindPrefferedSizeEx(HDC hdc, LPCTSTR pszText, LPCTSTR pszNewLine, SIZE *pSize)
+{
+ if (!pSize) return FALSE;
+ pSize->cx = 0; pSize->cy = 0;
+ if (!hdc || !pszText || !pszNewLine) return FALSE;
+ LPCTSTR pszBlock = pszText;
+ LPCTSTR pszCursor = pszBlock;
+ INT cchSep = lstrlenW(pszNewLine);
+ INT matched = 0;
+ for(;;)
+ {
+ if (*pszCursor)
+ {
+ if (*pszCursor == pszNewLine[matched]) matched++;
+ else matched = 0;
+ pszCursor++;
+ }
+ if (matched == cchSep || TEXT('\0') == *pszCursor)
+ {
+ SIZE sz;
+
+ INT l = (INT)(size_t)((pszCursor - pszBlock) - matched);
+ if (l > 0)
+ {
+ if (!GetTextExtentPoint32(hdc, pszBlock, l, &sz)) return FALSE;
+ }
+ else
+ {
+ if (!GetTextExtentPoint32(hdc, TEXT("\n"), 1, &sz)) return FALSE;
+ sz.cx = 0;
+ }
+
+
+ if (pSize->cx < sz.cx) pSize->cx= sz.cx;
+ pSize->cy += sz.cy;
+
+ if (TEXT('\0') == *pszCursor) break;
+ else
+ {
+ matched = 0;
+ pszBlock = pszCursor;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static BOOL FindPrefferedSize(HWND hwnd, LPCTSTR pszText, LPCTSTR pszNewLine, SIZE *pSize)
+{
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_PARENTCLIP);
+ if (!hdc) return FALSE;
+ HFONT hf, hfo;
+ hf = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0L);
+ if (NULL == hf) hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ hfo = (NULL != hf) ? (HFONT)SelectObject(hdc, hf) : NULL;
+
+ BOOL br = FindPrefferedSizeEx(hdc, pszText, pszNewLine, pSize);
+
+ if (hfo) SelectObject(hdc, hfo);
+ ReleaseDC(hwnd, hdc);
+
+ return br;
+}
+
+static INT_PTR SimpleHlp_OnInitDialog(HWND hdlg, HWND hFocus, LPARAM lParam)
+{
+ SIMPLEHELP *pHelp = (SIMPLEHELP*)lParam;
+ SIZE sizeCaption = { 0, 0 };
+ SIZE sizeText = { 0, 0 };
+ SIZE sizeClient = {0, 0};
+ SIZE sizeButton = {0, 0};
+
+ MLHELP *pmlh = (MLHELP*)calloc(1, sizeof(MLHELP));
+ if (pmlh)
+ {
+ pmlh->hOwner = pHelp->hOwner;
+ pmlh->uFlags = pHelp->uFlags;
+ pmlh->height = 0;
+ pmlh->width = 0;
+ }
+ SetProp(hdlg, MLHELP_PROP, (HANDLE)pmlh);
+
+ HWND hctrl;
+ if(pHelp)
+ {
+
+ WCHAR szBuffer[4096] = {0};
+ if (pHelp->pszTitle)
+ {
+ if (IS_INTRESOURCE(pHelp->pszCaption))
+ {
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pHelp->pszTitle, szBuffer, ARRAYSIZE(szBuffer));
+ pHelp->pszTitle = szBuffer;
+ }
+ SetWindowText(hdlg, pHelp->pszTitle);
+ }
+
+ if (pHelp->pszCaption)
+ {
+ if (IS_INTRESOURCE(pHelp->pszCaption))
+ {
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pHelp->pszCaption, szBuffer, ARRAYSIZE(szBuffer));
+ pHelp->pszCaption = szBuffer;
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_LBL_CAPTION)))
+ {
+ FindPrefferedSize(hctrl, pHelp->pszCaption, TEXT("\n"), &sizeCaption);
+ SetWindowText(hctrl, pHelp->pszCaption);
+ }
+ }
+
+ if (pHelp->pszText)
+ {
+ if (IS_INTRESOURCE(pHelp->pszText))
+ {
+ WCHAR form_szBuffer[4096] = {0}, *fszB = form_szBuffer, *szB = szBuffer;
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pHelp->pszText, form_szBuffer, ARRAYSIZE(form_szBuffer));
+ while(fszB && *fszB){
+ if(*fszB == L'\n' && *CharPrevW(form_szBuffer,fszB) != L'\r'){
+ *szB = L'\r';
+ szB = CharNextW(szB);
+ }
+ *szB = *fszB;
+ szB = CharNextW(szB);
+ fszB = CharNextW(fszB);
+ }
+ *szB = 0;
+ pHelp->pszText = szBuffer;
+ }
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_EDT_TEXT)))
+ {
+ FindPrefferedSize(hctrl, pHelp->pszText, TEXT("\r\n"), &sizeText);
+ SetWindowText(hctrl, pHelp->pszText);
+ }
+ }
+
+ if (0 == (HF_ALLOWRESIZE & pHelp->uFlags))
+ {
+ SetWindowLongPtrW(hdlg, GWL_EXSTYLE, GetWindowLongPtrW(hdlg, GWL_EXSTYLE) | WS_EX_DLGMODALFRAME);
+ SetWindowLongPtrW(hdlg, GWL_STYLE, (GetWindowLongPtrW(hdlg, GWL_STYLE) & ~WS_THICKFRAME) | DS_MODALFRAME);
+ }
+ }
+
+ if (sizeText.cx > 0) sizeText.cx += GetSystemMetrics(SM_CXVSCROLL);
+
+ sizeClient.cx = ((sizeText.cx > sizeCaption.cx) ? sizeText.cx : sizeCaption.cx) + 8;
+ if (sizeClient.cx < CLIENT_MIN_WIDTH) sizeClient.cx = CLIENT_MIN_WIDTH;
+ if (sizeClient.cx > CLIENT_MAX_WIDTH) sizeClient.cx = CLIENT_MAX_WIDTH;
+ sizeText.cx = sizeClient.cx;
+ sizeCaption.cx = sizeClient.cx;
+ sizeClient.cx += BORDER_SPACE * 2;
+
+ if (sizeCaption.cy > 0) sizeCaption.cy += 16;
+ if (sizeCaption.cy > CLIENT_MAX_HEIGHT/3) sizeCaption.cy = CLIENT_MAX_HEIGHT/3;
+
+ if (sizeText.cy > 0) sizeText.cy += 16;
+ if (sizeText.cy < (CLIENT_MIN_HEIGHT - sizeCaption.cy)) sizeText.cy = (CLIENT_MIN_HEIGHT - sizeCaption.cy);
+ if (sizeText.cy > (CLIENT_MAX_HEIGHT - sizeCaption.cy)) sizeText.cy = (CLIENT_MAX_HEIGHT - sizeCaption.cy);
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDCANCEL)))
+ {
+ RECT rw;
+ if (GetWindowRect(hctrl, &rw)) { sizeButton.cx = rw.right - rw.left; sizeButton.cy = rw.bottom - rw.top; }
+ }
+
+ LONG top = BORDER_SPACE;
+ sizeClient.cy = BORDER_SPACE +sizeCaption.cy + sizeText.cy + 8 + sizeButton.cy + BORDER_SPACE;
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_LBL_CAPTION)))
+ {
+ if (0 == sizeCaption.cy) EnableWindow(hctrl, FALSE);
+ SetWindowPos(hctrl, NULL, BORDER_SPACE, top, sizeCaption.cx, sizeCaption.cy, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
+ top += sizeCaption.cy;
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_EDT_TEXT)))
+ {
+ if (0 == sizeText.cy) EnableWindow(hctrl, FALSE);
+ SetWindowPos(hctrl, NULL, BORDER_SPACE, top, sizeText.cx, sizeText.cy, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDCANCEL)))
+ {
+ SetWindowPos(hctrl, NULL, sizeClient.cx - BORDER_SPACE - sizeButton.cx, sizeClient.cy - BORDER_SPACE - sizeButton.cy,
+ sizeButton.cx, sizeButton.cy, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
+ }
+
+ RECT rw, rc;
+ if (GetClientRect(hdlg, &rc) && GetWindowRect(hdlg, &rw))
+ {
+ sizeClient.cx += ((rw.right - rw.left) - (rc.right - rc.left));
+ sizeClient.cy += ((rw.bottom - rw.top) - (rc.bottom - rc.top));
+ }
+
+ SetRect(&rw, 0, 0, 0, 0);
+
+ if (pHelp->hOwner && GetWindowRect(pHelp->hOwner, &rw))
+ {
+ rw.left += ((rw.right - rw.left) - sizeClient.cx)/2;
+ rw.top += ((rw.bottom - rw.top) - sizeClient.cy)/2;
+ }
+ UINT swpFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | ((HF_DOMODAL & pHelp->uFlags) ? SWP_NOZORDER : 0);
+ pmlh->width = sizeClient.cx;
+ pmlh->height = sizeClient.cy;
+ SetWindowPos(hdlg, HWND_TOP, rw.left, rw.top, sizeClient.cx, sizeClient.cy, swpFlags);
+ return FALSE;
+}
+
+static void SimpleHelp_OnDestroy(HWND hdlg)
+{
+ MLHELP *pHelp = GetHelp(hdlg);
+ RemoveProp(hdlg, MLHELP_PROP);
+
+ if (pHelp && 0 == (HF_DOMODAL & pHelp->uFlags) && pHelp->hOwner && IsWindow(pHelp->hOwner))
+ SendMessageW(pHelp->hOwner, WM_PARENTNOTIFY, MAKEWPARAM(WM_DESTROY, 0), (LPARAM)hdlg);
+ if (pHelp) free(pHelp);
+}
+
+static void SimpleHlp_OnCommand(HWND hdlg, INT ctrlId, INT eventId, HWND hctrl)
+{
+ MLHELP *pHelp = GetHelp(hdlg);
+ switch(ctrlId)
+ {
+ case IDCANCEL:
+ case IDOK:
+ if (!pHelp || (HF_DOMODAL & pHelp->uFlags)) EndDialog(hdlg, ctrlId);
+ else DestroyWindow(hdlg);
+ break;
+ }
+}
+
+/*static void SimpleHlp_OnWindowPosChanging(HWND hdlg, WINDOWPOS *pwp)
+{
+ MLHELP *pHelp = GetHelp(hdlg);
+ if (!pHelp) return;
+
+ if (0 == (SWP_NOSIZE & pwp->flags))
+ {
+ if (pwp->cx < CLIENT_MIN_WIDTH) pwp->cx = CLIENT_MIN_WIDTH;
+ if (pwp->cx > CLIENT_MAX_WIDTH) pwp->cx = CLIENT_MAX_WIDTH;
+ if (pwp->cy < CLIENT_MIN_HEIGHT) pwp->cy = CLIENT_MIN_HEIGHT;
+ if (pwp->cy > CLIENT_MAX_HEIGHT) pwp->cy = CLIENT_MAX_HEIGHT;
+ }
+}
+
+static void SimpleHlp_OnWindowPosChanged(HWND hdlg, WINDOWPOS *pwp)
+{
+ MLHELP *pHelp = GetHelp(hdlg);
+ if (!pHelp) return;
+ if (0 == (SWP_NOSIZE & pwp->flags))
+ {
+ RECT rw;
+ GetWindowRect(hdlg, &rw);
+ LONG dx = (rw.right - rw.left) - pHelp->width;
+ LONG dy = (rw.bottom - rw.top) - pHelp->height;
+ pHelp->width = rw.right - rw.left;
+ pHelp->height = rw.bottom - rw.top;
+
+ HDWP hdwp = BeginDeferWindowPos(3);
+ HWND hctrl;
+ if (hdwp && 0 != dx && NULL != (hctrl = GetDlgItem(hdlg, IDC_LBL_CAPTION)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, 0, 0,
+ (rw.right - rw.left) + dx, (rw.bottom - rw.top), SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW);
+ InvalidateRect(hctrl, NULL, TRUE);
+ }
+ if (hdwp && (0 != dx || 0 != dy) &&
+ NULL != (hctrl = GetDlgItem(hdlg, IDC_EDT_TEXT)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, 0, 0,
+ (rw.right - rw.left) + dx, (rw.bottom - rw.top) + dy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW);
+ }
+ if (hdwp && (0 != dx || 0 != dy) &&
+ NULL != (hctrl = GetDlgItem(hdlg, IDCANCEL)) && GetWindowRect(hctrl, &rw))
+ {
+ MapWindowPoints(HWND_DESKTOP, hdlg, (POINT*)&rw, 2);
+ hdwp = DeferWindowPos(hdwp, hctrl, NULL, rw.left + dx, rw.top + dy, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW);
+ }
+ if (hdwp && EndDeferWindowPos(hdwp))
+ {
+ RedrawWindow(hdlg, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW | RDW_ERASENOW);
+ }
+ }
+}*/
+
+static INT_PTR CALLBACK SimpleHelp_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return SimpleHlp_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ case WM_DESTROY: SimpleHelp_OnDestroy(hdlg); break;
+ case WM_COMMAND: SimpleHlp_OnCommand(hdlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ /*case WM_WINDOWPOSCHANGING: SimpleHlp_OnWindowPosChanging(hdlg, (WINDOWPOS*)lParam); return 0;
+ case WM_WINDOWPOSCHANGED: SimpleHlp_OnWindowPosChanged(hdlg, (WINDOWPOS*)lParam); return 0;*/
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/infoBox.cpp b/Src/Plugins/Library/ml_disc/infoBox.cpp
new file mode 100644
index 00000000..23ea2313
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/infoBox.cpp
@@ -0,0 +1,145 @@
+#include "main.h"
+#include ".\infoBox.h"
+
+
+MLInfoBox::MLInfoBox(void)
+{
+ oldWndProc = NULL;
+ m_hwnd = NULL;
+ bodyBrush = NULL;
+ headerBrush = NULL;
+ headerText[0] = 0;
+
+ SetColors(RGB(0,0,0), RGB(255,255,255), RGB(0,60,0));
+
+ SetRect(&rcBody, 0,0,0,0);
+
+ drawHeader = TRUE;
+ SetRect(&rcHeader, 0,0,0,20); // default height
+
+ headerFont = NULL;
+
+}
+MLInfoBox::~MLInfoBox(void)
+{
+ SetWindowLong(m_hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)oldWndProc);
+ oldWndProc = NULL;
+
+ if (headerBrush) DeleteObject(headerBrush);
+ headerBrush = NULL;
+
+ if (bodyBrush) DeleteObject(bodyBrush);
+ bodyBrush = NULL;
+
+ if (headerFont) DeleteObject(headerFont);
+ headerFont = NULL;
+
+}
+
+void MLInfoBox::SetColors(COLORREF bodyBG, COLORREF headerFG, COLORREF headerBG)
+{
+ this->bodyBG = bodyBG;
+ this->headerFG = headerFG;
+ this->headerBG = headerBG;
+
+ if (headerBrush) DeleteObject(headerBrush);
+ headerBrush = NULL;
+ headerBrush = CreateSolidBrush(headerBG);
+
+ if (bodyBrush) DeleteObject(bodyBrush);
+ bodyBrush = NULL;
+ bodyBrush = CreateSolidBrush(bodyBG);
+
+}
+
+void MLInfoBox::Init(HWND hwnd)
+{
+ m_hwnd = hwnd;
+
+ HDC hdc = GetDC(hwnd);
+ long lfHeight;
+ lfHeight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72);
+ headerFont = CreateFontW(lfHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Arial");
+ ReleaseDC(hwnd, hdc);
+
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONGX86)(LONG_PTR)this);
+ oldWndProc= (WNDPROC)(LONG_PTR)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)newWndProc);
+ RECT rc;
+ GetWindowRect(hwnd, &rc);
+ SetSize(rc.right - rc.left, rc.bottom - rc.top);
+}
+
+void MLInfoBox::SetSize(int cx, int cy)
+{
+ int offset = 0;
+ if (drawHeader)
+ {
+ SetRect(&rcHeader, 0,0, cx, rcHeader.bottom);
+ offset = rcHeader.bottom;
+ }
+ SetRect(&rcBody, 0, offset, cx, cy);
+}
+LRESULT CALLBACK MLInfoBox::newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ MLInfoBox *box = (MLInfoBox*)(LONG_PTR)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(uMsg)
+ {
+ case WM_SIZE:
+ if (SIZE_MINIMIZED != wParam)
+ {
+ box->SetSize(LOWORD(lParam), HIWORD(lParam));
+ }
+ break;
+ case WM_ERASEBKGND:
+ {
+ HDC hdc = GetDC(hwndDlg);
+ SetTextColor(hdc, box->headerFG);
+ SetBkColor(hdc, box->headerBG);
+ RECT txtRect;
+ SetRect(&txtRect, box->rcHeader.left + 8, box->rcHeader.top + 2, box->rcHeader.right -2, box->rcHeader.bottom -2);
+ HFONT oldFont = (HFONT)SelectObject(hdc, box->headerFont);
+ GetWindowTextW(hwndDlg, box->headerText, CAPTION_LENGTH);
+ DrawTextW(hdc, box->headerText, -1, &txtRect, DT_VCENTER | DT_LEFT | DT_SINGLELINE);
+ SelectObject(hdc, oldFont);
+ ReleaseDC(hwndDlg, hdc);
+ }
+ return TRUE;
+
+
+ break;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT pt;
+ HDC hdc = BeginPaint(hwndDlg, &pt);
+ RECT drawRect ;
+ if(box->drawHeader && IntersectRect(&drawRect, &box->rcHeader, &pt.rcPaint))
+ {
+ FillRect(hdc, &drawRect, box->headerBrush);
+
+ SetTextColor(hdc, box->headerFG);
+ SetBkColor(hdc, box->headerBG);
+ SetRect(&drawRect, box->rcHeader.left + 8, box->rcHeader.top + 2, box->rcHeader.right -2, box->rcHeader.bottom -2);
+ HFONT oldFont = (HFONT)SelectObject(hdc, box->headerFont);
+ GetWindowTextW(hwndDlg, box->headerText, CAPTION_LENGTH);
+ DrawTextW(hdc, box->headerText, -1, &drawRect, DT_VCENTER | DT_LEFT | DT_SINGLELINE);
+ SelectObject(hdc, oldFont);
+ ValidateRect(hwndDlg, &drawRect);
+ }
+
+
+ if(IntersectRect(&drawRect, &box->rcBody, &pt.rcPaint))
+ {
+ FillRect(hdc, &drawRect, box->bodyBrush);
+ ValidateRect(hwndDlg, &drawRect);
+ }
+
+ EndPaint(hwndDlg, &pt);
+ }
+ break;
+ }
+
+ return CallWindowProc(box->oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/infoBox.h b/Src/Plugins/Library/ml_disc/infoBox.h
new file mode 100644
index 00000000..b945aab4
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/infoBox.h
@@ -0,0 +1,42 @@
+#ifndef NULLSOFT_ML_INFOBOX_HEADER
+#define NULLSOFT_ML_INFOBOX_HEADER
+
+#include <windows.h>
+
+#define CAPTION_LENGTH 64
+class MLInfoBox
+{
+public:
+ MLInfoBox(void);
+ ~MLInfoBox(void);
+
+public:
+
+ void SetColors(COLORREF bodyBG, COLORREF headerFG, COLORREF headerBG);
+ void Init(HWND hwnd);
+
+protected:
+ static LRESULT CALLBACK newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ void SetSize(int cx, int cy);
+
+private:
+
+ HWND m_hwnd;
+ WNDPROC oldWndProc;
+
+ wchar_t headerText[CAPTION_LENGTH];
+ BOOL drawHeader;
+
+ COLORREF headerBG;
+ COLORREF headerFG;
+ COLORREF bodyBG;
+
+ HFONT headerFont;
+
+ HBRUSH headerBrush;
+ HBRUSH bodyBrush;
+ RECT rcBody;
+ RECT rcHeader;
+};
+
+#endif // NULLSOFT_ML_INFOBOX_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/main.cpp b/Src/Plugins/Library/ml_disc/main.cpp
new file mode 100644
index 00000000..6f39f2f3
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/main.cpp
@@ -0,0 +1,1273 @@
+#include "Main.h"
+#include "ReplayGain.h"
+#include "../nu/AutoChar.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "./resource.h"
+#include "./settings.h"
+#include "./copyfiles.h"
+#include "../winamp/wa_ipc.h"
+//#include <primosdk.h>
+#include <shlwapi.h>
+#include <imapi.h>
+#include <imapierror.h>
+#include "../nu/ns_wc.h"
+#include <vector>
+#include <strsafe.h>
+
+#define VERSION_MAJOR 2
+#define VERSION_MINOR 0
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x)/sizeof(*x))
+#endif
+
+typedef struct _LISTENER
+{
+ HWND hwnd;
+ UINT uMsg;
+ CHAR cLetter;
+} LISTENER;
+
+typedef struct _NAVITEMENUMPARAM
+{
+ NAVITEMENUMPROC callback;
+ LPARAM param;
+} NAVITEMENUMPARAM;
+
+typedef enum _BTNSTATE
+{
+ BTNSTATE_NORMAL = 0,
+ BTNSTATE_HILITED = 1,
+ BTNSTATE_PRESSED = 2,
+ BTNSTATE_DISABLED = 3,
+} BTNSTATE;
+
+#define ICON_SIZE_CX 14
+#define ICON_SIZE_CY 14
+
+#define NAVBUTTON_STATECHECK_DELAY 100
+static int Init();
+static void Quit();
+
+HNAVITEM hniMain = NULL;
+static LRESULT delay_ml_startup;
+static HMLIMGLST hmlilIcons = NULL;
+LARGE_INTEGER freq;
+
+#define NAVITEM_PREFIX L"_ml_disc_"
+#define NAVITEM_PREFIX_SIZE (sizeof(NAVITEM_PREFIX)/sizeof(wchar_t))
+
+#define NCS_EX_SHOWEJECT 0x0100
+
+C_Config *g_config, *g_view_metaconf = NULL;
+HMENU g_context_menus;
+
+prefsDlgRecW myPrefsItemCD = {0};
+INT_PTR imgIndex = 0;
+wchar_t randb[64] = {0};
+static wchar_t cdrip[64];
+
+static DWORD g_navStyle = NCS_FULLROWSELECT | NCS_SHOWICONS;
+static DWORD riphash = 0;
+
+static std::vector<LPARAM> driveList;
+
+static LISTENER activeListener = { NULL, 0, };
+static WNDPROC oldWinampWndProc = NULL;
+
+api_application *WASABI_API_APP = 0;
+api_stats *AGAVE_API_STATS = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+static UINT uMsgBurnerNotify = 0;
+static UINT uMsgRipperNotify = 0;
+static UINT uMsgNavStyleUpdate = 0;
+static UINT uMsgCopyNotify = 0;
+
+static void CALLBACK Invoke_OnDriveManagerNotify(WORD wCode, INT_PTR param);
+static void Plugin_OnMLVisible(BOOL bVisible);
+void ShowHideRipBurnParent(void);
+
+
+
+static void DriveParam_RegisterDrive(DRIVE *drive)
+{
+ LPARAM param;
+ size_t index;
+
+ param = (LPARAM)drive;
+ if (NULL == param)
+ return;
+
+ index = driveList.size();
+ while(index--)
+ {
+ if(param == driveList[index])
+ return;
+ }
+
+ driveList.push_back(param);
+}
+
+static void DriveParam_UnregisterDrive(DRIVE *drive)
+{
+ LPARAM param;
+ size_t index;
+
+ param = (LPARAM)drive;
+ if (NULL == param)
+ return;
+
+ index = driveList.size();
+ while(index--)
+ {
+ if(param == driveList[index])
+ {
+ driveList.erase(driveList.begin() + index);
+ return;
+ }
+ }
+}
+
+static BOOL DriveParam_IsValid(LPARAM param)
+{
+ if (param > 0x0000FFFF)
+ {
+ size_t index = driveList.size();
+ while(index--)
+ {
+ if(param == driveList[index])
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+DRIVE *Plugin_GetDriveFromNavItem(HNAVITEM hItem)
+{
+ NAVITEM item;
+
+ if (!hItem) return NULL;
+
+ item.cbSize = sizeof(NAVITEM);
+ item.mask = NIMF_PARAM;
+ item.hItem = hItem;
+
+ return (MLNavItem_GetInfo(plugin.hwndLibraryParent, &item) && DriveParam_IsValid(item.lParam)) ?
+ (DRIVE*)item.lParam : NULL;
+}
+
+HNAVITEM Plugin_GetNavItemFromLetter(CHAR cLetter)
+{
+ NAVCTRLFINDPARAMS fp = {0};
+ wchar_t invariant[32] = {0};
+
+ if (S_OK == StringCchPrintfW(invariant, sizeof(invariant)/sizeof(wchar_t), L"%s%c", NAVITEM_PREFIX, cLetter))
+ {
+ fp.cchLength = -1;
+ fp.pszName = invariant;
+ fp.compFlags = NICF_INVARIANT;
+
+ return MLNavCtrl_FindItemByName(plugin.hwndLibraryParent, &fp);
+ }
+ return NULL;
+}
+
+static BOOL CALLBACK EnumerateNavItemsCB(HNAVITEM hItem, LPARAM param)
+{
+ DRIVE *pDrive = Plugin_GetDriveFromNavItem(hItem);
+ return (pDrive) ? ((NAVITEMENUMPARAM*)param)->callback(hItem, pDrive, ((NAVITEMENUMPARAM*)param)->param) : TRUE;
+}
+
+BOOL Plugin_EnumerateNavItems(NAVITEMENUMPROC callback, LPARAM param)
+{
+ NAVITEMENUMPARAM pluginenum;
+ NAVCTRLENUMPARAMS navenum;
+ if (!callback) return FALSE;
+
+ pluginenum.callback = callback;
+ pluginenum.param = param;
+
+ navenum.hItemStart = hniMain;
+ navenum.lParam = (LPARAM)&pluginenum;
+ navenum.enumProc = EnumerateNavItemsCB;
+
+ return MLNavCtrl_EnumItems(plugin.hwndLibraryParent, &navenum);
+}
+
+void Plugin_RegisterListener(HWND hwnd, UINT uMsg, CHAR cLetter)
+{
+ activeListener.hwnd = hwnd;
+ activeListener.uMsg = uMsg;
+ activeListener.cLetter = cLetter;
+}
+
+void Plugin_UnregisterListener(HWND hwnd)
+{
+ ZeroMemory(&activeListener, sizeof(LISTENER));
+}
+
+static BOOL CALLBACK EnumNavItems_OnUIChangeCB(HNAVITEM hItem, DRIVE *pDrive, LPARAM param)
+{
+ if (pDrive) pDrive->textSize = 0;
+ return TRUE;
+}
+
+static void UpdatedNavStyles(void)
+{
+ g_navStyle = MLNavCtrl_GetStyle(plugin.hwndLibraryParent);
+ if (0 != g_view_metaconf->ReadInt(TEXT("showeject"), 1)) g_navStyle |= NCS_EX_SHOWEJECT;
+}
+
+static BOOL CALLBACK EnumerateNavItemsRemoveCB(HNAVITEM hItem, DRIVE *pDrive, LPARAM param)
+{
+ if(pDrive)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent,hItem);
+ Plugin_EnumerateNavItems(EnumerateNavItemsRemoveCB, 0);
+ }
+ return TRUE;
+}
+
+static BOOL Plugin_QueryOkToQuit()
+{
+ CHAR szLetters[24] = {0};
+ INT c = DriveManager_GetDriveList(szLetters, ARRAYSIZE(szLetters));
+ while(c-- > 0)
+ {
+ INT msgId;
+ if (cdrip_isextracting(szLetters[c])) msgId = IDS_YOU_ARE_CURRENTLY_RIPPING_AUDIO_CD_MUST_CANCEL_TO_CLOSE_WINAMP;
+ else if (MLDisc_IsDiscCopying(szLetters[c])) msgId = IDS_YOU_ARE_CURRENTLY_COPYING_DATA_CD_MUST_CANCEL_TO_CLOSE_WINAMP;
+ else msgId = 0;
+ if (msgId)
+ {
+ wchar_t buffer[512] = {0};
+ StringCchPrintfW(buffer, 512, WASABI_API_LNGSTRINGW(msgId), szLetters[c]);
+ MessageBoxW(plugin.hwndWinampParent, buffer, WASABI_API_LNGSTRINGW(IDS_NOTIFICATION),
+ MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TOPMOST);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+LRESULT CALLBACK WinampWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsgNavStyleUpdate == uMsg)
+ {
+ if(!wParam)
+ {
+ UpdatedNavStyles();
+ Plugin_EnumerateNavItems(EnumNavItems_OnUIChangeCB, 0);
+ }
+ else
+ {
+ Plugin_EnumerateNavItems(EnumerateNavItemsRemoveCB, 0);
+ ShowHideRipBurnParent();
+ DriveManager_Uninitialize(0);
+ DriveManager_Initialize(Invoke_OnDriveManagerNotify, TRUE);
+ Plugin_OnMLVisible((BOOL)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_IS_VISIBLE, 0));
+ }
+ }
+ else if (uMsgBurnerNotify == uMsg) // burner broadcast message LOWORD(wParam) = drive letter, lParam = (BOOL)bStarted. if bStarted = TRUE - burning started, otherwise burning finished
+ {
+ if (0 == HIWORD(wParam))
+ {
+ DriveManager_SetDriveMode((CHAR)LOWORD(wParam), (0 != lParam) ? DM_MODE_BURNING : DM_MODE_READY);
+ }
+ }
+ else if (uMsgRipperNotify == uMsg)
+ {
+ if (HIWORD(wParam)) // another instance of winamp quering
+ {
+ if (LOWORD(wParam) && cdrip_isextracting((CHAR)LOWORD(wParam))) SendNotifyMessage((HWND)lParam, uMsgRipperNotify, LOWORD(wParam), (LPARAM)TRUE);
+ else
+ {
+ CHAR cLetter;
+ cLetter = (CHAR)cdrip_isextracting(-1);
+ if (cLetter) SendNotifyMessage((HWND)lParam, uMsgRipperNotify, cLetter, (LPARAM)TRUE);
+ }
+ }
+ else
+ {
+ DriveManager_SetDriveMode((CHAR)LOWORD(wParam), (0 != lParam) ? DM_MODE_RIPPING : DM_MODE_READY);
+ }
+ }
+ else if (uMsgCopyNotify == uMsg)
+ {
+ if (HIWORD(wParam))
+ {
+ if (LOWORD(wParam) && MLDisc_IsDiscCopying((CHAR)LOWORD(wParam))) SendNotifyMessage((HWND)lParam, uMsgCopyNotify, LOWORD(wParam), (LPARAM)TRUE);
+ else
+ {
+ CHAR szLetters[24] = {0};
+ INT c = DriveManager_GetDriveList(szLetters, ARRAYSIZE(szLetters));
+ while(c-- > 0)
+ {
+ if (MLDisc_IsDiscCopying(szLetters[c]))
+ SendNotifyMessage((HWND)lParam, uMsgCopyNotify, szLetters[c], (LPARAM)TRUE);
+ }
+ }
+ }
+ else
+ {
+ DriveManager_SetDriveMode((CHAR)LOWORD(wParam), (0 != lParam) ? DM_MODE_COPYING : DM_MODE_READY);
+ }
+ }
+ else if (WM_WA_IPC == uMsg)
+ {
+ switch(lParam)
+ {
+ case IPC_SKIN_CHANGED:
+ case IPC_CB_RESETFONT:
+ UpdatedNavStyles();
+ Plugin_EnumerateNavItems(EnumNavItems_OnUIChangeCB, 0);
+ break;
+ case IPC_FILE_TAG_MAY_HAVE_UPDATED:
+ case IPC_FILE_TAG_MAY_HAVE_UPDATEDW:
+ if (activeListener.hwnd) SendMessageW(activeListener.hwnd, activeListener.uMsg, (WPARAM)lParam, (LPARAM)wParam);
+ break;
+ }
+ if(lParam == delay_ml_startup)
+ {
+ if(!wParam)
+ {
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 1, delay_ml_startup);
+ }
+ else if(wParam == 1)
+ {
+ // TODO: benski> temp-hack fix for now -- if (SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_IS_VISIBLE, 0))
+ {
+ DriveManager_Initialize(Invoke_OnDriveManagerNotify, TRUE);
+ MLDisc_InitializeCopyData();
+
+ DriveManager_Resume(TRUE);
+ SendNotifyMessage(HWND_BROADCAST, uMsgBurnerNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ SendNotifyMessage(HWND_BROADCAST, uMsgRipperNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ SendNotifyMessage(HWND_BROADCAST, uMsgCopyNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ }
+ }
+ }
+ }
+ return (oldWinampWndProc) ? CallWindowProcW(oldWinampWndProc, hwnd, uMsg, wParam, lParam) : DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+static DM_UNITINFO2_PARAM unitinfo;
+static char szDesc[1024];
+static DWORD szTypes[64];
+
+static void CALLBACK FreeParam(DM_NOTIFY_PARAM *phdr)
+{
+ if (!phdr) return;
+ switch(phdr->opCode)
+ {
+ case DMOP_TITLE:
+ if (((DM_TITLE_PARAM*)phdr)->pszTitle) free(((DM_TITLE_PARAM*)phdr)->pszTitle);
+ break;
+ }
+ free(phdr);
+}
+static void RegisterIcons()
+{
+ MLIMAGELISTCREATE mlilCreate;
+ MLIMAGESOURCE mlis;
+ MLIMAGELISTITEM mlilItem;
+
+ if (hmlilIcons) return;
+
+ mlilCreate.cx = ICON_SIZE_CX;
+ mlilCreate.cy = ICON_SIZE_CY;
+ mlilCreate.cInitial = 5;
+ mlilCreate.cGrow = 1;
+ mlilCreate.cCacheSize = 3;
+ mlilCreate.flags = MLILC_COLOR24;
+
+ hmlilIcons = MLImageList_Create(plugin.hwndLibraryParent, &mlilCreate);
+ if (!hmlilIcons) return;
+
+ ZeroMemory(&mlilItem, sizeof(MLIMAGELISTITEM));
+ mlilItem.cbSize = sizeof(MLIMAGELISTITEM);
+ mlilItem.hmlil = hmlilIcons;
+ mlilItem.filterUID = MLIF_FILTER1_UID;
+ mlilItem.pmlImgSource = &mlis;
+
+ ZeroMemory(&mlis, sizeof(MLIMAGESOURCE));
+ mlis.cbSize = sizeof(MLIMAGESOURCE);
+ mlis.type = SRC_TYPE_PNG;
+ mlis.hInst = plugin.hDllInstance;
+
+ mlis.lpszName = MAKEINTRESOURCEW(IDB_NAVITEM_CDROM);
+ MLImageList_Add(plugin.hwndLibraryParent, &mlilItem);
+
+ mlis.lpszName = MAKEINTRESOURCEW(IDB_EJECT_NORMAL);
+ MLImageList_Add(plugin.hwndLibraryParent, &mlilItem);
+
+ mlis.lpszName = MAKEINTRESOURCEW(IDB_EJECT_HILITED);
+ MLImageList_Add(plugin.hwndLibraryParent, &mlilItem);
+
+ mlis.lpszName = MAKEINTRESOURCEW(IDB_EJECT_PRESSED);
+ MLImageList_Add(plugin.hwndLibraryParent, &mlilItem);
+
+ mlis.lpszName = MAKEINTRESOURCEW(IDB_EJECT_DISABLED);
+ MLImageList_Add(plugin.hwndLibraryParent, &mlilItem);
+
+}
+
+
+static BOOL UpdateTitle(CHAR cLetter, LPCWSTR pszTitle)
+{
+ HNAVITEM hItem;
+ NAVITEM item;
+ DRIVE *pDrive;
+
+ hItem = Plugin_GetNavItemFromLetter(cLetter);
+ if (!hItem) return FALSE;
+
+ pDrive = Plugin_GetDriveFromNavItem(hItem);
+ if (!pDrive) return FALSE;
+
+ if (S_OK != StringCchCopyW(pDrive->szTitle, sizeof(pDrive->szTitle)/sizeof(wchar_t), (pszTitle) ? pszTitle : L"")) return FALSE;
+
+ pDrive->textSize = 0;
+
+ item.cbSize = sizeof(NAVITEM);
+ item.mask = NIMF_TEXT;
+ item.hItem = hItem;
+ item.pszText = pDrive->szTitle;
+
+ return MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+
+}
+static void QueryTitle(CHAR cLetter)
+{
+ DM_TITLE_PARAM *pdtp;
+
+ pdtp = (DM_TITLE_PARAM*)calloc(4, sizeof(DM_TITLE_PARAM));
+ if (pdtp)
+ {
+ pdtp->header.callback = (INT_PTR)Invoke_OnDriveManagerNotify;
+ pdtp->header.cLetter = cLetter;
+ pdtp->header.uMsg = NULL;
+ pdtp->header.fnFree = FreeParam;
+ pdtp->cchTitle = 256;
+ pdtp->pszTitle = (wchar_t*)calloc(pdtp->cchTitle, sizeof(wchar_t));
+
+ DriveManager_QueryTitle(pdtp);
+ }
+}
+static void Drive_OnAdded(CHAR cLetter)
+{
+ wchar_t szInvariant[32] = {0};
+
+ DRIVE *pDrive;
+ NAVINSERTSTRUCT nis = {0};
+ wchar_t szDriveType[32] = {0}, szDriveCap[64] = {0};
+
+ pDrive = (DRIVE*)calloc(1, sizeof(DRIVE));
+ if (!pDrive) return;
+
+ StringCchPrintfW(szInvariant, sizeof(szInvariant)/sizeof(wchar_t), L"%s%c", NAVITEM_PREFIX, cLetter);
+
+ pDrive->cLetter = cLetter;
+ pDrive->cMode = DriveManager_GetDriveMode(cLetter);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD, szDriveType, sizeof(szDriveType)/sizeof(wchar_t));
+ WASABI_API_LNGSTRINGW_BUF(IDS_DRIVE_CAP, szDriveCap, sizeof(szDriveCap)/sizeof(wchar_t));
+ StringCchPrintfW(pDrive->szTitle, sizeof(pDrive->szTitle)/sizeof(wchar_t), L"%s %s (%C:)", szDriveType, szDriveCap, (WCHAR)cLetter);
+
+ if (NULL == hmlilIcons) RegisterIcons();
+
+ ZeroMemory(&nis, sizeof(NAVINSERTSTRUCT));
+ nis.hParent = hniMain;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = pDrive->szTitle;
+ nis.item.pszInvariant = szInvariant;
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_PARAM | NIMF_STYLE;
+ nis.item.style = NIS_CUSTOMDRAW | NIS_WANTSETCURSOR | NIS_WANTHITTEST;
+ nis.item.styleMask = nis.item.style;
+ nis.item.lParam = (LPARAM)pDrive;
+
+ if (NULL != MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis))
+ {
+ DriveParam_RegisterDrive(pDrive);
+ }
+}
+
+static void Drive_OnChanged(CHAR cLetter)
+{
+ QueryTitle(cLetter);
+}
+
+static void Drive_OnRemoved(CHAR cLetter)
+{
+ HNAVITEM hItem;
+ hItem = Plugin_GetNavItemFromLetter(cLetter);
+ if (hItem) MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, hItem);
+ if (riphash && (0xFF & (riphash >> 24)) == (UCHAR)cLetter) riphash = 0;
+}
+
+static void Drive_OnModeChanged(CHAR cLetter, CHAR cMode)
+{
+ HNAVITEM hItem;
+ DRIVE *pDrive;
+
+ hItem = Plugin_GetNavItemFromLetter(cLetter);
+ if (!hItem) return;
+
+ pDrive = Plugin_GetDriveFromNavItem(hItem);
+ if (pDrive)
+ {
+ NAVITEMINAVLIDATE inv;
+
+ pDrive->cMode = cMode;
+
+ inv.fErase = FALSE;
+ inv.hItem = hItem;
+ inv.prc = NULL;
+ MLNavItem_Invalidate(plugin.hwndLibraryParent, &inv);
+ }
+}
+
+static void Medium_OnAdded(CHAR cLetter)
+{
+ if (riphash && (0xFF & (riphash >> 24)) == (UCHAR)cLetter) riphash = 0;
+ QueryTitle(cLetter);
+}
+
+static void Medium_OnRemoved(CHAR cLetter)
+{
+ if (riphash && (0xFF & (riphash >> 24)) == (UCHAR)cLetter) riphash = 0;
+ QueryTitle(cLetter);
+}
+
+static void OnDriveMangerOpCompleted(DM_NOTIFY_PARAM *phdr)
+{
+ switch(phdr->opCode)
+ {
+ case DMOP_TITLE:
+ if (S_OK == phdr->result) UpdateTitle(phdr->cLetter, ((DM_TITLE_PARAM*)phdr)->pszTitle);
+ break;
+ }
+}
+
+static void CALLBACK OnDriveManagerNotify(ULONG_PTR param)
+{
+ WORD code;
+ CHAR cLetter;
+
+ code = LOWORD(param);
+ cLetter = (CHAR)(0xFF & HIWORD(param));
+
+ switch(code)
+ {
+ case DMW_DRIVEADDED: Drive_OnAdded(cLetter); break;
+ case DMW_DRIVEREMOVED: Drive_OnRemoved(cLetter); break;
+ case DMW_DRIVECHANGED: Drive_OnChanged(cLetter); break;
+ case DMW_MEDIUMARRIVED: Medium_OnAdded(cLetter); break;
+ case DMW_MEDIUMREMOVED: Medium_OnRemoved(cLetter); break;
+ case DMW_MODECHANGED: Drive_OnModeChanged(cLetter, ((CHAR)(HIWORD(param)>>8))); break;
+
+ }
+ if (activeListener.hwnd && (0 == activeListener.cLetter || cLetter == activeListener.cLetter))
+ PostMessageW(activeListener.hwnd, activeListener.uMsg, (WPARAM)code, (LPARAM)HIWORD(param));
+}
+
+static void CALLBACK Invoke_OnDriveManagerNotify(WORD wCode, INT_PTR param)
+{
+ switch(wCode)
+ {
+ case DMW_DRIVEADDED:
+ case DMW_DRIVEREMOVED:
+ case DMW_DRIVECHANGED:
+ case DMW_MEDIUMARRIVED:
+ case DMW_MEDIUMREMOVED:
+ case DMW_MODECHANGED:
+ if (GetCurrentThreadId() != GetWindowThreadProcessId(plugin.hwndLibraryParent, NULL))
+ {
+ HANDLE htWA = (WASABI_API_APP) ? WASABI_API_APP->main_getMainThreadHandle() : NULL;
+ if (htWA)
+ {
+ QueueUserAPC(OnDriveManagerNotify, htWA, MAKELONG(wCode, (WORD)(param)));
+ CloseHandle(htWA);
+ }
+ }
+ else OnDriveManagerNotify(MAKELONG(wCode, (WORD)(param)));
+ break;
+ case DMW_OPCOMPLETED: OnDriveMangerOpCompleted((DM_NOTIFY_PARAM*)param); break;
+ }
+}
+
+void ShowHideRipBurnParent(void)
+{
+ BOOL bVal;
+ if (S_OK == Settings_GetBool(C_GLOBAL, GF_SHOWPARENT, &bVal) && bVal)
+ {
+ if(!hniMain)
+ {
+ NAVINSERTSTRUCT nis;
+ ZeroMemory(&nis, sizeof(NAVITEM));
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_RIP_AND_BURN, randb,64);
+ nis.item.pszInvariant = NAVITEM_PREFIX L"main";
+ nis.item.mask = NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT | NIMF_PARAM;
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.styleMask = nis.item.style;
+ nis.item.lParam = 0L;
+ hniMain = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+ }
+ else
+ {
+ if(hniMain)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent,hniMain);
+ }
+ hniMain = NULL;
+ }
+}
+
+int Init()
+{
+ QueryPerformanceFrequency(&freq);
+
+ // get the Application service
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(applicationApiServiceGuid);
+ if (sf) WASABI_API_APP = (api_application *)sf->getInterface();
+
+ // loader so that we can get the localisation service api for use
+ sf = plugin.service->service_getServiceByGuid(languageApiGUID);
+ if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface());
+
+ sf = plugin.service->service_getServiceByGuid(AnonymousStatsGUID);
+ if (sf) AGAVE_API_STATS = reinterpret_cast<api_stats*>(sf->getInterface());
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlDiscLangGUID);
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ static wchar_t szDescription[256];
+ StringCchPrintf(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_RIP_AND_BURN), VERSION_MAJOR, VERSION_MINOR);
+ plugin.description = (char*)szDescription;
+
+ // add to Winamp preferences
+ myPrefsItemCD.dlgID = IDD_PREFSCDRIPFR;
+ myPrefsItemCD.name = WASABI_API_LNGSTRINGW_BUF(IDS_CD_RIPPING,cdrip,64);
+ myPrefsItemCD.proc = (void*)CDRipPrefsProc;
+ myPrefsItemCD.hInst = WASABI_API_LNG_HINST;
+ myPrefsItemCD.where = 6; // media library
+ SENDWAIPC(plugin.hwndWinampParent, IPC_ADD_PREFS_DLGW, (WPARAM)&myPrefsItemCD);
+
+ wchar_t szIniFile[MAX_PATH],
+ *INI_DIR = (wchar_t*)SENDWAIPC(plugin.hwndWinampParent, IPC_GETINIDIRECTORYW, 0);
+
+ PathCombine(szIniFile, INI_DIR, TEXT("Plugins\\gen_ml.ini"));
+ g_config = new C_Config(szIniFile);
+
+ PathCombine(szIniFile, INI_DIR, TEXT("Plugins\\ml\\cdrom.vmd"));
+ g_view_metaconf = new C_Config(szIniFile);
+
+ g_context_menus = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+
+ oldWinampWndProc = (WNDPROC)(LONG_PTR)SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG)(LONG_PTR)WinampWndProc);
+
+ if (!uMsgBurnerNotify) uMsgBurnerNotify = RegisterWindowMessageA("WABURNER_BROADCAST_MSG");
+ if (!uMsgRipperNotify) uMsgRipperNotify = RegisterWindowMessageA("WARIPPER_BROADCAST_MSG");
+ if (!uMsgCopyNotify) uMsgCopyNotify = RegisterWindowMessageA("WACOPY_BROADCAST_MSG");
+ if (!uMsgNavStyleUpdate) uMsgNavStyleUpdate = RegisterWindowMessageW(L"ripburn_nav_update");
+
+ UpdatedNavStyles();
+ ShowHideRipBurnParent();
+
+ delay_ml_startup = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"ml_disc_delay", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, delay_ml_startup);
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ DriveManager_Uninitialize(4000); // allow to wait for 4 sec.
+ MLDisc_ReleaseCopyData();
+ delete(g_view_metaconf);
+ g_view_metaconf = 0;
+
+ delete(g_config);
+ g_config = NULL;
+
+ if (rgThread)
+ {
+ QueueUserAPC(QuitThread, rgThread, 0);
+ WaitForSingleObject(rgThread, INFINITE);
+ CloseHandle(rgThread);
+ rgThread = 0;
+ }
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(AnonymousStatsGUID);
+ if (sf) sf->releaseInterface(AGAVE_API_STATS);
+}
+
+int getFileInfo(const char *filename, const char *metadata, char *dest, int len)
+{
+ dest[0] = 0;
+ extendedFileInfoStruct efis = { filename, metadata, dest, len, };
+ return (int)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & efis, IPC_GET_EXTENDED_FILE_INFO); //will return 1 if wa2 supports this IPC call
+}
+
+int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len)
+{
+ if (dest && len)
+ dest[0] = 0;
+ extendedFileInfoStructW efis = { filename, metadata, dest, len, };
+ return (int)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_GET_EXTENDED_FILE_INFOW); //will return 1 if wa2 supports this IPC call
+}
+
+
+void Plugin_ShowRippingPreferences(void)
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&myPrefsItemCD, IPC_OPENPREFSTOPAGE);
+}
+BOOL Plugin_IsExtractScheduled(CHAR cLetter)
+{
+ BOOL result;
+ if (riphash && (0xFF & (riphash >> 24)) == (UCHAR)cLetter)
+ {
+ DWORD mediumSN;
+ char devname[] = "X:\\";
+ devname[0] = cLetter;
+ result = (GetVolumeInformationA(devname, NULL, 0, &mediumSN, NULL, NULL, NULL, 0) && (0x00FFFFFF & riphash) == mediumSN);
+ riphash = 0;
+ }
+ else result = FALSE;
+ return result;
+}
+
+
+static int Root_OnContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts)
+{
+ HMENU hMenu = GetSubMenu(g_context_menus, 7);
+ if (!hMenu) return 0;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, hMenu,
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ pt.x, pt.y, hHost, NULL);
+ switch (r)
+ {
+ case ID_NAVIGATION_PREFERENCES: Plugin_ShowRippingPreferences(); return 1;
+ case ID_NAVIGATION_HELP: SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8111574760468-CD-Ripping-with-Winamp"); return 1;
+ break;
+ }
+ return 0;
+}
+
+static int Plugin_OnContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts, CHAR cLetter)
+{
+ HMENU hMenu = GetSubMenu(g_context_menus, 2);
+ if (!hMenu) return 0;
+
+ MENUITEMINFO mii = { sizeof(MENUITEMINFO), };
+ mii.fMask = MIIM_STATE;
+ if (GetMenuItemInfo(hMenu, ID_CDROMMENU_EJECTCD, FALSE, &mii))
+ {
+ mii.fState &= ~(MFS_ENABLED | MFS_DISABLED);
+ mii.fState |= ((DM_MODE_READY == DriveManager_GetDriveMode(cLetter)) ? MFS_ENABLED : MFS_DISABLED);
+ SetMenuItemInfo(hMenu, ID_CDROMMENU_EJECTCD, FALSE, &mii);
+ }
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, hMenu,
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ pt.x, pt.y, hHost, NULL);
+ switch (r)
+ {
+ case ID_CDROMMENU_EXTRACT_CONFIGURE: Plugin_ShowRippingPreferences(); return 1;
+ case ID_CDROMMENU_EXTRACT_HELP: SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8111574760468-CD-Ripping-with-Winamp"); return 1;
+ case ID_CDROMMENU_PLAYALL:
+ case ID_CDROMMENU_ENQUEUEALL:
+ {
+ int enq = r == ID_CDROMMENU_ENQUEUEALL;
+ itemRecordList obj = {0, };
+ saveCDToItemRecordList(cLetter, &obj, NULL);
+ mlSendToWinampStruct p;
+ p.type = ML_TYPE_CDTRACKS;
+ p.enqueue = enq | 2;
+ p.data = &obj;
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SENDTOWINAMP, (WPARAM)&p);
+ freeRecordList(&obj);
+ }
+ break;
+ case ID_CDROMMENU_EXTRACT_EXTRACTALL:
+ riphash = 0;
+ if (hItem)
+ {
+ if (hItem == MLNavCtrl_GetSelection(plugin.hwndLibraryParent))
+ {
+ HWND hwnd = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0);
+ if (hwnd && SendMessageW(hwnd, WM_EXTRACTDISC, cLetter, 0)) break;
+ }
+
+ char devname[] = "X:\\";
+ devname[0] = cLetter;
+ if (!GetVolumeInformationA(devname, NULL, 0, &riphash, NULL, NULL, NULL, 0)) riphash = 0;
+ if (riphash) riphash = ((0x00FFFFFF & riphash) | (cLetter << 24));
+ MLNavItem_Select(plugin.hwndLibraryParent, hItem);
+ }
+ break;
+ case ID_CDROMMENU_EJECTCD:
+ {
+ CHAR cMode;
+ cMode = DriveManager_GetDriveMode(cLetter);
+ if (DM_MODE_READY != cMode)
+ {
+ wchar_t title[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW((DM_MODE_RIPPING == cMode) ? IDS_ERROR_CD_RIP_IN_PROGRESS : IDS_ERROR_CD_BURN_IN_PROGRESS),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD_EJECT,title,32), 0);
+ return FALSE;
+ }
+ else DriveManager_Eject(cLetter, DM_EJECT_REMOVE);
+ }
+ break;
+ }
+ Sleep(100);
+ MSG msg;
+ while (PeekMessageW(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ return 0;
+}
+
+static DWORD resumeTick = 0; // this is cheating
+static void Plugin_OnMLVisible(BOOL bVisible)
+{
+ if (bVisible)
+ {
+ DriveManager_Resume(TRUE);
+ resumeTick = GetTickCount();
+ SendNotifyMessage(HWND_BROADCAST, uMsgBurnerNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ SendNotifyMessage(HWND_BROADCAST, uMsgRipperNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ SendNotifyMessage(HWND_BROADCAST, uMsgCopyNotify, MAKEWPARAM(0, 0xffff), (LPARAM)plugin.hwndWinampParent);
+ return;
+ }
+ else DriveManager_Suspend();
+}
+
+static HWND Plugin_OnViewCreate(HNAVITEM hItem, HWND hwndParent)
+{
+ if (hItem == hniMain)
+ {
+ return WASABI_API_CREATEDIALOGW(IDD_VIEW_RIPBURN, hwndParent, view_ripburnDialogProc);
+ }
+ else
+ {
+ DRIVE *pDrive = Plugin_GetDriveFromNavItem(hItem);
+ if (pDrive) return CreateContainerWindow(hwndParent, pDrive->cLetter, ((GetTickCount() - resumeTick) > 100));
+ resumeTick = 0;
+ }
+ return NULL;
+}
+static BOOL Plugin_OnNavItemDelete(HNAVITEM hItem)
+{
+ DRIVE *pDrive = Plugin_GetDriveFromNavItem(hItem);
+ if (!pDrive) return FALSE;
+ DriveParam_UnregisterDrive(pDrive);
+ free(pDrive);
+ return TRUE;
+}
+
+static BOOL Plugin_OnNavItemClick(HNAVITEM hItem, UINT nAction, HWND hwndParent)
+{
+ return FALSE;
+}
+
+static INT Plugin_OnNavCustomDraw(HNAVITEM hItem, NAVITEMDRAW *pnicd, LPARAM lParam)
+{
+ static INT indent = 0;
+ DRIVE *pDrive;
+
+ if (FALSE == DriveParam_IsValid(lParam))
+ return FALSE;
+
+ pDrive = (DRIVE*)lParam;
+
+ if (0 == indent) indent = MLNavCtrl_GetIndent(plugin.hwndLibraryParent);
+ switch(pnicd->drawStage)
+ {
+ case NIDS_PREPAINT:
+ if (pnicd->prc->bottom > 0 && pnicd->prc->bottom > pnicd->prc->top)
+ {
+ HIMAGELIST himl;
+ INT realIndex, l, t, r;
+ MLIMAGELISTREALINDEX mlilRealIndex;
+
+ himl = MLImageList_GetRealList(plugin.hwndLibraryParent, hmlilIcons);
+
+ mlilRealIndex.cbSize = sizeof(MLIMAGELISTREALINDEX);
+ mlilRealIndex.hmlil = hmlilIcons;
+ mlilRealIndex.rgbBk = GetBkColor(pnicd->hdc);
+ mlilRealIndex.rgbFg = GetTextColor(pnicd->hdc);
+
+ t = pnicd->prc->top + (pnicd->prc->bottom - pnicd->prc->top - ICON_SIZE_CY)/2;
+ l = pnicd->prc->left + (indent*pnicd->iLevel) + 3;
+ r = pnicd->prc->right;
+
+ mlilRealIndex.mlilIndex = 0;
+ realIndex = ((NCS_SHOWICONS & g_navStyle) && himl && l < pnicd->prc->right) ?
+ MLImageList_GetRealIndex(plugin.hwndLibraryParent, &mlilRealIndex) : -1;
+ if (-1 != realIndex) // draw icon
+ {
+ if (ImageList_Draw(himl, realIndex, pnicd->hdc, l, t, ILD_NORMAL))
+ {
+ ExcludeClipRect(pnicd->hdc, l, t, l + ICON_SIZE_CX, t + ICON_SIZE_CY);
+ l += (ICON_SIZE_CX + 5);
+ }
+ }
+
+ pDrive->bEjectVisible = FALSE;
+ mlilRealIndex.mlilIndex = 1 + ((DM_MODE_READY == pDrive->cMode) ? pDrive->nBtnState : BTNSTATE_DISABLED);
+ realIndex = ((NCS_EX_SHOWEJECT & g_navStyle) && himl && (r - l) > (24 + 6 + ICON_SIZE_CX)) ?
+ MLImageList_GetRealIndex(plugin.hwndLibraryParent, &mlilRealIndex) : -1;
+ if (-1 != realIndex)
+ {
+ if (ImageList_Draw(himl, realIndex, pnicd->hdc, r - (ICON_SIZE_CX + 2), t, ILD_NORMAL))
+ {
+ r -= (ICON_SIZE_CX + 2);
+ ExcludeClipRect(pnicd->hdc, r, t, r + ICON_SIZE_CX, t + ICON_SIZE_CY);
+ r -= 4;
+ pDrive->bEjectVisible = TRUE;
+ }
+ }
+
+ if (*pDrive->szTitle && l < r)
+ {
+ RECT rt;
+ INT textH, textW;
+ COLORREF rgbOld(0), rgbBkOld(0);
+
+ if (!pDrive->textSize || (pDrive->textOrigWidth > r-l-3 && pDrive->itemWidth > (pnicd->prc->right - pnicd->prc->left)) ||
+ (LOWORD(pDrive->textSize) != pDrive->textOrigWidth && pDrive->itemWidth < (pnicd->prc->right - pnicd->prc->left)))
+ {
+ NAVITEM item;
+ item.cbSize = sizeof(NAVITEM);
+ item.mask = NIMF_TEXT;
+ item.hItem = hItem;
+ item.cchTextMax = sizeof(pDrive->szTitle)/sizeof(wchar_t);
+ item.pszText = pDrive->szTitle;
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &item);
+ {
+ if (pDrive->szTitle != item.pszText)
+ {
+ StringCchCopyW(pDrive->szTitle, sizeof(pDrive->szTitle)/sizeof(wchar_t), item.pszText);
+ }
+ if (!pDrive->textSize)
+ {
+ SetRect(&rt, 0, 0, 1, 1);
+ DrawTextW(pnicd->hdc, pDrive->szTitle, -1, &rt, DT_SINGLELINE | DT_CALCRECT);
+ pDrive->textOrigWidth = rt.right - rt.left;
+ }
+ SetRect(&rt, 0, 0, r - l - 3, 1);
+ textH = DrawTextW(pnicd->hdc, pDrive->szTitle, -1, &rt, DT_SINGLELINE|DT_CALCRECT|DT_END_ELLIPSIS|DT_MODIFYSTRING);
+ textW = rt.right - rt.left;
+ pDrive->textSize = (DWORD)MAKELONG(textW, textH);
+ }
+ }
+ else
+ {
+ textW = LOWORD(pDrive->textSize);
+ textH = HIWORD(pDrive->textSize);
+ }
+
+ if (0 == (NCS_FULLROWSELECT & g_navStyle) && ((NIS_SELECTED | NIS_DROPHILITED) & pnicd->itemState))
+ {
+ rgbOld = SetTextColor(pnicd->hdc, pnicd->clrText);
+ rgbBkOld = SetBkColor(pnicd->hdc, pnicd->clrTextBk);
+ }
+
+ if (r > (l + textW + 7)) r = l + textW + 7;
+
+ SetRect(&rt, l, pnicd->prc->top, r, pnicd->prc->bottom);
+
+ t = pnicd->prc->top + (pnicd->prc->bottom - pnicd->prc->top - textH)/2;
+ ExtTextOutW(pnicd->hdc, rt.left + 2, t, ETO_CLIPPED | ETO_OPAQUE, &rt, pDrive->szTitle, lstrlenW(pDrive->szTitle), 0);
+ if (0 == (NCS_FULLROWSELECT & g_navStyle) && (NIS_FOCUSED & pnicd->itemState) &&
+ 0 == (0x1 /*UISF_HIDEFOCUS*/ & (INT)SendMessageW(MLNavCtrl_GetHWND(plugin.hwndLibraryParent), 0x129 /*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ DrawFocusRect(pnicd->hdc, &rt);
+ }
+ ExcludeClipRect(pnicd->hdc, rt.left, rt.top, rt.right, rt.bottom);
+
+
+ if (0 == (NCS_FULLROWSELECT & g_navStyle) && ((NIS_SELECTED | NIS_DROPHILITED) & pnicd->itemState))
+ {
+ if (rgbOld != pnicd->clrText) SetTextColor(pnicd->hdc, rgbOld);
+ if (rgbBkOld != pnicd->clrTextBk) SetBkColor(pnicd->hdc, rgbBkOld);
+ }
+ }
+
+ pDrive->itemWidth = (WORD)(pnicd->prc->right - pnicd->prc->left);
+
+ if (NCS_FULLROWSELECT & g_navStyle)
+ {
+ ExtTextOutW(pnicd->hdc, 0, 0, ETO_OPAQUE, pnicd->prc, L"", 0, 0);
+ return NICDRF_SKIPDEFAULT;
+ }
+ else ExcludeClipRect(pnicd->hdc, pnicd->prc->left, pnicd->prc->top, pnicd->prc->right, pnicd->prc->bottom);
+ }
+ break;
+ case NIDS_POSTPAINT:
+ break;
+ }
+ return NICDRF_DODEFAULT;
+}
+
+static HNAVITEM hItemActive = NULL;
+
+static BOOL GetEjectBtnRect(HNAVITEM hItem, RECT *prc)
+{
+ NAVITEMGETRECT navRect;
+ navRect.fItem = FALSE;
+ navRect.hItem = hItem;
+
+ if (!hItem || !prc || !MLNavItem_GetRect(plugin.hwndLibraryParent, &navRect)) return FALSE;
+
+ navRect.rc.right -= 2;
+ navRect.rc.left = navRect.rc.right - ICON_SIZE_CX;
+ navRect.rc.top += (navRect.rc.bottom - navRect.rc.top - ICON_SIZE_CY)/2;
+ navRect.rc.bottom = navRect.rc.top + ICON_SIZE_CY;
+
+ CopyRect(prc, &navRect.rc);
+ return TRUE;
+}
+
+static INT_PTR Plugin_OnNavHitTest(HNAVITEM hItem, NAVHITTEST *pnavHitTest, LPARAM lParam)
+{
+ DRIVE *pDrive;
+
+ if (FALSE == DriveParam_IsValid(lParam))
+ return FALSE;
+
+ pDrive = (DRIVE*)lParam;
+
+ if ((NAVHT_ONITEMRIGHT | NAVHT_ONITEM) & pnavHitTest->flags)
+ {
+ RECT rb;
+
+ if (pDrive->bEjectVisible && GetEjectBtnRect(hItem, &rb) &&
+ pnavHitTest->pt.x >= rb.left && pnavHitTest->pt.x <= rb.right &&
+ pnavHitTest->pt.y >= rb.top && pnavHitTest->pt.y <= rb.bottom)
+ {
+ pnavHitTest->flags = NAVHT_NOWHERE;
+ pnavHitTest->hItem = NULL;
+ }
+ }
+ return 1;
+}
+
+static void CALLBACK NavButton_TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ DRIVE *pDrive;
+ POINT pt;
+ RECT rb;
+
+ pDrive = (hItemActive) ? Plugin_GetDriveFromNavItem(hItemActive) : NULL;
+ if (!pDrive || (BYTE)BTNSTATE_NORMAL == pDrive->nBtnState || !pDrive->bEjectVisible || !GetEjectBtnRect(hItemActive, &rb))
+ {
+ KillTimer(NULL, idEvent);
+ return;
+ }
+
+ GetCursorPos(&pt);
+ MapWindowPoints(HWND_DESKTOP, MLNavCtrl_GetHWND(plugin.hwndLibraryParent), &pt, 1);
+
+
+ if (pt.x < rb.left || pt.x > rb.right || pt.y < rb.top || pt.y > rb.bottom)
+ {
+ NAVITEMINAVLIDATE inv;
+
+ KillTimer(NULL, idEvent);
+
+ inv.fErase = FALSE;
+ inv.hItem = hItemActive;
+ inv.prc = &rb;
+
+ hItemActive = NULL;
+ pDrive->nBtnState = BTNSTATE_NORMAL;
+
+ MLNavItem_Invalidate(plugin.hwndLibraryParent, &inv);
+ }
+}
+
+static INT_PTR Plugin_OnNavSetCursor(HNAVITEM hItem, LPARAM lParam)
+{
+ POINT pt;
+ DRIVE *pDrive;
+ BYTE state;
+ RECT rb;
+
+ if (FALSE == DriveParam_IsValid(lParam))
+ return FALSE;
+
+ pDrive = (DRIVE*)lParam;
+
+ if (DM_MODE_READY != pDrive->cMode || !pDrive->bEjectVisible || !GetEjectBtnRect(hItem, &rb)) return -1;
+
+ GetCursorPos(&pt);
+
+ MapWindowPoints(HWND_DESKTOP, MLNavCtrl_GetHWND(plugin.hwndLibraryParent), &pt, 1);
+ if (pt.x >= rb.left && pt.x <= rb.right && pt.y >= rb.top && pt.y <= rb.bottom)
+ {
+ state = (BYTE)((0x8000 & GetAsyncKeyState( GetSystemMetrics(SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON)) ?
+ BTNSTATE_PRESSED : BTNSTATE_HILITED);
+ }
+ else state = BTNSTATE_NORMAL;
+
+ if (pDrive->nBtnState != state)
+ {
+
+ NAVITEMINAVLIDATE inv;
+
+ if ((BYTE)BTNSTATE_PRESSED == pDrive->nBtnState && BTNSTATE_HILITED == state)
+ {
+ DriveManager_Eject(pDrive->cLetter, DM_EJECT_CHANGE);
+ }
+
+ if (pDrive->timerId)
+ {
+ KillTimer(NULL, pDrive->timerId);
+ pDrive->timerId = 0;
+ }
+ if (hItemActive)
+ {
+ DRIVE *pDriveOld = Plugin_GetDriveFromNavItem(hItemActive);
+ if (pDriveOld)
+ {
+ RECT rb2;
+ if (pDriveOld->timerId)
+ {
+ KillTimer(NULL, pDriveOld->timerId);
+ pDriveOld->timerId = NULL;
+ }
+ if ((BYTE)BTNSTATE_NORMAL != pDriveOld->nBtnState && GetEjectBtnRect(hItemActive, &rb2))
+ {
+ pDriveOld->nBtnState = BTNSTATE_NORMAL;
+ inv.fErase = FALSE;
+ inv.hItem = hItemActive;
+ inv.prc = &rb2;
+ MLNavItem_Invalidate(plugin.hwndLibraryParent, &inv);
+ }
+ hItemActive = NULL;
+ }
+ }
+
+ if (BTNSTATE_NORMAL != state)
+ {
+ hItemActive = hItem;
+ pDrive->timerId = SetTimer(NULL, 0, NAVBUTTON_STATECHECK_DELAY, NavButton_TimerProc);
+ }
+
+ pDrive->nBtnState = state;
+
+ inv.fErase = FALSE;
+ inv.hItem = hItem;
+ inv.prc = &rb;
+ MLNavItem_Invalidate(plugin.hwndLibraryParent, &inv);
+
+ }
+
+
+ return -1;
+}
+
+static BOOL Plugin_OnConfig(void)
+{
+ SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&myPrefsItemCD, IPC_OPENPREFSTOPAGE);
+ return TRUE;
+}
+
+static INT_PTR pluginMessageProc(int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ HNAVITEM hItem;
+
+ if (msg >= ML_MSG_TREE_BEGIN && msg <= ML_MSG_TREE_END)
+ {
+ hItem = (msg < ML_MSG_NAVIGATION_FIRST) ? MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param1) : (HNAVITEM)param1;
+ if (!hItem) return 0;
+ } else hItem = NULL;
+
+ switch (msg)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW: return (INT_PTR)Plugin_OnViewCreate(hItem, (HWND)param2);
+ case ML_MSG_NAVIGATION_ONDELETE: return (INT_PTR)Plugin_OnNavItemDelete(hItem);
+ case ML_MSG_NAVIGATION_ONCUSTOMDRAW: return (INT_PTR)Plugin_OnNavCustomDraw(hItem, (NAVITEMDRAW*)param2, (LPARAM)param3);
+ case ML_MSG_NAVIGATION_ONHITTEST: return (INT_PTR)Plugin_OnNavHitTest(hItem, (NAVHITTEST*)param2, (LPARAM)param3);
+ case ML_MSG_NAVIGATION_ONSETCURSOR: return (INT_PTR)Plugin_OnNavSetCursor(hItem, (LPARAM)param3);
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ DRIVE *pDrive;
+ if (hniMain && (hItem == hniMain))
+ return Root_OnContextMenu(hItem, (HWND)param2, MAKEPOINTS(param3));
+
+ //Plugin Item
+ pDrive = Plugin_GetDriveFromNavItem(hItem);
+ if (pDrive)
+ return Plugin_OnContextMenu(hItem, (HWND)param2, MAKEPOINTS(param3), pDrive->cLetter);
+
+ return 0;
+ }
+ case ML_MSG_TREE_ONCLICK: return (INT_PTR)Plugin_OnNavItemClick(hItem, (UINT)param2, (HWND)param3);
+ case ML_MSG_CONFIG: return (INT_PTR)Plugin_OnConfig();
+ case ML_MSG_MLVISIBLE: Plugin_OnMLVisible((BOOL)param1); break;
+ case ML_MSG_NOTOKTOQUIT: if (!Plugin_QueryOkToQuit()) { return TRUE; } break;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_disc.dll)",
+ Init,
+ Quit,
+ pluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/main.h b/Src/Plugins/Library/ml_disc/main.h
new file mode 100644
index 00000000..285b8de6
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/main.h
@@ -0,0 +1,222 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+#include "./config.h"
+#include "./drivemngr.h"
+#include "./drive.h"
+#include "./medium.h"
+#include "api__ml_disc.h"
+#include "..\..\General\gen_ml/menu.h"
+
+#include "./copyfiles.h"
+#include "./copyinternal.h"
+
+#include <shlobj.h>
+#include <windows.h>
+#include <commctrl.h>
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#define OLD_AAC_CODEC mmioFOURCC('A','A','C',' ')
+
+#define BN_EX_GETTEXT 0x0FFF
+
+#define WM_EXTRACTDISC (WM_APP + 0x010)
+#define WM_SHOWFILEINFO (WM_APP + 0x11) // (wParam)(UINT)WISF_, (lParam)(LPCWSTR)file(track)name (can be NULL to reset)
+#define WM_QUERYFILEINFO (WM_APP + 0x12)
+#define WM_TAGUPDATED (WM_APP + 0x13) // wParam = 0, lParam = (LPCWSTR)pszFileName
+
+#define VCM_CREATECOMMANDBAR (WM_APP + 0x20)
+#define VCM_DESTROYCOMMANDBAR (WM_APP + 0x21)
+#define VCM_GETCOMMANDBAR (WM_APP + 0x22)
+#define VCM_GETMININFOENABLED (WM_APP + 0x23)
+#define VCM_GETMININFOVISIBLE (WM_APP + 0x24)
+
+
+typedef struct __CMDBARCREATESTRUCT
+{
+ HWND hwndOwner;
+ UINT resourceId;
+ DLGPROC fnDialogProc;
+ ULONG_PTR uData;
+} CMDBARCREATESTRUCT;
+
+#define MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#define ViewContainer_CreateCmdBar(/*HWND*/ __hwndViewContainer, /*HWND*/ __hwndOwner, /*INT_PTR*/ __resourceId, /*DLGPROC*/ __fnDialogProc, /*ULONG_PTR*/ __uData)\
+ {CMDBARCREATESTRUCT cs; cs.hwndOwner = (__hwndOwner); cs.resourceId = (__resourceId); cs.fnDialogProc = (__fnDialogProc); cs.uData = (__uData);\
+ ((HWND)SNDMSG((__hwndViewContainer), VCM_CREATECOMMANDBAR, 0, (LPARAM)(&cs)));}
+
+#define ViewContainer_DestroyCmdBar(/*HWND*/ __hwndViewContainer)\
+ ((BOOL)SNDMSG((__hwndViewContainer), VCM_DESTROYCOMMANDBAR, 0, 0L))
+
+#define ViewContainer_GetCmdBar(/*HWND*/ __hwndViewContainer)\
+ ((HWND)SNDMSG((__hwndViewContainer), VCM_GETCOMMANDBAR, 0, 0L))
+
+#define ViewContainer_GetMiniInfoEnabled(/*HWND*/ __hwndViewContainer)\
+ ((HWND)SNDMSG((__hwndViewContainer), VCM_GETMININFOENABLED, 0, 0L))
+
+#define ViewContainer_GetMiniInfoVisible(/*HWND*/ __hwndViewContainer)\
+ ((HWND)SNDMSG((__hwndViewContainer), VCM_GETMININFOVISIBLE, 0, 0L))
+
+
+extern winampMediaLibraryPlugin plugin;
+extern LARGE_INTEGER freq;
+
+void CleanupDirectoryString(LPTSTR pszDirectory);
+LPWSTR GetExtensionString(LPWSTR pszBuffer, INT cchBufferMax, DWORD fourcc);
+HRESULT FormatFileName(LPTSTR pszTextOut, INT cchTextMax, LPCTSTR pszFormat,
+ INT nTrackNo, LPCTSTR pszArtist,
+ LPCTSTR pszAlbum, LPCTSTR pszTitle,
+ LPCTSTR pszGenre, LPCTSTR pszYear,
+ LPCTSTR pszTrackArtist,
+ LPCTSTR pszFileName, LPCTSTR pszDisc);
+
+
+bool RegisteredEncoder(DWORD fourcc);
+
+extern C_Config *g_config;
+extern HMENU g_context_menus;
+extern C_Config *g_view_metaconf;
+
+#define DSF_CANRECORD 0x00010000
+
+#define DSF_PLAYING 0x00000001
+#define DSF_RIPPING 0x00000002
+#define DSF_BURNING 0x00000004
+#define DSF_GETTINGINFO 0x00000008
+
+typedef struct _DRIVE
+{
+ CHAR cLetter;
+ CHAR cMode;
+ WCHAR szTitle[64];
+ DWORD textSize;
+ BOOL textOrigWidth;
+ WORD itemWidth;
+ BYTE nBtnState;
+ BOOL bEjectVisible;
+ UINT_PTR timerId;
+} DRIVE;
+
+
+typedef BOOL (CALLBACK *NAVITEMENUMPROC)(HNAVITEM hItem, DRIVE *pDrive, LPARAM param);
+
+DRIVE *Plugin_GetDriveFromNavItem(HNAVITEM hItem);
+HNAVITEM Plugin_GetNavItemFromLetter(CHAR cLetter);
+BOOL Plugin_EnumerateNavItems(NAVITEMENUMPROC callback, LPARAM param);
+void Plugin_RegisterListener(HWND hwnd, UINT uMsg, CHAR cLetter); // active view can register itself to be notified about drive/medium changes if cLetter = 0 you will be notifed for all drives
+void Plugin_UnregisterListener(HWND hwnd);
+void Plugin_ShowRippingPreferences(void);
+BOOL Plugin_IsExtractScheduled(CHAR cLetter);
+
+int getFileInfo(const char *filename, const char *metadata, char *dest, int len);
+int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len);
+
+#define HF_DOMODAL 0x0001
+#define HF_ALLOWRESIZE 0x0010
+
+HWND MLDisc_ShowHelp(HWND hParent, LPCWSTR pszWindowTitle, LPCWSTR pszCaption, LPCWSTR pszText, UINT uFlags); // returns hwnd only if not HF_DOMODAL
+
+
+#define QBF_SHOW_CHECKBOX 0x00000001L
+#define QBF_SHOW_EXTRA_BUTTON 0x00000002L
+#define QBF_TOPMOST 0x00000100L
+#define QBF_SETFOREGROUND 0x00000200L
+#define QBF_BEEP 0x00000400L
+#define QBF_FLASH 0x00000800L
+#define QBF_DEFAULT_OK 0x00000000L
+#define QBF_DEFAULT_CANCEL 0x00001000L
+#define QBF_DEFAULT_EXTRA1 0x00002000L
+
+
+typedef struct _QUESTIONBOX
+{
+ HWND hParent; // [in]
+ LPCTSTR pszIcon; // [in]
+ UINT uBeepType; // [in]
+ LPCTSTR pszTitle; // [in] accepts MAKEINTRESOURCE() as parameters.
+ LPCTSTR pszMessage; // [in] accepts MAKEINTRESOURCE() as parameters.
+ UINT uFlags; // [in]
+ LPCTSTR pszBtnOkText; // [in] accepts MAKEINTRESOURCE() as parameters.
+ LPCTSTR pszBtnCancelText; // [in] accepts MAKEINTRESOURCE() as parameters.
+ LPCTSTR pszCheckboxText; // [in] accepts MAKEINTRESOURCE() as parameters.
+ LPCTSTR pszBtnExtraText; // [in] accepts MAKEINTRESOURCE() as parameters.
+ BOOL checkboxChecked; // [in][out]
+} QUESTIONBOX;
+
+INT_PTR MLDisc_ShowQuestionBox(QUESTIONBOX *pQuestionBox); // returns pressed button id;
+
+// cdrip.cpp
+BOOL CALLBACK CDRipPrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+typedef struct
+{
+ char drive_letter;
+
+ wchar_t *album;
+ wchar_t *artist;
+ wchar_t *genre;
+ wchar_t *year;
+ wchar_t *publisher; // record label
+ wchar_t *disc; // disc ##/##
+ wchar_t *comment; // notes from CDDB
+ wchar_t **composers;
+ wchar_t **conductors;
+ wchar_t **gracenoteFileIDs;
+ wchar_t **gracenoteExtData;
+ int total_length_bytes;
+
+ int ntracks; // total number of tracks
+ wchar_t **tracks; // set these to NULL to not rip em
+ wchar_t **trackArtists;
+
+ int *lengths; // lengths, in seconds
+
+ wchar_t **filenames; // can be used internally to override output filenames
+ // (should always allocate, but leave NULL ptrs in the array)
+ wchar_t **tempFilenames; //where we are ripping to, we'll move at the end
+} cdrip_params;
+
+void cdrip_extractFiles(cdrip_params *parms);
+
+int cdrip_isextracting(char drive);
+void cdrip_stop_all_extracts();
+
+//gracenote.cpp
+void gracenoteInit();
+int gracenoteQueryFile(const char *filename);
+void gracenoteCancelRequest();
+int gracenoteDoTimerStuff();
+void gracenoteSetValues(char *artist, char *album, char *title);
+char *gracenoteGetTuid();
+int gracenoteIsWorking();
+
+//view_ripburn.cpp
+INT_PTR CALLBACK view_ripburnDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+// view_cdrom.cpp
+void saveCDToItemRecordList(CHAR cLetter, itemRecordList *obj, char *album);
+int cdrom_contextMenu(HWND parent, CHAR cLetter, HNAVITEM hItem);
+void cdburn_appendItemRecord(itemRecordList *obj, char driveletter);
+
+HWND CreateContainerWindow(HWND hwndParent, CHAR cLetter, BOOL bQueryInfo);
+HWND CreateWaitWindow(HWND hwndParent, CHAR cLetter);
+HWND CreateInfoWindow(HWND hwndParent, CHAR cLetter);
+HWND CreateCDViewWindow(HWND hwndParent, DM_NOTIFY_PARAM *phdr);
+HWND CreateCDBurnWindow(HWND hwndParent, CHAR cLetter);
+HWND CreateCDRipWindow(HWND hwndParent, CHAR cLetter);
+HWND CreateCdDataViewWindow(HWND hwndParent, CHAR cLetter);
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/medium.cpp b/Src/Plugins/Library/ml_disc/medium.cpp
new file mode 100644
index 00000000..38bfd259
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/medium.cpp
@@ -0,0 +1,136 @@
+#include "./main.h"
+#include "./medium.h"
+#include "./drive.h"
+#include "./resource.h"
+//#include <primosdk.h>
+
+static int pType[] =
+{
+ IDS_STAMPED_DISC_OR_RECORDABLE_THAT_HAS_BEEN_RECORDED,
+ IDS_REWRITEABLE_DISC_HAS_DATA_BUT_KEPT_OPEN_FOR_APPEND,
+ IDS_REWRITEABLE_DISC_NOT_POSSIBLE_TO_APPEND_DATA,
+ IDS_BLANK_REWRITEABLE_DISC,
+};
+
+static int pFormat[] =
+{
+ IDS_MEDIA_BLANK_DISC,
+ IDS_MEDIA_DATA_MODE_1_DAO,
+ IDS_MEDIA_KODAK_PHOTO_CD,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_1_CLOSED,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_2_CLOSED,
+ IDS_MEDIA_DATA_MODE_2_DAO,
+ IDS_MEDIA_CDRFS,
+ IDS_MEDIA_PACKET_WRITING,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_1_OPEN,
+ IDS_MEDIA_DATA_MULTISESSION_MODE_2_OPEN,
+ IDS_MEDIA_AUDIO_DAO_SAO_TAO,
+ IDS_MEDIA_AUDIO_REWRITEABLE_DISC_WITH_SESSION_NOT_CLOSED,
+ IDS_MEDIA_FIRST_TYPE_OF_ENHANCED_CD_ABORTED,
+ IDS_MEDIA_CD_EXTRA,
+ IDS_MEDIA_AUDIO_TAO_WITH_SESSION_NOT_WRITTEN,
+ IDS_MEDIA_FIRST_TRACK_DATA_OTHERS_AUDIO,
+ IDS_MEDIA_MIXED_MODE_MADE_TAO,
+ IDS_MEDIA_KODAK_PORTFOLIO,
+ IDS_MEDIA_VIDEO_CD,
+ IDS_MEDIA_CDi,
+ IDS_MEDIA_PLAYSTATION_SONY_GAMES,
+ IDS_MEDIA_OBSOLETE,
+ IDS_MEDIA_OBSOLETE_FOR_RESTRICTED_OVERWRITE_DVD,
+ IDS_MEDIA_DVDROM_OR_CLOSED_RECORDABLE,
+ IDS_MEDIA_INCREMENTAL_DVD_WITH_APPENDABLE_ZONE,
+ IDS_MEDIA_APPENDABLE_DVD_OF_ANY_TYPE,
+ IDS_MEDIA_DVDRAM_CARTRIDGE,
+ IDS_MEDIA_CD_OTHER_TYPE,
+};
+
+static wchar_t buffer[256];
+
+LPCWSTR Medium_GetTypeString(DWORD nType)
+{
+ int index = -1;
+#if 0
+ switch(nType)
+ {
+ case PRIMOSDK_SILVER: index = 0; break;
+ case PRIMOSDK_COMPLIANTGOLD: index = 1; break;
+ case PRIMOSDK_OTHERGOLD: index = 2; break;
+ case PRIMOSDK_BLANK: index = 3; break;
+ }
+#endif
+ return WASABI_API_LNGSTRINGW_BUF((-1 != index) ? pType[index] : IDS_UNKNOWN, buffer,
+ sizeof(buffer)/sizeof(wchar_t));
+}
+
+LPCWSTR Medium_GetPhysicalTypeString(DWORD nType)
+{
+ return Drive_GetTypeString(nType);
+}
+
+LPCWSTR Medium_GetFormatString(DWORD nFormat)
+{
+ int index = -1;
+#if 0
+ switch(nFormat)
+ {
+ case PRIMOSDK_B1: index = 0; break;
+ case PRIMOSDK_D1: index = 1; break;
+ case PRIMOSDK_D2: index = 2; break;
+ case PRIMOSDK_D3: index = 3; break;
+ case PRIMOSDK_D4: index = 4; break;
+ case PRIMOSDK_D5: index = 5; break;
+ case PRIMOSDK_D6: index = 6; break;
+ case PRIMOSDK_D7: index = 7; break;
+ case PRIMOSDK_D8: index = 8; break;
+ case PRIMOSDK_D9: index = 9; break;
+ case PRIMOSDK_A1: index = 10; break;
+ case PRIMOSDK_A2: index = 11; break;
+ case PRIMOSDK_A3: index = 12; break;
+ case PRIMOSDK_A4: index = 13; break;
+ case PRIMOSDK_A5: index = 14; break;
+ case PRIMOSDK_M1: index = 15; break;
+ case PRIMOSDK_M2: index = 16; break;
+ case PRIMOSDK_M3: index = 17; break;
+ case PRIMOSDK_M4: index = 18; break;
+ case PRIMOSDK_M5: index = 19; break;
+ case PRIMOSDK_M6: index = 20; break;
+ case PRIMOSDK_F1: index = 21; break;
+ case PRIMOSDK_F2: index = 22; break;
+ case PRIMOSDK_F3: index = 23; break;
+ case PRIMOSDK_F4: index = 24; break;
+ case PRIMOSDK_F8: index = 25; break;
+ case PRIMOSDK_FA: index = 26; break;
+ case PRIMOSDK_GENERICCD: index = 27; break;
+ }
+#endif
+ return WASABI_API_LNGSTRINGW_BUF((-1 != index) ? pFormat[index] : IDS_UNKNOWN, buffer,
+ sizeof(buffer)/sizeof(wchar_t));
+}
+
+BOOL Medium_IsRecordableType(DWORD nType)
+{
+ #if 0
+ return (PRIMOSDK_COMPLIANTGOLD == nType || PRIMOSDK_BLANK == nType);
+#else
+ return FALSE;
+#endif
+}
+
+BOOL Medium_IsRecordable(CHAR cLetter)
+{
+ wchar_t info[128] = {0};
+ wchar_t name[] = L"cda://X.cda";
+ DWORD result;
+ BOOL reloaded = FALSE;
+
+ name[6] = cLetter;
+
+ for(;;)
+ {
+ result = getFileInfoW(name, L"cdtype", info, sizeof(info)/sizeof(wchar_t));
+ if (result || reloaded || !getFileInfoW(name, L"reloadsonic", NULL, 0)) break;
+ reloaded = TRUE;
+ }
+
+ return (result) ? (!lstrcmpW(info, L"CDR") || !lstrcmpW(info, L"CDRW")) : FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/medium.h b/Src/Plugins/Library/ml_disc/medium.h
new file mode 100644
index 00000000..fc0f5764
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/medium.h
@@ -0,0 +1,17 @@
+#ifndef NULLSOFT_MLDISC_MEDIUM_HEADER
+#define NULLSOFT_MLDISC_MEDIUM_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+LPCWSTR Medium_GetTypeString(DWORD nType);
+LPCWSTR Medium_GetPhysicalTypeString(DWORD nType);
+LPCWSTR Medium_GetFormatString(DWORD nFormat);
+BOOL Medium_IsRecordableType(DWORD nType);
+BOOL Medium_IsRecordable(CHAR cLetter);
+
+
+#endif // NULLSOFT_MLDISC_MEDIUM_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/menu.cpp b/Src/Plugins/Library/ml_disc/menu.cpp
new file mode 100644
index 00000000..e01503f4
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/menu.cpp
@@ -0,0 +1,25 @@
+#include "main.h"
+#include "./menu.h"
+#include "./resource.h"
+#include "../gen_ml/ml_ipc_0313.h"
+
+INT Menu_TrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ MLSKINNEDPOPUP popup;
+ ZeroMemory(&popup, sizeof(MLSKINNEDPOPUP));
+ popup.cbSize = sizeof(MLSKINNEDPOPUP);
+ popup.hmenu = hMenu;
+ popup.fuFlags = fuFlags;
+ popup.x = x;
+ popup.y = y;
+ popup.hwnd = hwnd;
+ popup.lptpm = lptpm;
+ popup.skinStyle = SMS_USESKINFONT/*SMS_SYSCOLORS*/;
+ popup.customProc = NULL;
+ popup.customParam = 0;
+
+ return (INT)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_TRACKSKINNEDPOPUPEX, &popup);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/menu.h b/Src/Plugins/Library/ml_disc/menu.h
new file mode 100644
index 00000000..d776685b
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/menu.h
@@ -0,0 +1,13 @@
+#ifndef NULLOSFT_MLDISC_PLUGIN_MENU_HEADER
+#define NULLOSFT_MLDISC_PLUGIN_MENU_HEADER
+
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+INT Menu_TrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm);
+
+#endif //NULLOSFT_MLDISC_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/ml_disc.rc b/Src/Plugins/Library/ml_disc/ml_disc.rc
new file mode 100644
index 00000000..a1dc5bc4
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/ml_disc.rc
@@ -0,0 +1,939 @@
+// 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_VIEW_WAIT DIALOGEX 0, 0, 291, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "Show Info",IDC_BTN_SHOWINFO,"Button",BS_OWNERDRAW | WS_TABSTOP,251,231,40,11
+ CTEXT "",IDC_LBL_TEXT,0,19,290,209,SS_CENTERIMAGE
+END
+
+IDD_PREFSCDRIPFR DIALOGEX 0, 0, 272, 246
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Tab1",IDC_TAB1,"SysTabControl32",WS_TABSTOP,0,0,271,246
+END
+
+IDD_PREFS_CDRIP1 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Output Filenames",IDC_STATIC,4,3,256,108
+ LTEXT "Specify the destination folder for ripped tracks:",IDC_STATIC,10,15,204,8
+ EDITTEXT IDC_DESTPATH,10,26,198,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BUTTON1,208,26,44,13
+ LTEXT "Specify the naming convention for ripped tracks:",IDC_STATIC,10,44,203,8
+ EDITTEXT IDC_FILENAMEFMT,10,55,198,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Format Help",IDC_BUTTON2,208,55,44,13
+ CONTROL "Use uppercase file extensions (i.e. MP3 instead of mp3)",IDC_UPPERCASEEXT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,73,192,10
+ LTEXT "",IDC_FMTOUT,10,87,244,18
+ GROUPBOX "Tagging Settings",IDC_STATIC,4,115,256,110
+ CONTROL "Automatically add ripped files to Library database",IDC_CHECK_ML,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,128,193,10
+ CONTROL "Automatically add tags with metadata to ripped files",IDC_TAGFILES,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,141,183,10
+ CONTROL "Automatically calculate replay gain",IDC_AUTO_RG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,154,126,10
+ CONTROL "Write track numbers as track/total (e.g. 12/15)",IDC_TOTAL_TRACKS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,167,167,10
+ LTEXT "When tagging ripped CDs, make track numbers start with:",IDC_STATIC,10,181,186,8
+ EDITTEXT IDC_EDIT2,198,179,25,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "Add the following to the comment field of ripped files:",IDC_STATIC,10,195,174,8
+ EDITTEXT IDC_EDIT1,10,206,243,13,ES_AUTOHSCROLL
+END
+
+IDD_PREFS_CDRIP2 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Encoding Format",IDC_STATIC,4,3,256,44
+ LTEXT "Select the format you wish to encode to:",IDC_STATIC,12,15,240,11
+ COMBOBOX IDC_ENCFORMAT,20,27,233,116,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_ENC_CONFIG,"Static",SS_BLACKRECT | NOT WS_VISIBLE,4,51,255,167
+END
+
+IDD_VIEW_CDROM_EXTRACT DIALOGEX 0, 0, 269, 156
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU
+CAPTION "Ripping from CD..."
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Current Track",IDC_CTFRAME,4,7,261,70
+ EDITTEXT IDC_CURTRACK,16,18,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER | NOT WS_TABSTOP
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,16,31,240,18
+ CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,16,52,240,14
+ GROUPBOX "All Tracks",IDC_STATIC,4,72,261,62
+ LTEXT "",IDC_STATUS2,16,88,240,22
+ CONTROL "Progress1",IDC_PROGRESS2,"msctls_progress32",WS_BORDER,16,114,240,14
+ PUSHBUTTON "Hide Window",IDCANCEL,76,139,57,13
+ PUSHBUTTON "Cancel",IDC_BUTTON1,137,139,57,13
+END
+
+IDD_PREFS_CDRIP4 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Playlist Creation Settings",IDC_STATIC,4,3,256,152
+ LTEXT "When Winamp completes ripping an entire CD, it can automatically generate a playlist of the ripped tracks.",IDC_STATIC,10,15,243,18
+ LTEXT "Create the following playlists:",IDC_STATIC,10,37,230,10
+ CONTROL "Library playlist",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,49,80,10
+ CONTROL ".M3U playlist",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,62,57,10
+ CONTROL "Use extended M3U playlist format",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,29,74,121,10
+ CONTROL ".PLS playlist",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,86,53,10
+ LTEXT "Specify the naming convention for M3U and PLS playlists:",IDC_STATIC,10,102,183,8
+ EDITTEXT IDC_FILENAMEFMT,10,114,196,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Format Help",IDC_BUTTON2,207,114,45,13
+ LTEXT "",IDC_FMTOUT,13,131,242,18
+END
+
+IDD_VIEW_CDROM_EX2 DIALOGEX 0, 0, 291, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "",IDC_CDINFO,1,0,288,18,SS_NOPREFIX
+ CONTROL "List4",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_TABSTOP,0,19,290,209
+ CONTROL "Rip Options",IDC_RIPOPTS,"Button",BS_OWNERDRAW | WS_TABSTOP,0,231,48,11
+ CONTROL "Cancel Rip",IDC_CANCEL_RIP,"Button",BS_OWNERDRAW | WS_TABSTOP,52,231,44,11
+ CONTROL "Show Info",IDC_BTN_SHOWINFO,"Button",BS_OWNERDRAW | WS_TABSTOP,251,231,40,11
+END
+
+IDD_VIEW_CDROM_BURN DIALOGEX 0, 0, 291, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "",IDC_CDINFO,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | WS_GROUP,1,0,255,17
+ CONTROL 1101,IDC_LOGO,"Static",SS_BITMAP | SS_NOTIFY | SS_REALSIZEIMAGE,260,2,31,15
+ CONTROL "List4",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,19,290,209
+ CONTROL "Add...",IDC_ADD,"Button",BS_OWNERDRAW | WS_TABSTOP,0,231,40,11
+ CONTROL "Burn Options",IDC_BURN_OPTS,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,0,231,46,11
+ CONTROL "Clear",IDC_CLEAR,"Button",BS_OWNERDRAW | WS_TABSTOP,42,231,40,11
+ CONTROL "Cancel Burn",IDC_CANCEL_BURN,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,51,231,46,11
+ CONTROL "Burn",IDC_BURN,"Button",BS_OWNERDRAW | WS_TABSTOP,84,231,40,11
+ CONTROL "Show Info",IDC_BTN_SHOWINFO,"Button",BS_OWNERDRAW | WS_TABSTOP,251,231,40,11
+END
+
+IDD_BURN_ADD_STATUS DIALOGEX 0, 0, 160, 42
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "CD Burner"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Adding tracks to burner...",IDC_STAT,7,7,146,8
+ CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,7,21,146,14
+END
+
+IDD_BURN DIALOGEX 0, 0, 251, 143
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Dialog"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Burn speed:",IDC_STATIC,7,9,39,8
+ COMBOBOX IDC_COMBO1,50,7,71,154,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Want to burn at your computer's maximum speed?",IDC_PRO1,7,23,168,8
+ CONTROL "Click here",IDC_PRO2,"Button",BS_OWNERDRAW | WS_TABSTOP,176,23,35,10
+ CONTROL "Enable Burn-proof mode (when available)",IDC_CHECK2,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,35,145,10
+ LTEXT "Burn-proof mode is supported on newer CD burners and helps ensure that the burn does not fail. The downside of burn-proof mode is higher memory use. Burn-proof mode is highly recommended.",IDC_STATIC,20,47,224,26
+ CONTROL "Test mode (do not actually write to media)",IDC_CHECK1,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,77,147,10
+ LTEXT "Test mode lets you go through the burning process without actually writing to the physical CD. This can be useful to test system performance without risking making unusable media.",IDC_STATIC,20,91,222,26
+ DEFPUSHBUTTON "Burn",IDOK,71,123,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,124,123,50,13
+END
+
+IDD_WAITFORCDR DIALOGEX 0, 0, 186, 63
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Waiting for blank CD..."
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "",IDC_TEXT,7,7,172,8
+ PUSHBUTTON "Cancel",IDCANCEL,64,42,50,14
+END
+
+/* IDD_UPSELL_RIPPING DIALOGEX 0, 0, 223, 93
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Rip CDs faster in MP3"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "With Winamp Pro you can:\n\t- Rip in MP3\n\t- Burn and Rip Faster\n\t- Support Winamp",IDC_STATIC,7,7,209,33
+ LTEXT "The free version of Winamp will only rip CDs at 8x speed in AAC.",IDC_STATIC,7,43,209,10
+ CONTROL "Never show me this again",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,57,97,10
+ PUSHBUTTON "More Info",IDC_BUTTON1,7,73,60,13
+ PUSHBUTTON "Rip at 8x in AAC",IDOK,70,73,60,13
+ PUSHBUTTON "Cancel Rip",IDCANCEL,166,73,50,13
+END */
+
+IDD_NOBURN DIALOGEX 0, 0, 300, 58
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Cannot burn track"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Skip File",IDOK,192,41,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,246,41,50,13
+ EDITTEXT IDC_MESSAGE2,4,4,292,32,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+END
+
+IDD_VIEW_RIPBURN DIALOGEX 0, 0, 294, 236
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CTEXT "Available drives:",IDC_LBL_DRIVES,0,2,66,12,SS_CENTERIMAGE
+ LTEXT "Drive Info:",IDC_LBL_INFO_DRIVE,70,2,222,69
+ LISTBOX IDC_LIST_DRIVES,0,18,66,205,LBS_SORT | LBS_OWNERDRAWVARIABLE | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER | WS_TABSTOP
+ LTEXT "Drive Letter:",IDC_LBL_DRIVE_LETTER,80,18,58,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_DRIVE_LETTER_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,18,150,8
+ LTEXT "Description:",IDC_LBL_DRIVE_DESCRIPTION,80,28,58,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_DRIVE_DESCRIPTION_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,28,150,8
+ LTEXT "Bus type:",IDC_LBL_DRIVE_BUS,80,38,58,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_DRIVE_BUS_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,38,150,8
+ LTEXT "Supported Types:",IDC_LBL_DRIVE_TYPES,80,48,58,8,NOT WS_VISIBLE
+ LTEXT "",IDC_LBL_DRIVE_TYPES_VAL,140,48,150,16,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ LTEXT "Medium Info:",IDC_LBL_INFO_MEDIUM,70,77,222,146
+// LTEXT "Medium information is not available due to the Sonic Burning engine not being installed.\n\nPlease install the Sonic Burning engine if you want to see medium information.",IDC_LBL_MEDIUM_UPDATE,110,112,150,51
+ LTEXT "This feature is not available in this Winamp build.\n\nDrive info and CD Burning will return for the next release.",IDC_LBL_MEDIUM_UPDATE,110,112,150,51
+ LTEXT "Type:",IDC_LBL_MEDIUM_TYPE,80,91,50,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_MEDIUM_DISC_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,91,150,8
+ LTEXT "Format:",IDC_LBL_MEDIUM_FORMAT,80,101,50,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_MEDIUM_FORMAT_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,101,150,8
+ LTEXT "Tracks #:",IDC_LBL_MEDIUM_TRACKN,80,111,50,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_MEDIUM_TRACKN_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,111,150,8
+ LTEXT "Capacity:",IDC_LBL_MEDIUM_CAPACITY,80,121,50,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_MEDIUM_CAPACITY_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,121,150,8
+ LTEXT "Recordable:",IDC_LBL_MEDIUM_RECORDABLE,80,135,50,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_MEDIUM_RECORDABLE_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,135,150,8
+ LTEXT "Erasable:",IDC_LBL_MEDIUM_ERASEABLE,80,145,50,8,NOT WS_VISIBLE
+ CONTROL "",IDC_LBL_MEDIUM_ERASEABLE_VAL,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | NOT WS_VISIBLE | WS_GROUP,140,145,150,8
+ LTEXT "Additional Info:",IDC_LBL_MEDIUM_ADDINFO,80,159,50,8,NOT WS_VISIBLE
+ LTEXT "",IDC_LBL_MEDIUM_ADDINFO_VAL,140,159,149,8,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "Refresh List",IDC_BTN_REFRESH,"Button",BS_OWNERDRAW | WS_TABSTOP,0,225,67,11
+END
+
+IDD_VIEW_CONTAINER DIALOGEX 0, 0, 276, 166
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+END
+
+IDD_VIEW_CDROM DIALOGEX 0, 0, 291, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "",IDC_CDINFO,1,0,173,18,SS_NOPREFIX
+ LTEXT "",IDC_CDINFO2,174,0,116,18
+ CONTROL "List4",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_TABSTOP,0,19,290,209
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,231,35,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,39,231,38,11
+ CONTROL "Rip",IDC_BUTTON_EXTRACT,"Button",BS_OWNERDRAW | WS_TABSTOP,81,231,32,11
+ CONTROL "Eject CD",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,117,231,42,11
+ CONTROL "Show Info",IDC_BTN_SHOWINFO,"Button",BS_OWNERDRAW | WS_TABSTOP,251,231,40,11
+END
+
+IDD_VIEW_INFO DIALOGEX 0, 0, 291, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "Show Info",IDC_BTN_SHOWINFO,"Button",BS_OWNERDRAW | WS_TABSTOP,251,231,40,11
+ CTEXT "\n\n\nAnother instance of Winamp has locked this drive.\nYou will need to wait or cancel the operation.",IDC_LBL_TEXT,0,19,290,209
+END
+
+IDD_VIEW_CDROM_DATA DIALOGEX 0, 0, 478, 474
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+END
+
+IDD_COMMANDBAR_DATA DIALOGEX 0, 0, 291, 14
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "",IDC_BTN_PLAYEX,0,0,75,14,WS_GROUP
+ PUSHBUTTON "Copy...",IDC_BTN_COPY,83,0,50,14
+ PUSHBUTTON "",IDC_BTN_EJECT,265,0,24,14
+ CONTROL "",IDC_LBL_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_CENTERIMAGE | SS_WORDELLIPSIS | WS_GROUP,137,0,108,14
+END
+
+IDD_FILECOPY_PREPARE DIALOGEX 0, 0, 279, 206
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Copy Files"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ PUSHBUTTON "",IDC_BTN_OPTIONS,4,43,59,14
+ DEFPUSHBUTTON "OK",IDOK,169,43,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,225,43,50,14
+ EDITTEXT IDC_EDT_PATH,10,83,259,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BTN_BROWSE,219,98,50,14
+ CONTROL "Add files to Library database",IDC_CHK_ADDTOMLDB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,123,259,10
+ CONTROL "Use custom naming",IDC_CHK_CUSTOMNAME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,135,259,10
+ EDITTEXT IDC_EDT_NAMEFORMAT,20,147,249,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Help",IDC_BTN_HELP,219,179,50,14
+ LTEXT "",IDC_LBL_MESSAGE,68,6,207,28
+ CONTROL "Destination folder:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,10,73,259,8
+ GROUPBOX "",IDC_GRP_OPTIONS,4,65,271,135
+ CONTROL "",IDC_PIC_LOGO,"Static",SS_BITMAP | SS_REALSIZEIMAGE,10,6,15,13
+ CONTROL "",IDC_LBL_EXAMPLE,"Static",SS_LEFTNOWORDWRAP | SS_ENDELLIPSIS | WS_GROUP,56,162,211,8
+ CONTROL "Example:",IDC_LBL_EXAMPLE_TITLE,"Static",SS_SIMPLE | WS_GROUP,22,162,30,8
+ CONTROL "Free:",IDC_LBL_FREE_TITLE,"Static",SS_SIMPLE | WS_GROUP,12,97,18,8
+ CONTROL "Required:",IDC_LBL_REQUIRED_TITLE,"Static",SS_SIMPLE | WS_GROUP,12,106,32,8
+ LTEXT "",IDC_LBL_FREE,49,97,113,8
+ LTEXT "",IDC_LBL_REQUIRED,49,106,113,8
+END
+
+IDD_FILECOPY_PROGRESS DIALOGEX 0, 0, 279, 63
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+CAPTION "Copy Files"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ PUSHBUTTON "Cancel",IDCANCEL,225,43,50,14
+ CONTROL "",IDC_PIC_LOGO,"Static",SS_BITMAP | SS_REALSIZEIMAGE,10,6,15,13
+ LTEXT "",IDC_LBL_TASK,68,6,207,9
+ CONTROL "",IDC_PRG_TOTAL,"msctls_progress32",WS_BORDER,68,27,207,10
+ LTEXT "",IDC_LBL_OPERATION,68,16,207,9
+END
+
+IDD_SIMPLEHELP DIALOGEX 0, 0, 148, 123
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_NOFAILCREATE | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+CAPTION "Dialog"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Close",IDCANCEL,90,102,50,14
+ EDITTEXT IDC_EDT_TEXT,7,39,133,50,ES_MULTILINE | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER | WS_VSCROLL,WS_EX_STATICEDGE
+ LTEXT "",IDC_LBL_CAPTION,7,7,133,24
+END
+
+IDD_FILECOPY_QUESTION DIALOGEX 0, 0, 248, 66
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,136,46,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,192,46,50,14
+ LTEXT "Label",IDC_LBL_MESSAGE,53,6,189,23
+ CONTROL "",IDC_CHECKBOX1,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,6,35,236,10
+ PUSHBUTTON "Extra1",IDC_BTN_EXTRA1,80,46,50,14,NOT WS_VISIBLE
+ ICON 32514,IDC_PIC_ICON,6,6,21,20,SS_REALSIZEIMAGE
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_LISTITEM_CDDRIVE BITMAP "resources\\cdrom_32x32_24.bmp"
+IDB_LISTBOX_BACK BITMAP "resources\\listbox_back_2x68x24.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "ExtractMenu"
+ BEGIN
+ MENUITEM "CD ripping preferences...", 40001
+ MENUITEM SEPARATOR
+ MENUITEM "Rip all tracks", 40002
+ MENUITEM "Rip selected tracks", 40003
+ END
+ POPUP "CdromMenu"
+ BEGIN
+ MENUITEM "Play selection\tEnter", 40004
+ MENUITEM "Enqueue selection\tShift+Enter", 40005
+ MENUITEM SEPARATOR
+ MENUITEM "Select All\tCtrl+A", ID_CDROMMENU_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Play all tracks", 40006
+ MENUITEM "Enqueue all tracks", 40007
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &CD Info...\tAlt+3", 40008
+ POPUP "Rip"
+ BEGIN
+ MENUITEM "Rip selected track(s)", 40009
+ MENUITEM "Rip all tracks", 40010
+ MENUITEM SEPARATOR
+ MENUITEM "CD ripping preferences...", 40011
+ END
+ END
+ POPUP "CdromMenu2"
+ BEGIN
+ MENUITEM "Play audio CD", 40006
+ MENUITEM "Enqueue audio CD", 40007
+ MENUITEM SEPARATOR
+ MENUITEM "Rip audio CD", 40010
+ MENUITEM "CD ripping preferences...", 40011
+ MENUITEM SEPARATOR
+ MENUITEM "Eject CD", 40012
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_CDROMMENU_EXTRACT_HELP
+ END
+ POPUP "BurnAddMenu"
+ BEGIN
+ MENUITEM "Files...", 40013
+ MENUITEM "Folder...", 40014
+ MENUITEM SEPARATOR
+ MENUITEM "Current playlist", 40015
+ END
+ POPUP "BurnContextMenu"
+ BEGIN
+ MENUITEM "Play selection\tEnter", 40016
+ MENUITEM "Enqueue selection\tShift+Enter", 40017
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", ID_BURNCONTEXTMENU_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Move selected items up\tAlt+Up", 40018
+ MENUITEM "Move selected items down\tAlt+Down", 40019
+ MENUITEM SEPARATOR
+ MENUITEM "Remove selected items\tDel", 40020
+ MENUITEM SEPARATOR
+ MENUITEM "Burn...", 40021
+ END
+ POPUP "RipOptions"
+ BEGIN
+ POPUP "Priority"
+ BEGIN
+ MENUITEM "Highest", 40022
+ MENUITEM "Above normal", 40023
+ MENUITEM "Normal", 40024
+ MENUITEM "Below normal", 40025
+ MENUITEM "Lowest", 40026
+ MENUITEM "Idle", 40027
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Ripping Status Window", 40028
+ MENUITEM SEPARATOR
+ MENUITEM "&Eject CD when complete", 40029
+ MENUITEM "&Play tracks when complete", 40030
+ MENUITEM "&Close view when complete", 40031
+ END
+ POPUP "BurnOptions"
+ BEGIN
+ MENUITEM "&Burning Status Window", 40028
+ MENUITEM SEPARATOR
+ MENUITEM "&Eject CD when complete", 40029
+ MENUITEM "&Close view when complete", 40031
+ MENUITEM "&Add CD titles to local DB when complete", 40032
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_VIEW_CDROM_EXTRACT, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 265
+ BOTTOMMARGIN, 152
+ END
+
+ IDD_NOBURN, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 296
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 54
+ END
+
+ IDD_VIEW_CONTAINER, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 269
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 159
+ END
+
+ IDD_VIEW_CDROM_DATA, DIALOG
+ BEGIN
+ RIGHTMARGIN, 477
+ BOTTOMMARGIN, 472
+ END
+
+ IDD_FILECOPY_PREPARE, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 275
+ VERTGUIDE, 10
+ VERTGUIDE, 20
+ VERTGUIDE, 49
+ VERTGUIDE, 68
+ VERTGUIDE, 269
+ TOPMARGIN, 6
+ BOTTOMMARGIN, 200
+ HORZGUIDE, 57
+ HORZGUIDE, 97
+ HORZGUIDE, 106
+ END
+
+ IDD_FILECOPY_PROGRESS, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 275
+ VERTGUIDE, 68
+ TOPMARGIN, 6
+ BOTTOMMARGIN, 57
+ END
+
+ IDD_SIMPLEHELP, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 140
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 116
+ END
+
+ IDD_FILECOPY_QUESTION, DIALOG
+ BEGIN
+ LEFTMARGIN, 6
+ RIGHTMARGIN, 242
+ TOPMARGIN, 6
+ BOTTOMMARGIN, 60
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_ACCELERATOR_VIEW ACCELERATORS
+BEGIN
+ VK_RETURN, ID_COPY_SELECTION, VIRTKEY, CONTROL, NOINVERT
+ "E", ID_EJECT_DISC, VIRTKEY, SHIFT, CONTROL, NOINVERT
+ "I", ID_MINIINFO_SHOW, VIRTKEY, CONTROL, NOINVERT
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.K.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_RIP_AND_BURN "Nullsoft Rip & Burn v%d.%02d"
+ 65535 "{2C913A2F-CD49-40a1-8F1A-8EF7C2A22229}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ERROR_WRITING_TEMP_BURN_LIST "Error writing temporary burning list."
+ IDS_ERROR "Error"
+ IDS_BURN_X_TRACKX_ON_X "Burn %d %s on %c:"
+ IDS_X_OF_X_SECTORS_FREE "%d of %d sectors free"
+ IDS_GET_PRO_FOR_BURN_HIGHER_THAN_2X
+ "In order to use more than 2x burning, you must purchase Winamp Pro.\nWould you like information on purchasing Winamp Pro?"
+// IDS_WINAMP_PRO_FEATURE "Winamp Pro Feature"
+ IDS_NEED_TO_ADD_SOME_TRACKS_TO_BURN
+ "You need to add some tracks to burn first!"
+ IDS_TOTAL_LENGTH_IS_BIGGER_THAN_MEDIA_CAPACITY
+ "The total length of the tracks in the list is bigger than the capacity of the blank CD in the drive!\n\nYou need to remove %01d:%02ds from the list."
+ IDS_BURNING "Burning..."
+ IDS_CANCEL_BURN "Cancel Burn"
+ IDS_NO_BLANK_CDR_IN_DRIVE "No blank CD-R in drive.\n"
+ IDS_X_CAPACITY_DETAILS "%s - Capacity: %01d:%02d.\n"
+ IDS_USED_X_X_TRACKS "Used: %01d:%02d (%d %s)"
+ IDS_AVAILABLE_X_X ", Available: %01d:%02d."
+ IDS_X_OVER_CAPACITY_REMOVE_X_TRACKS
+ ", %01d:%02d over capacity. Please remove %d tracks."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_FILE_X_CANNOT_BE_BURNED_REASON_NOT_FOUND
+ "The file\n%s\ncannot be burned.\nReason: File not found."
+ IDS_FILE_X_CANNOT_BE_BURNED_REASON_FILETYPE_NOT_REGISTERED
+ "The file\n%s\ncannot be burned.\nReason: The file type (extension %s) is not registered with Winamp"
+ IDS_FILE_X_CANNOT_BE_BURNED_REASON_X
+ "The file\n%s\ncannot be burned.\nReason: %s"
+ IDS_VIDEO_FILES_CANNOT_BE_BURNED "Video files cannot be burned"
+ IDS_NOT_AN_AUDIO_FILE "Not an audio file"
+ IDS_FILE_CANNOT_BE_BURNED "The file\n%s\ncannot be burned."
+ IDS_TRACK_NUMBER "Track #"
+ IDS_TITLE "Title"
+ IDS_LENGTH "Length"
+ IDS_STATUS "Status"
+ IDS_CANCELLING "Cancelling..."
+ IDS_BURNING_AUDIO_CANCELLING
+ "Burning Audio CD...\nCurrent Operation: Canceling..."
+ IDS_BURNING_AUDIO_FINISHING
+ "Burning Audio CD...\nCurrent Operation: Finishing..."
+ IDS_BURNING_AUDIO_DATA_PREP_FINISHED
+ "Burning Audio CD...\nCurrent Operation: Data preparation finished."
+ IDS_BURNING_AUDIO_VERIFYING_FILES
+ "Burning Audio CD...\nCurrent Operation: Verifying files..."
+ IDS_BURNING_AUDIO_VERIFICATION_COMPLETED
+ "Burning Audio CD...\nCurrent Operation: Verification completed."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_OPENING_DISC_WRITING_LEAD_IN "Opening disc / Writing Lead-In..."
+ IDS_CLOSING_DISC_WRITING_LEAD_OUT "Closing disc / Writing Lead-Out..."
+ IDS_BURNING_AUDIO_CURRENT_OPERATION
+ "Burning Audio CD... ( %d%% completed)\nCurrent Operation: %s"
+ IDS_BURNING_AUDIO_CD_PREP_DATA
+ "Burning Audio CD... ( %d%% completed)\nCurrent Operation: Preparing data..."
+ IDS_BURNING_AUDIO_BURNING_DATA
+ "Burning Audio CD... ( %d%% completed)\nCurrent Operation: Burning data..."
+ IDS_CLOSE "Close"
+ IDS_AUDIO_CD_BURNED_SUCCESSFULLY "Audio CD burned successfully."
+ IDS_BURN_ABORTED_BY_USER "Burning aborted by user."
+ IDS_BURNING_FAILED "Burning failed."
+ IDS_BURNING_COMPLETED_STATUS_X "Burning completed.\nStatus: %s"
+ IDS_ALL_FILES "All files"
+ IDS_ADD_FILES_TO_BURNING_LIST "Add file(s) to burning list"
+ IDS_CHOOSE_A_FOLDER_TO_ADD_TO_BURNING_LIST
+ "Choose a folder to add to the burning list"
+ IDS_SURE_YOU_WANT_TO_CLEAR_BURNING_LIST
+ "Are you sure you want to clear the burning list?"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_BURNING_ "Burning"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PREPARING "Preparing"
+ IDS_FINISHED "Finished"
+ IDS_PREPARED "Prepared"
+ IDS_SKIPPED "Skipped"
+ IDS_SCHEDULED "Scheduled"
+ IDS_CHECKING_LICENSE "Checking License..."
+ IDS_LICENSED "Licensed"
+ IDS_CANCELLED "Cancelled"
+ IDS_FAILED "Failed"
+ IDS_BAD_FILENAME "Bad filename"
+ IDS_UNABLE_TO_OPEN_FILE "Unable to open file"
+ IDS_CACHE_WRITE_FAILED "Cache write failed"
+ IDS_UNABLE_TO_FIND_DECODER "Unable to find decoder"
+ IDS_CANNOT_ADD_TO_THE_DISC "Cannot add to the disc"
+ IDS_CACHE_READ_FAILED "Cache read failed"
+ IDS_UNKNOWN_ERROR "Unknown Error"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ADDING_TRACKS_TO_BURNER_TOTAL_LENGTH_X
+ "Adding tracks to burner. Total length: %01d:%02d"
+ IDS_PLEASE_INSERT_BLANK_RECORDABLE_CD
+ "Please insert a blank recordable CD in drive %c:"
+ IDS_COMPLETED "Completed"
+ IDS_QUEUED "Queued"
+ IDS_RIPPING "Ripping"
+ IDS_CANCEL_RIP "Cancel rip?"
+ IDS_CD_RIP_QUESTION "CD Rip Question"
+ IDS_INITIALIZING "Initializing..."
+ IDS_CALCULATING_REPLAY_GAIN "Calculating Replay Gain"
+ IDS_UNKNOWN_ARTIST "Unknown Artist"
+ IDS_UNKNOWN_ALBUM "Unknown Album"
+ IDS_UNKNOWN "Unknown"
+ IDS_RIP_COMPLETE "Rip Complete"
+ IDS_RIP_FAILED "Rip Failed"
+ IDS_X_TRACKS_RIPPED_IN_X
+ "%d track%s ripped in %d:%02d (%.1fx realtime)\nOutput bitrate: %dkbps, Output size: %.1fMB"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DONE "Done"
+ IDS_ELAPSED_X_REMAINING_X_TOTAL_X
+ "Elapsed: %d:%02d, Remaining: %d:%02d, Total: %d:%02d (%.1fx realtime)\nOutput bitrate: %dkbps, Output size (estimated): %.1fMB"
+ IDS_X_KBPS_AT_X_REALTIME "%02d%%: %dkbps at %.1fx realtime"
+ IDS_X_OF_X_ELAPSED_X_REMAINING_X
+ "%d/%d: Elapsed: %d:%02d, Remaining: %d:%02d, Total: %d:%02d (%.1fx realtime)\nOutput bitrate: %dkbps, Output size (estimated): %.1fMB"
+ IDS_X_PERCENT_RIPPING_FROM_CD "[%d%%] Ripping from CD..."
+ IDS_WAITING "Waiting..."
+ IDS_STAMPED_DISC_OR_RECORDABLE_THAT_HAS_BEEN_RECORDED
+ "Stamped disc or a recordable disc that has been recorded Disc-At-Once."
+ IDS_REWRITEABLE_DISC_HAS_DATA_BUT_KEPT_OPEN_FOR_APPEND
+ "Rewritable disc that contains data but remains open, allowing the appending of additional data."
+ IDS_REWRITEABLE_DISC_NOT_POSSIBLE_TO_APPEND_DATA
+ "Rewritable disc not possible to append additional data."
+ IDS_BLANK_REWRITEABLE_DISC "Blank rewritable disc."
+ IDS_MEDIA_BLANK_DISC "Blank disc"
+ IDS_MEDIA_DATA_MODE_1_DAO "Data Mode 1 DAO"
+ IDS_MEDIA_KODAK_PHOTO_CD "Kodak Photo CD"
+ IDS_MEDIA_DATA_MULTISESSION_MODE_1_CLOSED
+ "Data multisession Mode 1 (closed)"
+ IDS_MEDIA_DATA_MULTISESSION_MODE_2_CLOSED
+ "Data multisession Mode 2 (closed)"
+ IDS_MEDIA_DATA_MODE_2_DAO "Data Mode 2 DAO"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_MEDIA_CDRFS "CDRFS"
+ IDS_MEDIA_PACKET_WRITING "Packet writing"
+ IDS_MEDIA_DATA_MULTISESSION_MODE_1_OPEN "Data multisession Mode 1 (open)"
+ IDS_MEDIA_DATA_MULTISESSION_MODE_2_OPEN "Data multisession Mode 2 (open)"
+ IDS_MEDIA_AUDIO_DAO_SAO_TAO "Audio DAO/SAO/TAO"
+ IDS_MEDIA_AUDIO_REWRITEABLE_DISC_WITH_SESSION_NOT_CLOSED
+ "Audio Rewritable disc with session not closed (TAO or SAO)"
+ IDS_MEDIA_FIRST_TYPE_OF_ENHANCED_CD_ABORTED
+ "First type of Enhanced CD (aborted)"
+ IDS_MEDIA_CD_EXTRA "CD Extra"
+ IDS_MEDIA_AUDIO_TAO_WITH_SESSION_NOT_WRITTEN
+ "Audio TAO with session not written (in-progress compilation)"
+ IDS_MEDIA_FIRST_TRACK_DATA_OTHERS_AUDIO "First track data, others audio"
+ IDS_MEDIA_MIXED_MODE_MADE_TAO "Mixed-mode made TAO"
+ IDS_MEDIA_KODAK_PORTFOLIO "Kodak Portfolio (as per the Kodak standard)"
+ IDS_MEDIA_VIDEO_CD "Video CD"
+ IDS_MEDIA_CDi "CD-i"
+ IDS_MEDIA_PLAYSTATION_SONY_GAMES "PlayStation (Sony games)"
+ IDS_MEDIA_OBSOLETE "Obsolete"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_MEDIA_OBSOLETE_FOR_RESTRICTED_OVERWRITE_DVD
+ "Obsolete for restricted overwrite DVD (DLA DVD-RW)"
+ IDS_MEDIA_DVDROM_OR_CLOSED_RECORDABLE "DVD-ROM or closed recordable"
+ IDS_MEDIA_INCREMENTAL_DVD_WITH_APPENDABLE_ZONE
+ "Incremental DVD with appendable zone (DLA DVD-R and DVD+RW)"
+ IDS_MEDIA_APPENDABLE_DVD_OF_ANY_TYPE
+ "Appendable DVD of any type (single border or multiborder)"
+ IDS_MEDIA_DVDRAM_CARTRIDGE "DVD-RAM cartridge"
+ IDS_MEDIA_CD_OTHER_TYPE "CD (other type)"
+ IDS_X_DRIVE_X "%s Drive %c:"
+ IDS_X_DRIVE_BRACKET_X "%s Drive (%c:)"
+ IDS_YOU_ARE_CURRENTLY_BURNING_AUDIO_CD_MUST_CANCEL_TO_CLOSE_WINAMP
+ "You are currently burning Audio CD in drive %c:\nYou must first cancel burning in order to close Winamp."
+ IDS_NOTIFICATION "Notification"
+ IDS_RIP_AND_BURN "Rip & Burn"
+ IDS_CD_RIPPING "CD Ripping"
+ IDS_CD_BURNER_ON_X "CD Burner on %c:"
+ IDS_EXAMPLE_RIPPED_FILE_FILENAME "Example ripped file filename:\n"
+ IDS_EXAMPLE_PLAYLIST_FILENAME "Example playlist filename:\n"
+ IDS_RIPPED_FILENAME_FORMAT_HELP "Ripped Filename Format Help"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RIPPPED_PLAYLIST_FORMAT_HELP "Ripped Playlist Filename Format Help"
+ IDS_RIPPED_PLAYLIST_FORMAT
+ "<Album> - inserts the album (default capitalization)\r\n<ALBUM> - inserts the album (all uppercase)\r\n<album> - inserts the album (all lowercase)\r\n<Artist> - inserts the album artist (default capitalization)\r\n<ARTIST> - inserts the album artist (all uppercase)\r\n<artist> - inserts the album artist (all lowercase)\r\n<Genre> - inserts the album genre (default capitalization)\r\n<GENRE> - inserts the album genre (all uppercase)\r\n<genre> - inserts the album genre (all lowercase)\r\n<year> - inserts the album year\r\n<disc> - inserts the disc number\r\n<discs> - inserts the total discs number (e.g. CD<disc> of <discs>)"
+ IDS_RIPPED_FILENAME_FORMAT
+ "<Album> - inserts the album (default capitalization)\r\n<ALBUM> - inserts the album (all uppercase)\r\n<album> - inserts the album (all lowercase)\r\n<Artist> - inserts the album artist (default capitalization)\r\n<ARTIST> - inserts the album artist (all uppercase)\r\n<artist> - inserts the album artist (all lowercase)\r\n<Genre> - inserts the album genre (default capitalization)\r\n<GENRE> - inserts the album genre (all uppercase)\r\n<genre> - inserts the album genre (all lowercase)\r\n<Title> - inserts the track title (default capitalization)\r\n<TITLE> - inserts the track title (all uppercase)\r\n<title> - inserts the track title (all lowercase)\r\n<Trackartist> - inserts the track artist (default capitalization)\r\n<TRACKARTIST> - inserts the track artist (all uppercase)\r\n<trackartist> - inserts the track artist (all lowercase)\r\n<year> - inserts the album year\r\n#, ##, or ### - inserts the track number, with leading 0s if ## or ###\r\n<disc> - inserts the disc number\r\n<discs> - inserts the total discs number (e.g. CD<disc> of <discs>)"
+ IDS_CHOOSE_A_FOLDER "Choose a folder"
+ IDS_TO_EXTRACT_TO_MP3_NEED_TO_PURCHASE_WINAMP_PRO
+ "In order to extract to MP3, you must purchase Winamp Pro.\nWould you like information on purchasing Winamp Pro?"
+ IDS_ENCODER "Encoder"
+ IDS_OUTPUT_FILE_SETTINGS "Output File Settings"
+ IDS_PLAYLIST_GENERATION "Playlist Generation"
+ IDS_ERROR_CD_RIP_IN_PROGRESS "Error - CD Rip in progress"
+ IDS_CD_RIP "CD Rip"
+ IDS_ERROR_CD_BURN_IN_PROGRESS "Error - CD Burn in progress"
+ IDS_ERROR_CANNOT_EXTRACT_DATA_CDS "Error - Cannot extract Data CDs"
+ IDS_NO_TRACKS_TO_RIP "No tracks to rip"
+ IDS_CD_PLAYBACK_ERROR "CD Playback Error"
+ IDS_CD_EJECT "CD Eject"
+ IDS_NO_CD "No CD"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DATA_TRACK "Data track"
+ IDS_AUDIO_TRACK "Audio track"
+ IDS_DISC_READ_ERROR "Unable to read disc info."
+ IDS_DATA_CD "Data CD"
+ IDS_DRIVE "drive"
+ IDS_ARTIST "Artist"
+ IDS_TRACK "track"
+ IDS_TRACKS "tracks"
+ IDS_YES "Yes"
+ IDS_NO "No"
+ IDS_SHOW_INFO "Show Info"
+ IDS_HIDE_INFO "Hide Info"
+ IDS_READINGDISC "Reading Disc..."
+ IDS_INFO_RIPPING "Sorry, Information unavailable while ripping cd."
+ IDS_DVD_DRIVE "DVD Drive"
+ IDS_CD_DRIVE "CD Drive"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CD_RECORDER "CD Recorder"
+ IDS_DATA_DISC "Data Disc"
+ IDS_BLANK_DISC "Blank Disc"
+ IDS_DRIVE_CAP "Drive"
+ IDS_RECORDER_CAP "Recorder"
+ IDS_CD_AUDIO "Audio CD"
+ IDS_DISC_BLANK "Blank Disc"
+ IDS_DISC_DATA "Data Disc"
+ IDS_CD "CD"
+ IDS_DVD "DVD"
+ IDS_CALCULATING "Calculating..."
+ IDS_ML_VIEW_ARTIST_ALBUM "Artist: %s\nAlbum: %s"
+ IDS_ML_VIEW_YEAR_GENRE "Year: %s\nGenre: %s"
+ IDS_RIPPED_PLAYLIST_FORMAT_CAPTION
+ "You may enter a format string for the playlist of your ripped files.\nIt can contain \\ or / to delimit a path, and the following keywords:"
+ IDS_RIPPED_FILENAME_FORMAT_CAPTION
+ "You may enter a filename format string for your ripped files.\nIt can contain \\ or / to delimit a path, and the following keywords:"
+ IDS_COPY_FILENAME_FORMAT_TITLE "Filename Format Help"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COPY_FILENAME_FORMAT_CAPTION
+ "You may enter a format string for your copied files.\nIt can contain \\ or / to delimit a path, and the following keywords:"
+ IDS_COPY_FILENAME_FORMAT
+ "<Album> - inserts the album (default capitalization)\r\n<ALBUM> - inserts the album (all uppercase)\r\n<album> - inserts the album (all lowercase)\r\n<Artist> - inserts the album artist (default capitalization)\r\n<ARTIST> - inserts the album artist (all uppercase)\r\n<artist> - inserts the album artist (all lowercase)\r\n<Extension> - inserts file extension (default capitalization)\r\n<EXTENSION> - inserts file extension (all uppercase)\r\n<extension> - inserts file extension (all lowercase)\r\n<Filename> - inserts file name (default capitalization)\r\n<FILENAME> - inserts file name (all uppercase)\r\n<filename> - inserts file name (all lowercase)\r\n<Genre> - inserts the album genre (default capitalization)\r\n<GENRE> - inserts the album genre (all uppercase)\r\n<genre> - inserts the album genre (all lowercase)\r\n<Title> - inserts the track title (default capitalization)\r\n<TITLE> - inserts the track title (all uppercase)\r\n<title> - inserts the track title (all lowercase)\r\n<Trackartist> - inserts the track artist (default capitalization)\r\n<TRACKARTIST> - inserts the track artist (all uppercase)\r\n<trackartist> - inserts the track artist (all lowercase)\r\n<year> - inserts the album year\r\n#, ##, or ### - inserts the track number, with leading 0s if ## or ###"
+ IDS_OPTIONS_SHOW "Show Options"
+ IDS_OPTIONS_HIDE "Hide Options"
+ IDS_COPY_PREP_MESSAGE_SINGLE_FILE
+ "Are you sure you want to copy '%s' to '%s'?"
+ IDS_COPY_PREP_MESSAGE_MULTIPLE_FILES
+ "Are you sure you want to copy these %d files to '%s'?"
+ IDS_COPY_TASK_PREPARE "Initializing..."
+ IDS_COPY_TASK_COPY "Copying..."
+ IDS_COPY_OP_CALCULATESIZE "Calculating total size..."
+ IDS_COPY_OP_CHECKDESTINATION "Checking destination..."
+ IDS_COPY_TASK_FINISHED "Completed"
+ IDS_COPY_ERROR_CAPTION "File Copy Error"
+ IDS_COPY_ERROR_MESSAGE "Unable to copy files to '%s'.\n\nReason:\t %s.\nCode:\t %d\nDetails:\t %s"
+ IDS_COPY_ERRMSG_INITIALIZATION_FAILED "Initialization failed"
+ IDS_COPY_ERRMSG_DIRECTORYCREATE_FAILED "Unable to create directory"
+ IDS_COPY_ERRMSG_COPYFILE_FAILED "Unable to copy file"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COPY_ERRMSG_TITLEFORMAT_FAILED "File title formatting failed"
+ IDS_COPY_ERRMSG_ADDTOMLDB_FAILED
+ "Unable to add file to local media database"
+ IDS_COPY_ERRMSG_SETATTRIBUTES_FAILED "Unable to set file attributes"
+ IDS_COPY_ERRMSG_COPYFILE_USERABORT "User abort"
+ IDS_COPY_ERRMSG_DELETEFILE_FAILED "Unable to delete file"
+ IDS_CONFIRM_CREATE_DESTINATION "Confirm Create Destination"
+ IDS_DESTINATION_NOT_EXIST_FORMAT
+ "Destination path '%s' does not exist.\nDo you want to create it?"
+ IDS_CANCEL "Cancel"
+ IDS_SKIP "Skip"
+ IDS_OVERWRITE "Overwrite"
+ IDS_APPLY_TO_ALL_FILES "Apply to all files"
+ IDS_CONFIRM_FILE_REPLACE "Confirm File Replace"
+ IDS_FILE_REPLACE_FORMAT "Destination folder '%s' already contains file named '%s'.\n\nWould you like to replace existing file %s with this one? %s"
+ IDS_CONFIRM_FILE_DELETE "Confirm File Delete"
+ IDS_READONLY_FILE_DELETE_FORMAT
+ "The file '%s' is a read-only file.\nAre you sure you want to delete it? "
+ IDS_SIZE "Size"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CREATED "Created"
+ IDS_MODIFIED "Modified"
+ IDS_UNKNOWN_GENRE "Unknown Genre"
+ IDS_YOU_ARE_CURRENTLY_COPYING_DATA_CD_MUST_CANCEL_TO_CLOSE_WINAMP
+ "You are currently copying files from drive %c:\nYou must first cancel copying in order to close Winamp."
+ IDS_YOU_ARE_CURRENTLY_RIPPING_AUDIO_CD_MUST_CANCEL_TO_CLOSE_WINAMP
+ "You are currently ripping Audio CD in drive %c:\nYou must first cancel ripping in order to close Winamp."
+ IDS_ERROR_RIPPING_TRACK "Error ripping track #%d: %s\n\nAborting."
+ IDS_NO_INFO_AVAILABLE "No information available"
+END
+
+#endif // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_disc/ml_disc.sln b/Src/Plugins/Library/ml_disc/ml_disc.sln
new file mode 100644
index 00000000..6fa5a23a
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/ml_disc.sln
@@ -0,0 +1,30 @@
+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_disc", "ml_disc.vcxproj", "{CAEC36CE-5A74-4C31-9956-E2FF8713D26F}"
+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
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Debug|Win32.Build.0 = Debug|Win32
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Debug|x64.ActiveCfg = Debug|x64
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Debug|x64.Build.0 = Debug|x64
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Release|Win32.ActiveCfg = Release|Win32
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Release|Win32.Build.0 = Release|Win32
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Release|x64.ActiveCfg = Release|x64
+ {CAEC36CE-5A74-4C31-9956-E2FF8713D26F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4B5DC2A4-73F3-41B4-A1E4-5F556BDE113D}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_disc/ml_disc.vcxproj b/Src/Plugins/Library/ml_disc/ml_disc.vcxproj
new file mode 100644
index 00000000..a20771ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/ml_disc.vcxproj
@@ -0,0 +1,382 @@
+<?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>{CAEC36CE-5A74-4C31-9956-E2FF8713D26F}</ProjectGuid>
+ <RootNamespace>ml_disc</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>
+ <InlineFunctionExpansion>Default</InlineFunctionExpansion>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <SmallerTypeCheck>false</SmallerTypeCheck>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;winmm.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ </Link>
+ <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>
+ <InlineFunctionExpansion>Default</InlineFunctionExpansion>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <SmallerTypeCheck>false</SmallerTypeCheck>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4838;4090;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;winmm.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ShowIncludes>false</ShowIncludes>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;winmm.lib;setupapi.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>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4838;4090;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ShowIncludes>false</ShowIncludes>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;winmm.lib;setupapi.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>
+ <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\gaystring.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\graphics.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\trace.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="cdburn.cpp">
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+ </ClCompile>
+ <ClCompile Include="cdrip.cpp" />
+ <ClCompile Include="cmdbar_data.cpp" />
+ <ClCompile Include="commandbar.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="copyfiles.cpp" />
+ <ClCompile Include="copyfiles_post.cpp" />
+ <ClCompile Include="copyprep.cpp" />
+ <ClCompile Include="copyprogress.cpp" />
+ <ClCompile Include="drive.cpp" />
+ <ClCompile Include="driveListBox.cpp" />
+ <ClCompile Include="drivemngr.cpp" />
+ <ClCompile Include="formatfilename.cpp" />
+ <ClCompile Include="helpwnd.cpp" />
+ <ClCompile Include="infoBox.cpp" />
+ <ClCompile Include="M3UWriter.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="medium.cpp" />
+ <ClCompile Include="PLSWriter.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="primosdk_helper.cpp" />
+ <ClCompile Include="questionwnd.cpp" />
+ <ClCompile Include="ReplayGain.cpp" />
+ <ClCompile Include="settings.cpp" />
+ <ClCompile Include="spti.cpp" />
+ <ClCompile Include="view_cdrom.cpp" />
+ <ClCompile Include="view_container.cpp" />
+ <ClCompile Include="view_data.cpp" />
+ <ClCompile Include="view_info.cpp" />
+ <ClCompile Include="view_ripburn.cpp" />
+ <ClCompile Include="view_wait.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="api__ml_disc.h" />
+ <ClInclude Include="commandbar.h" />
+ <ClInclude Include="config.h" />
+ <ClInclude Include="copyfiles.h" />
+ <ClInclude Include="copyinternal.h" />
+ <ClInclude Include="drive.h" />
+ <ClInclude Include="driveListBox.h" />
+ <ClInclude Include="drivemngr.h" />
+ <ClInclude Include="infoBox.h" />
+ <ClInclude Include="M3UWriter.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="medium.h" />
+ <ClInclude Include="PLSWriter.h" />
+ <ClInclude Include="primosdk_helper.h" />
+ <ClInclude Include="ReplayGain.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="settings.h" />
+ <ClInclude Include="spti.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\cdrom_32x32_24.bmp" />
+ <Image Include="resources\filecopy.bmp" />
+ <Image Include="resources\listbox_back_2x68x24.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_disc.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_disc/ml_disc.vcxproj.filters b/Src/Plugins/Library/ml_disc/ml_disc.vcxproj.filters
new file mode 100644
index 00000000..0f828c34
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/ml_disc.vcxproj.filters
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="cdrip.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="cdburn.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="cmdbar_data.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="commandbar.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="copyfiles.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="copyfiles_post.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="copyprep.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="copyprogress.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="drive.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="driveListBox.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="drivemngr.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="formatfilename.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="helpwnd.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="infoBox.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="M3UWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="medium.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PLSWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="primosdk_helper.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="questionwnd.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ReplayGain.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="settings.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="spti.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_cdrom.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_container.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_data.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_info.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_ripburn.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_wait.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\graphics.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\trace.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_disc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="commandbar.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="config.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="copyfiles.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="copyinternal.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="drive.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="driveListBox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="drivemngr.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="infoBox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="M3UWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="medium.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PLSWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="primosdk_helper.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ReplayGain.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="settings.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="spti.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="png.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="ml_disc.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\cdrom_32x32_24.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\filecopy.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\listbox_back_2x68x24.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{1b8ee540-fd62-43d4-8cca-be075c8c8d67}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{e018c8c5-3a48-41af-bf92-d63db4e43326}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{0b7fb4d3-0bfd-4ef3-90f2-d969de7e4400}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{191771a6-3744-4d06-a476-64f3d85cdd56}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{12bdb53c-e56b-4403-bb0c-7fbe8226593c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{64766d69-e8b1-4432-a8f3-d082202e7362}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{bcf8a930-ff77-437f-a229-e985d9675be1}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{91f09635-9292-4055-b23d-613bb045199b}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{615eddd8-7e7c-4526-a8bf-1afe093a8149}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{ac350456-8c89-462f-b8c1-8868c38dede8}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/png.rc b/Src/Plugins/Library/ml_disc/png.rc
new file mode 100644
index 00000000..3ae6cdb0
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/png.rc
@@ -0,0 +1,45 @@
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Data
+//
+IDB_NAVITEM_CDROM RCDATA
+".\\resources\\cdrom.png"
+IDB_EJECT_NORMAL RCDATA
+".\\resources\\eject3.png"
+IDB_EJECT_HILITED RCDATA
+".\\resources\\eject1.png"
+IDB_EJECT_PRESSED RCDATA
+".\\resources\\eject2.png"
+IDB_EJECT_DISABLED RCDATA
+".\\resources\\eject4.png"
+IDB_PLAY_NORMAL RCDATA
+".\\resources\\play_n.png"
+IDB_PLAY_HIGHLIGHTED RCDATA
+".\\resources\\play_p.png"
+IDB_PLAY_DISABLED RCDATA
+".\\resources\\play_d.png"
+IDB_PLAY_PRESSED RCDATA
+".\\resources\\play_p.png"
+IDB_ENQUEUE_NORMAL RCDATA
+".\\resources\\enqueue_n.png"
+IDB_ENQUEUE_HIGHLIGHTED RCDATA
+".\\resources\\enqueue_p.png"
+IDB_ENQUEUE_DISABLED RCDATA
+".\\resources\\enqueue_d.png"
+IDB_ENQUEUE_PRESSED RCDATA
+".\\resources\\enqueue_p.png"
+IDB_EJECT2_NORMAL RCDATA
+".\\resources\\eject_n.png"
+IDB_EJECT2_HIGHLIGHTED RCDATA
+".\\resources\\eject_p.png"
+IDB_EJECT2_DISABLED RCDATA
+".\\resources\\eject_d.png"
+IDB_EJECT2_PRESSED RCDATA
+".\\resources\\eject_p.png"
+IDB_FILECOPY RCDATA
+".\\resources\\filecopy.png"
+IDB_PLAY_MENU RCDATA
+".\\resources\\play_menu_16x16.png"
+IDB_ENQUEUE_MENU RCDATA
+".\\resources\\enqueue_menu_16x16.png"
diff --git a/Src/Plugins/Library/ml_disc/prefs.cpp b/Src/Plugins/Library/ml_disc/prefs.cpp
new file mode 100644
index 00000000..f145f984
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/prefs.cpp
@@ -0,0 +1,351 @@
+#include "main.h"
+#include "../nu/AutoWide.h"
+#include "./resource.h"
+#include "./settings.h"
+#include "../Winamp/wa_ipc.h"
+#include <strsafe.h>
+
+static convertConfigStruct m_ccs;
+static int m_has_seled;
+
+static void myEnumProc(intptr_t user_data, const char *desc, int fourcc)
+{
+ HWND hwndDlg = (HWND) user_data;
+ if (fourcc == OLD_AAC_CODEC)
+ return ;
+
+ int a = (INT)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)(const wchar_t *)AutoWide(desc));
+
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, fourcc);
+
+ if ( m_ccs.format == fourcc )
+ {
+ m_has_seled = 1;
+ SendDlgItemMessage( hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0 );
+ }
+}
+
+static void doConfigResizeChild(HWND parent, HWND child)
+{
+ if (child)
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r);
+ ScreenToClient(parent, (LPPOINT)&r);
+ SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ ShowWindow(child, SW_SHOWNA);
+ }
+}
+
+static HWND subWnd;
+
+static void DisplayFormatExample(HWND hdlg, INT nItemId, BOOL bFile)
+{
+ BOOL bUpper;
+ TCHAR szBuffer[MAX_PATH*2] = {0};
+ TCHAR szFormat[MAX_PATH] = {0};
+
+ Settings_ReadString(C_EXTRACT, (bFile) ? EF_TITLEFMT : EF_PLAYLISTFMT, szFormat, ARRAYSIZE(szFormat));
+
+ WASABI_API_LNGSTRINGW_BUF(((bFile) ? IDS_EXAMPLE_RIPPED_FILE_FILENAME : IDS_EXAMPLE_PLAYLIST_FILENAME),
+ szBuffer, ARRAYSIZE(szBuffer));
+
+ FormatFileName(szBuffer, ARRAYSIZE(szBuffer), szFormat,
+ (bFile) ? 10 : 0xdeadbeef,
+ TEXT("U2"), TEXT("The Joshua Tree"),
+ (bFile) ? TEXT("Exit") : NULL,
+ TEXT("Rock"),
+ TEXT("1987"),
+ TEXT("U2"),
+ NULL,
+ TEXT(""));
+
+ wchar_t szExtension[32] = {0};
+ if (bFile)
+ {
+ int c;
+ Settings_GetInt(C_EXTRACT, EF_FOURCC, &c);
+ if (c == OLD_AAC_CODEC) Settings_GetDefault(C_EXTRACT, EF_FOURCC, &c);
+ GetExtensionString(szExtension, ARRAYSIZE(szExtension), c);
+
+ Settings_GetBool(C_EXTRACT, EF_UPPEREXTENSION, &bUpper);
+ if (bUpper) CharUpper(szExtension);
+ else CharLower(szExtension);
+ }
+ else StringCchCopy(szExtension, ARRAYSIZE(szExtension), TEXT("m3u"));
+
+ StringCchCat(szBuffer, ARRAYSIZE(szBuffer), TEXT("."));
+ StringCchCat(szBuffer, ARRAYSIZE(szBuffer), szExtension);
+ SetDlgItemText(hdlg, nItemId, szBuffer);
+}
+
+static INT_PTR CALLBACK CDPrefs1Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND hActiveHelp = NULL;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ SendDlgItemMessage(hwndDlg, IDC_DESTPATH, EM_SETLIMITTEXT, MAX_PATH, 0);
+ Settings_SetCheckBox(C_EXTRACT, EF_UPPEREXTENSION, hwndDlg, IDC_UPPERCASEEXT);
+ Settings_SetDirectoryCtrl(C_EXTRACT, EF_PATH, hwndDlg, IDC_DESTPATH);
+ Settings_SetDlgItemText(C_EXTRACT, EF_TITLEFMT, hwndDlg, IDC_FILENAMEFMT);
+ Settings_SetCheckBox(C_EXTRACT, EF_ADDMETADATA, hwndDlg, IDC_TAGFILES);
+ Settings_SetCheckBox(C_EXTRACT, EF_CALCULATERG, hwndDlg, IDC_AUTO_RG);
+ Settings_SetCheckBox(C_EXTRACT, EF_USETOTALTRACKS, hwndDlg, IDC_TOTAL_TRACKS);
+ Settings_SetCheckBox(C_EXTRACT, EF_ADDTOMLDB, hwndDlg, IDC_CHECK_ML);
+ Settings_SetDlgItemInt(C_EXTRACT, EF_TRACKOFFSET, hwndDlg, IDC_EDIT2);
+ Settings_SetDlgItemText(C_EXTRACT, EF_COMMENTTEXT, hwndDlg, IDC_EDIT1);
+
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_DESTPATH:
+ if (HIWORD(wParam) == EN_CHANGE) Settings_FromDirectoryCtrl(C_EXTRACT, EF_PATH, hwndDlg,IDC_DESTPATH);
+ break;
+ case IDC_UPPERCASEEXT:
+ Settings_FromCheckBox(C_EXTRACT, EF_UPPEREXTENSION, hwndDlg, IDC_UPPERCASEEXT);
+ DisplayFormatExample(hwndDlg, IDC_FMTOUT, TRUE);
+ break;
+ case IDC_FILENAMEFMT:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ Settings_FromDlgItemText(C_EXTRACT, EF_TITLEFMT, hwndDlg, IDC_FILENAMEFMT);
+ DisplayFormatExample(hwndDlg, IDC_FMTOUT, TRUE);
+ }
+ break;
+ case IDC_BUTTON1:
+ Settings_BrowseForFolder(C_EXTRACT, EF_PATH, hwndDlg, IDC_DESTPATH);
+ break;
+ case IDC_BUTTON2:
+ if (hActiveHelp) SetWindowPos(hActiveHelp, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ else hActiveHelp = MLDisc_ShowHelp(hwndDlg, MAKEINTRESOURCE(IDS_RIPPED_FILENAME_FORMAT_HELP),
+ MAKEINTRESOURCE(IDS_RIPPED_FILENAME_FORMAT_CAPTION), MAKEINTRESOURCE(IDS_RIPPED_FILENAME_FORMAT), HF_ALLOWRESIZE);
+ break;
+ case IDC_EDIT2: if (EN_CHANGE == HIWORD(wParam)) Settings_FromDlgItemText(C_EXTRACT, EF_TRACKOFFSET, hwndDlg, IDC_EDIT2);break;
+ case IDC_EDIT1: if (EN_CHANGE == HIWORD(wParam)) Settings_FromDlgItemText(C_EXTRACT, EF_COMMENTTEXT, hwndDlg, IDC_EDIT1); break;
+ case IDC_AUTO_RG: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_CALCULATERG, hwndDlg, IDC_AUTO_RG); break;
+ case IDC_TOTAL_TRACKS: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_USETOTALTRACKS, hwndDlg, IDC_TOTAL_TRACKS); break;
+ case IDC_TAGFILES: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_ADDMETADATA, hwndDlg, IDC_TAGFILES); break;
+ case IDC_CHECK_ML: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_ADDTOMLDB, hwndDlg, IDC_CHECK_ML); break;
+
+ }
+ break;
+ case WM_DESTROY:
+ if (hActiveHelp) DestroyWindow(hActiveHelp);
+ break;
+ case WM_PARENTNOTIFY:
+ if (hActiveHelp && LOWORD(wParam) == WM_DESTROY && hActiveHelp == (HWND)lParam)
+ hActiveHelp = NULL;
+ break;
+ }
+ return 0;
+}
+
+int getRegVer();
+
+static INT_PTR CALLBACK CDPrefs2Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ m_ccs.hwndParent = hwndDlg;
+ Settings_GetInt(C_EXTRACT, EF_FOURCC, &m_ccs.format);
+ if (m_ccs.format == OLD_AAC_CODEC) Settings_GetDefault(C_EXTRACT, EF_FOURCC, &m_ccs.format);
+
+ converterEnumFmtStruct enumf = { myEnumProc, (INT)(INT_PTR)hwndDlg };
+ m_has_seled = 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&enumf, IPC_CONVERT_CONFIG_ENUMFMTS);
+ if (!m_has_seled)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, 0, 0);
+ m_ccs.format = mmioFOURCC('W', 'A', 'V', ' ');
+ }
+
+ HWND h = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & m_ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_ENCFORMAT:
+ if (HIWORD(wParam) != CBN_SELCHANGE) return 0;
+ {
+ int sel = (INT)(INT_PTR)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
+ if (sel != CB_ERR)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&m_ccs, IPC_CONVERT_CONFIG_END);
+ int last = m_ccs.format;
+ if (RegisteredEncoder(last) || last == OLD_AAC_CODEC) Settings_GetDefault(C_EXTRACT, EF_FOURCC, &last);
+
+ m_ccs.format = (int)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0);
+ Settings_SetInt(C_EXTRACT, EF_FOURCC, m_ccs.format);
+
+ HWND h = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & m_ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ }
+ }
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&m_ccs, IPC_CONVERT_CONFIG_END);
+ break;
+ }
+ return 0;
+}
+
+
+static INT_PTR CALLBACK CDPrefs4Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND hActiveHelp = NULL;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ Settings_SetDlgItemText(C_EXTRACT, EF_PLAYLISTFMT, hwndDlg, IDC_FILENAMEFMT);
+ Settings_SetCheckBox(C_EXTRACT, EF_CREATEM3U, hwndDlg, IDC_CHECK1);
+ Settings_SetCheckBox(C_EXTRACT, EF_USEM3UEXT, hwndDlg, IDC_CHECK3);
+ Settings_SetCheckBox(C_EXTRACT, EF_CREATEPLS, hwndDlg, IDC_CHECK2);
+ Settings_SetCheckBox(C_EXTRACT, EF_CREATEMLPL, hwndDlg, IDC_CHECK4);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK3), IsDlgButtonChecked(hwndDlg, IDC_CHECK1));
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_FILENAMEFMT:
+ if (LOWORD(wParam) != IDC_FILENAMEFMT || HIWORD(wParam) == EN_CHANGE)
+ {
+ Settings_FromDlgItemText(C_EXTRACT, EF_PLAYLISTFMT, hwndDlg, IDC_FILENAMEFMT);
+ DisplayFormatExample(hwndDlg, IDC_FMTOUT, FALSE);
+ }
+ return 0;
+ case IDC_BUTTON2:
+ if (hActiveHelp) SetWindowPos(hActiveHelp, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ else hActiveHelp = MLDisc_ShowHelp(hwndDlg, MAKEINTRESOURCE(IDS_RIPPPED_PLAYLIST_FORMAT_HELP),
+ MAKEINTRESOURCE(IDS_RIPPED_PLAYLIST_FORMAT_CAPTION), MAKEINTRESOURCE(IDS_RIPPED_PLAYLIST_FORMAT), HF_ALLOWRESIZE);
+ break;
+ case IDC_CHECK1:
+ if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_CREATEM3U, hwndDlg, IDC_CHECK1);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK3), IsDlgButtonChecked(hwndDlg, IDC_CHECK1));
+ break;
+
+ case IDC_CHECK3: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_USEM3UEXT, hwndDlg, IDC_CHECK3); break;
+ case IDC_CHECK2: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_CREATEPLS, hwndDlg, IDC_CHECK2); break;
+ case IDC_CHECK4: if (BN_CLICKED == HIWORD(wParam)) Settings_FromCheckBox(C_EXTRACT, EF_CREATEMLPL, hwndDlg, IDC_CHECK4); break;
+ }
+ break;
+
+ case WM_DESTROY:
+ if (hActiveHelp) DestroyWindow(hActiveHelp);
+ break;
+ case WM_PARENTNOTIFY:
+ if (hActiveHelp && LOWORD(wParam) == WM_DESTROY && hActiveHelp == (HWND)lParam)
+ hActiveHelp = NULL;
+ break;
+ }
+ return 0;
+}
+
+
+static int has_extract;
+
+static void _dosetsel(HWND hwndDlg)
+{
+ HWND tabwnd = GetDlgItem(hwndDlg, IDC_TAB1);
+ int sel = TabCtrl_GetCurSel(tabwnd);
+
+ if (sel >= 0 && (sel != g_config->ReadInt(L"lastcdprefp", 0) || !subWnd))
+ {
+ g_config->WriteInt(L"lastcdprefp", sel);
+ if (subWnd) DestroyWindow(subWnd);
+ subWnd = 0;
+
+ UINT t = 0;
+ DLGPROC p = NULL;
+ if (!has_extract && sel) sel++;
+ switch (sel)
+ {
+ case 2: t = IDD_PREFS_CDRIP1; p = CDPrefs1Proc; break;
+ case 0: t = IDD_PREFS_CDRIP2; p = CDPrefs2Proc; break;
+ case 3: t = IDD_PREFS_CDRIP4; p = CDPrefs4Proc; break;
+ case 1:
+ {
+ t = 0;
+ char buf2[512] = {0};
+ char buf3[512] = {0};
+ StringCchPrintfA(buf3, 512, "cdda_cf_%d", (INT)(INT_PTR)hwndDlg);
+ getFileInfo("cda://", buf3, buf2, sizeof(buf2));
+ subWnd = (HWND)(INT_PTR)atoi(buf2);
+ }
+ break;
+ default: subWnd = 0; t = 0; break;
+ }
+ if (t) subWnd = WASABI_API_CREATEDIALOGW(t, hwndDlg, p);
+
+ if (subWnd)
+ {
+ RECT r;
+ GetClientRect(tabwnd, &r);
+ TabCtrl_AdjustRect(tabwnd, FALSE, &r);
+ SetWindowPos(subWnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE);
+ ShowWindow(subWnd, SW_SHOWNA);
+ }
+
+ if(!SendMessage(plugin.hwndWinampParent,WM_WA_IPC,IPC_ISWINTHEMEPRESENT,IPC_USE_UXTHEME_FUNC))
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)tabwnd,IPC_USE_UXTHEME_FUNC);
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)subWnd,IPC_USE_UXTHEME_FUNC);
+ }
+ }
+}
+
+
+BOOL CALLBACK CDRipPrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ TCITEM item;
+ HWND tabwnd = GetDlgItem(hwndDlg, IDC_TAB1);
+ item.mask = TCIF_TEXT;
+ item.pszText = WASABI_API_LNGSTRINGW(IDS_ENCODER);
+ TabCtrl_InsertItem(tabwnd, 0, &item);
+
+ wchar_t buf2[512] = {0};
+ getFileInfoW(L"cda://", L"cdda_config_text", buf2, 512);
+
+ if (buf2[0])
+ {
+ item.pszText = buf2;
+ TabCtrl_InsertItem(tabwnd, 3, &item);
+ has_extract = 1;
+ }
+ else has_extract = 0;
+
+ item.pszText = WASABI_API_LNGSTRINGW(IDS_OUTPUT_FILE_SETTINGS);
+ TabCtrl_InsertItem(tabwnd, 1 + has_extract, &item);
+ item.pszText = WASABI_API_LNGSTRINGW(IDS_PLAYLIST_GENERATION);
+ TabCtrl_InsertItem(tabwnd, 2 + has_extract, &item);
+
+ TabCtrl_SetCurSel(tabwnd, g_config->ReadInt(L"lastcdprefp", 0));
+ _dosetsel(hwndDlg);
+ }
+ return 0;
+ case WM_NOTIFY:
+ {
+ LPNMHDR p = (LPNMHDR) lParam;
+ if (p->idFrom == IDC_TAB1 && p->code == TCN_SELCHANGE) _dosetsel(hwndDlg);
+ }
+ return 0;
+ case WM_DESTROY:
+ subWnd = NULL;
+ return 0;
+ }
+ return 0;
+}
+
diff --git a/Src/Plugins/Library/ml_disc/primosdk_helper.cpp b/Src/Plugins/Library/ml_disc/primosdk_helper.cpp
new file mode 100644
index 00000000..60b32ff2
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/primosdk_helper.cpp
@@ -0,0 +1,109 @@
+#include "./primosdk_helper.h"
+//#include "../primo/obj_primo.h"
+#include "api__ml_disc.h"
+#include <api/service/waservicefactory.h>
+
+typedef struct _PRIMOSDK_INSTANCE
+{
+// obj_primo *primo;
+ BOOL bLoadFailed;
+ LONG uRef;
+} PRIMOSDK_INSTANCE;
+
+static PRIMOSDK_INSTANCE sdk = {/*NULL,*/ FALSE, 0, };
+
+BOOL PrimoSDKHelper_IsInitialized(void)
+{
+ return (0 != sdk.uRef);
+}
+
+BOOL PrimoSDKHelper_IsLoaded(void)
+{
+ /*char t[64] = {0};
+ wsprintfA(t,"%d %d %d\n%d",sdk.bLoadFailed, sdk.primo, sdk.uRef, !(!sdk.uRef || !sdk.primo));
+ MessageBoxA(0,t,0,0);*/
+ return !(!sdk.uRef /*|| !sdk.primo*/);//(sdk.bLoadFailed==FALSE);
+}
+
+LONG PrimoSDKHelper_Initialize(void)
+{
+ return 0;
+#if 0
+ if (sdk.bLoadFailed) return 0;
+
+ if (!sdk.uRef)
+ {
+ BOOL bFailed = TRUE;
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(obj_primo::getServiceGuid());
+ if (sf) sdk.primo = reinterpret_cast<obj_primo *>(sf->getInterface());
+ if (sdk.primo)
+ bFailed = FALSE;
+
+ sdk.bLoadFailed += bFailed;
+ }
+ InterlockedIncrement(&sdk.uRef);
+
+ return sdk.uRef;
+#endif
+}
+
+LONG PrimoSDKHelper_Uninitialize(void)
+{
+ return 0;
+#if 0
+ if (sdk.uRef && 0 == InterlockedDecrement(&sdk.uRef))
+ {
+ if (sdk.primo)
+ {
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(obj_primo::getServiceGuid());
+ if (sf) sf->releaseInterface(sdk.primo);
+ sdk.primo = 0;
+ }
+ }
+ return sdk.uRef;
+#endif
+}
+
+DWORD PrimoSDKHelper_UnitInfo(PDWORD pdwUnit, PDWORD pdwType, PBYTE szDescr, PDWORD pdwReady)
+{
+ return PRIMOSDK_CMDSEQUENCE;
+#if 0
+ if (!sdk.uRef) return PRIMOSDK_CMDSEQUENCE;
+ if (!sdk.primo) return PRIMOSDK_CMDSEQUENCE;
+
+ return sdk.primo->UnitInfo(pdwUnit, pdwType, szDescr, pdwReady);
+#endif
+}
+
+DWORD PrimoSDKHelper_UnitInfo2(PDWORD pdwUnit, PDWORD pdwTypes, PDWORD pdwClass, PDWORD pdwBusType, PDWORD pdwRFU)
+{
+return PRIMOSDK_CMDSEQUENCE;
+#if 0
+ if (!sdk.uRef) return PRIMOSDK_CMDSEQUENCE;
+ if (!sdk.primo) return PRIMOSDK_CMDSEQUENCE;
+
+ return sdk.primo->UnitInfo2(pdwUnit, pdwTypes, pdwClass, pdwBusType, pdwRFU);
+#endif
+}
+
+DWORD PrimoSDKHelper_DiscInfoEx(PDWORD pdwUnit, DWORD dwFlags, PDWORD pdwMediumType, PDWORD pdwMediumFormat, PDWORD pdwErasable, PDWORD pdwTracks, PDWORD pdwUsed, PDWORD pdwFree)
+{
+#if 0
+ if (!sdk.uRef) return PRIMOSDK_CMDSEQUENCE;
+ if (!sdk.primo) return PRIMOSDK_CMDSEQUENCE;
+
+ return sdk.primo->DiscInfoEx(pdwUnit, dwFlags, pdwMediumType, pdwMediumFormat, pdwErasable, pdwTracks, pdwUsed, pdwFree);
+#endif
+ return PRIMOSDK_CMDSEQUENCE;
+}
+
+DWORD PrimoSDKHelper_DiscInfo2(PDWORD pdwUnit, PDWORD pdwMedium, PDWORD pdwProtectedDVD, PDWORD pdwFlags, PDWORD pdwMediumEx, PDWORD pdwRFU3)
+{
+ return PRIMOSDK_CMDSEQUENCE;
+#if 0
+ if (!sdk.uRef) return PRIMOSDK_CMDSEQUENCE;
+ if (!sdk.primo) return PRIMOSDK_CMDSEQUENCE;
+
+ return sdk.primo->DiscInfo2(pdwUnit, pdwMedium, pdwProtectedDVD, pdwFlags, pdwMediumEx, pdwRFU3);
+#endif
+}
diff --git a/Src/Plugins/Library/ml_disc/primosdk_helper.h b/Src/Plugins/Library/ml_disc/primosdk_helper.h
new file mode 100644
index 00000000..04078bfd
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/primosdk_helper.h
@@ -0,0 +1,31 @@
+#ifndef NULLOSFT_MLDISC_PRIMOSDK_HELPER_HEADER
+#define NULLOSFT_MLDISC_PRIMOSDK_HELPER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+//#include <primosdk.h>
+#define PRIMOSDK_CMDSEQUENCE ((DWORD)0xFFFFFFFF)
+#define PRIMOSDK_OK 0
+#define PRIMOSDK_PACKETWRITTEN 0
+// !!!! Not thread safe !!!!
+
+#define DEFAULT_HANDLE ((DWORD)0xFFFFFFFF)
+
+// Initialization
+LONG PrimoSDKHelper_Initialize(void);
+LONG PrimoSDKHelper_Uninitialize(void);
+BOOL PrimoSDKHelper_IsInitialized(void);
+BOOL PrimoSDKHelper_IsLoaded(void);
+
+// Drive Info (You can use DEFAULT_HANDLE)
+DWORD PrimoSDKHelper_UnitInfo(PDWORD pdwUnit, PDWORD pdwType, PBYTE szDescr, PDWORD pdwReady);
+DWORD PrimoSDKHelper_UnitInfo2(PDWORD pdwUnit, PDWORD pdwTypes, PDWORD pdwClass, PDWORD pdwBusType, PDWORD pdwRFU);
+
+// Medium Info (You can use DEFAULT_HANDLE)
+DWORD PrimoSDKHelper_DiscInfoEx(PDWORD pdwUnit, DWORD dwFlags, PDWORD pdwMediumType, PDWORD pdwMediumFormat, PDWORD pdwErasable, PDWORD pdwTracks, PDWORD pdwUsed, PDWORD pdwFree);
+DWORD PrimoSDKHelper_DiscInfo2(PDWORD pdwUnit, PDWORD pdwMedium, PDWORD pdwProtectedDVD, PDWORD pdwFlags, PDWORD pdwMediumEx, PDWORD pdwRFU3);
+
+#endif // NULLOSFT_MLDISC_PRIMOSDK_HELPER_HEADER
diff --git a/Src/Plugins/Library/ml_disc/questionwnd.cpp b/Src/Plugins/Library/ml_disc/questionwnd.cpp
new file mode 100644
index 00000000..66071eea
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/questionwnd.cpp
@@ -0,0 +1,279 @@
+#include "main.h"
+#include "./copyfiles.h"
+#include "./copyinternal.h"
+#include "./resource.h"
+#include "../nu/trace.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+#define QUESTIONBOX_PROP TEXT("QUESTIONBOX")
+#define GetQuestionBox(__hdlg) ((QUESTIONBOX*)GetProp((__hdlg), QUESTIONBOX_PROP))
+
+
+#define GetResolvedString(__pszText, __pszBuffer, __chhBufferMax)\
+ (IS_INTRESOURCE(__pszText) ? WASABI_API_LNGSTRINGW_BUF((UINT)(UINT_PTR)(__pszText), (__pszBuffer), (__chhBufferMax)) : (__pszText))
+
+static INT_PTR CALLBACK CopyQuestion_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+INT_PTR MLDisc_ShowQuestionBox(QUESTIONBOX *pQuestionBox)
+{
+ if (!pQuestionBox) return IDCANCEL;
+ return WASABI_API_DIALOGBOXPARAMW(IDD_FILECOPY_QUESTION, pQuestionBox->hParent, CopyQuestion_DialogProc, (LPARAM)pQuestionBox);
+}
+
+static BOOL FindPrefferedSizeEx(HDC hdc, LPCTSTR pszText, LPCTSTR pszNewLine, SIZE *pSize)
+{
+ if (!pSize) return FALSE;
+ pSize->cx = 0; pSize->cy = 0;
+ if (!hdc || !pszText || !pszNewLine) return FALSE;
+ LPCTSTR pszBlock = pszText;
+ LPCTSTR pszCursor = pszBlock;
+ INT cchSep = lstrlenW(pszNewLine);
+ INT matched = 0;
+ for(;;)
+ {
+ if (*pszCursor)
+ {
+ if (*pszCursor == pszNewLine[matched]) matched++;
+ else matched = 0;
+ pszCursor++;
+ }
+ if (matched == cchSep || TEXT('\0') == *pszCursor)
+ {
+ SIZE sz;
+
+ INT l = (INT)(size_t)((pszCursor - pszBlock) - matched);
+ if (l > 0)
+ {
+ if (!GetTextExtentPoint32(hdc, pszBlock, l, &sz)) return FALSE;
+ }
+ else
+ {
+ if (!GetTextExtentPoint32(hdc, TEXT("\n"), 1, &sz)) return FALSE;
+ sz.cx = 0;
+ }
+
+ if (pSize->cx < sz.cx) pSize->cx= sz.cx;
+ pSize->cy += sz.cy;
+
+ if (TEXT('\0') == *pszCursor) break;
+ else
+ {
+ matched = 0;
+ pszBlock = pszCursor;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static BOOL FindPrefferedSize(HWND hwnd, LPCTSTR pszText, LPCTSTR pszNewLine, SIZE *pSize)
+{
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_PARENTCLIP);
+ if (!hdc) return FALSE;
+ HFONT hf, hfo;
+ hf = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0L);
+ if (NULL == hf) hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ hfo = (NULL != hf) ? (HFONT)SelectObject(hdc, hf) : NULL;
+
+ BOOL br = FindPrefferedSizeEx(hdc, pszText, pszNewLine, pSize);
+
+ if (hfo) SelectObject(hdc, hfo);
+ ReleaseDC(hwnd, hdc);
+
+ return br;
+}
+
+static INT_PTR CopyQuestion_OnInitDialog(HWND hdlg, HWND hFocus, QUESTIONBOX *pqb)
+{
+ if (!pqb) return FALSE;
+ SetProp(hdlg, QUESTIONBOX_PROP, pqb);
+
+ HWND hctrl;
+ TCHAR szBuffer[2048] = {0};
+ LONG messageLeft = 0;
+
+ if (NULL != pqb->pszTitle) SetWindowText(hdlg, GetResolvedString(pqb->pszTitle, szBuffer, ARRAYSIZE(szBuffer)));
+
+ if (NULL != pqb->pszBtnOkText) SetDlgItemText(hdlg, IDOK, GetResolvedString(pqb->pszBtnOkText, szBuffer, ARRAYSIZE(szBuffer)));
+ if (NULL != pqb->pszBtnCancelText) SetDlgItemText(hdlg, IDCANCEL, GetResolvedString(pqb->pszBtnCancelText, szBuffer, ARRAYSIZE(szBuffer)));
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_BTN_EXTRA1)))
+ {
+ ShowWindow(hctrl, (QBF_SHOW_EXTRA_BUTTON & pqb->uFlags) ? SW_SHOWNA : SW_HIDE);
+ if (NULL != pqb->pszBtnExtraText) SetWindowText(hctrl, GetResolvedString(pqb->pszBtnExtraText, szBuffer, ARRAYSIZE(szBuffer)));
+ }
+
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_PIC_ICON)))
+ {
+ HICON hIcon = NULL;
+ if (NULL != pqb->pszIcon)
+ {
+ hIcon = LoadIcon(WASABI_API_LNG_HINST, pqb->pszIcon);
+ if (NULL == hIcon) hIcon = LoadIcon(WASABI_API_ORIG_HINST, pqb->pszIcon);
+ if (NULL == hIcon) hIcon = LoadIcon(NULL, pqb->pszIcon);
+ }
+ SendMessage(hctrl, STM_SETICON, (WPARAM)hIcon, 0L);
+ ShowWindow(hctrl, (hIcon) ? SW_SHOWNA : SW_HIDE);
+ RECT rw;
+ GetWindowRect(hctrl, &rw);
+ MapWindowPoints(HWND_DESKTOP, hdlg, (POINT*)&rw, 2);
+ messageLeft = (hIcon) ? (rw.right + 24) : rw.left;
+ }
+
+ INT shiftRight = 0, shiftBottom = 0;
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_LBL_MESSAGE)))
+ {
+ RECT rw;
+ SIZE textSize = { 0, 0 };
+ LPCTSTR pszText = (NULL != pqb->pszMessage) ? GetResolvedString(pqb->pszMessage, szBuffer, ARRAYSIZE(szBuffer)) : NULL;
+ if (pszText)
+ {
+ FindPrefferedSize(hctrl, pszText, TEXT("\n"), &textSize);
+ textSize.cx += 8; textSize.cy += 4;
+ }
+ SetWindowText(hctrl, pszText);
+ GetWindowRect(hctrl, &rw);
+ MapWindowPoints(HWND_DESKTOP, hdlg, (POINT*)&rw, 2);
+ rw.left = messageLeft;
+ shiftRight = (rw.left + textSize.cx) - rw.right;
+ if (shiftRight < 0) shiftRight = 0;
+ shiftBottom = textSize.cy - (rw.bottom - rw.top);
+ if (shiftBottom < 0) shiftBottom = 0;
+ SetWindowPos(hctrl, NULL, rw.left, rw.top, (rw.right - rw.left) + shiftRight, (rw.bottom - rw.top) + shiftBottom, SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_CHECKBOX1)))
+ {
+
+ if (NULL != pqb->pszCheckboxText) SetWindowText(hctrl, GetResolvedString(pqb->pszCheckboxText, szBuffer, ARRAYSIZE(szBuffer)));
+ SendMessage(hctrl, BM_SETCHECK, (pqb->checkboxChecked) ? BST_CHECKED : BST_UNCHECKED, 0L);
+
+ RECT rw;
+ GetWindowRect(hctrl, &rw);
+
+ if (0 == (QBF_SHOW_CHECKBOX & pqb->uFlags))
+ {
+ shiftBottom -= (rw.bottom - rw.top);
+ ShowWindow(hctrl, SW_HIDE);
+ }
+ else if (shiftRight || shiftBottom)
+ {
+ MapWindowPoints(HWND_DESKTOP, hdlg, (POINT*)&rw, 2);
+ SetWindowPos(hctrl, NULL, rw.left, rw.top + shiftBottom,
+ (rw.right - rw.bottom) + shiftRight, (rw.bottom - rw.top), SWP_NOACTIVATE | SWP_NOZORDER);
+ ShowWindow(hctrl, SW_SHOWNA);
+ }
+ }
+
+ if (shiftRight || shiftBottom)
+ {
+ RECT rw;
+ INT idList[] = {IDC_BTN_EXTRA1, IDOK, IDCANCEL, };
+ for (int i = 0; i < ARRAYSIZE(idList); i++)
+ {
+ if (NULL != (hctrl = GetDlgItem(hdlg, idList[i])))
+ {
+ GetWindowRect(hctrl, &rw);
+ MapWindowPoints(HWND_DESKTOP, hdlg, (POINT*)&rw, 2);
+ SetWindowPos(hctrl, NULL, rw.left + shiftRight, rw.top + shiftBottom, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+ }
+
+ }
+
+ HWND hParent = GetParent(hdlg);
+ if (hParent)
+ {
+ RECT rw, rc;
+ GetClientRect(hParent, &rc);
+ GetWindowRect(hdlg, &rw);
+ rw.right += shiftRight;
+ rw.bottom += shiftBottom;
+
+ SetWindowPos(hdlg, NULL,
+ rw.left + ((rc.right - rc.left) - (rw.right - rw.left))/2,
+ rw.top + ((rc.bottom - rc.top) - (rw.bottom - rw.top))/2,
+ rw.right - rw.left, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+
+ SendMessage(hdlg, DM_REPOSITION, 0, 0L);
+ return FALSE;
+}
+
+static void CopyQuestion_OnDestroy(HWND hdlg)
+{
+ QUESTIONBOX *pqb = GetQuestionBox(hdlg);
+ if (pqb)
+ {
+ pqb->checkboxChecked = (BST_CHECKED == IsDlgButtonChecked(hdlg, IDC_CHECKBOX1));
+ }
+ RemoveProp(hdlg, QUESTIONBOX_PROP);
+}
+
+static void CopyQuestion_OnCommand(HWND hdlg, INT ctrlId, INT eventId, HWND hctrl)
+{
+ switch(ctrlId)
+ {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hdlg, ctrlId);
+ break;
+ case IDC_BTN_EXTRA1:
+ if (BN_CLICKED == eventId) EndDialog(hdlg, ctrlId);
+ break;
+ }
+}
+#define IDT_POSTSHOW 1985
+#define DELAY_POSTSHOW 0
+
+static void CALLBACK CopyQuestion_OnPostShowElapsed(HWND hdlg, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ QUESTIONBOX *pqb = GetQuestionBox(hdlg);
+ KillTimer(hdlg, idEvent);
+ if (!pqb) return;
+
+ if (QBF_FLASH & pqb->uFlags)
+ {
+ FLASHWINFO flash = { sizeof(FLASHWINFO), };
+ flash.hwnd = hdlg;
+ flash.dwFlags = FLASHW_ALL;
+ flash.uCount = 2;
+ flash.dwTimeout = 300;
+ FlashWindowEx(&flash);
+ }
+
+ if (QBF_BEEP & pqb->uFlags) MessageBeep(pqb->uBeepType);
+
+ if ((QBF_SETFOREGROUND | QBF_TOPMOST) & pqb->uFlags)
+ {
+ SetForegroundWindow(hdlg);
+ SetWindowPos(hdlg, (QBF_SETFOREGROUND & pqb->uFlags) ? HWND_TOP : HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ }
+
+
+
+
+
+}
+static void CopyQuestion_OnShowWindow(HWND hdlg, BOOL bShow, UINT nStatus)
+{
+ if (bShow)
+ {
+ SetTimer(hdlg, IDT_POSTSHOW, DELAY_POSTSHOW, CopyQuestion_OnPostShowElapsed);
+ }
+}
+
+static INT_PTR CALLBACK CopyQuestion_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return CopyQuestion_OnInitDialog(hdlg, (HWND)wParam, (QUESTIONBOX*)lParam);
+ case WM_DESTROY: CopyQuestion_OnDestroy(hdlg); break;
+ case WM_COMMAND: CopyQuestion_OnCommand(hdlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ case WM_SHOWWINDOW: CopyQuestion_OnShowWindow(hdlg, (BOOL)wParam, (UINT)lParam); break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/resource.h b/Src/Plugins/Library/ml_disc/resource.h
new file mode 100644
index 00000000..60dec117
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resource.h
@@ -0,0 +1,455 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_disc.rc
+//
+#define IDS_ERROR_WRITING_TEMP_BURN_LIST 1
+#define IDS_ERROR 2
+#define IDS_BURN_X_TRACKX_ON_X 3
+#define IDS_X_OF_X_SECTORS_FREE 4
+#define IDS_GET_PRO_FOR_BURN_HIGHER_THAN_2X 5
+// #define IDS_WINAMP_PRO_FEATURE 6
+#define IDS_NEED_TO_ADD_SOME_TRACKS_TO_BURN 7
+#define IDS_TOTAL_LENGTH_IS_BIGGER_THAN_MEDIA_CAPACITY 8
+#define IDS_BURNING 9
+#define IDS_CANCEL_BURN 10
+#define IDS_NO_BLANK_CDR_IN_DRIVE 11
+#define IDS_X_CAPACITY_DETAILS 12
+#define IDS_USED_X_X_TRACKS 13
+#define IDS_AVAILABLE_X_X 14
+#define IDS_X_OVER_CAPACITY_REMOVE_X_TRACKS 15
+#define IDS_FILE_X_CANNOT_BE_BURNED_REASON_NOT_FOUND 16
+#define IDS_FILE_X_CANNOT_BE_BURNED_REASON_FILETYPE_NOT_REGISTERED 17
+#define IDS_FILE_X_CANNOT_BE_BURNED_REASON_X 18
+#define IDS_VIDEO_FILES_CANNOT_BE_BURNED 19
+#define IDS_NOT_AN_AUDIO_FILE 20
+#define IDS_FILE_CANNOT_BE_BURNED 21
+#define IDS_TRACK_NUMBER 22
+#define IDS_TITLE 23
+#define IDS_LENGTH 24
+#define IDS_STATUS 25
+#define IDS_CANCELLING 26
+#define IDS_BURNING_AUDIO_CANCELLING 27
+#define IDS_BURNING_AUDIO_FINISHING 28
+#define IDS_BURNING_AUDIO_DATA_PREP_FINISHED 29
+#define IDS_BURNING_AUDIO_VERIFYING_FILES 30
+#define IDS_BURNING_AUDIO_VERIFICATION_COMPLETED 31
+#define IDS_OPENING_DISC_WRITING_LEAD_IN 32
+#define IDS_CLOSING_DISC_WRITING_LEAD_OUT 33
+#define IDS_BURNING_AUDIO_CURRENT_OPERATION 34
+#define IDS_BURNING_AUDIO_CD_PREP_DATA 35
+#define IDS_BURNING_AUDIO_BURNING_DATA 36
+#define IDS_CLOSE 37
+#define IDS_AUDIO_CD_BURNED_SUCCESSFULLY 38
+#define IDS_BURN_ABORTED_BY_USER 39
+#define IDS_BURNING_FAILED 40
+#define IDS_BURNING_COMPLETED_STATUS_X 41
+#define IDS_ALL_FILES 42
+#define IDS_ADD_FILES_TO_BURNING_LIST 43
+#define IDS_CHOOSE_A_FOLDER_TO_ADD_TO_BURNING_LIST 44
+#define IDS_SURE_YOU_WANT_TO_CLEAR_BURNING_LIST 45
+#define IDS_CONFIRMATION 46
+#define IDS_BURNING_ 47
+#define IDS_PREPARING 48
+#define IDS_FINISHED 49
+#define IDS_PREPARED 50
+#define IDS_SKIPPED 51
+#define IDS_SCHEDULED 52
+#define IDS_CHECKING_LICENSE 53
+#define IDS_LICENSED 54
+#define IDS_CANCELLED 55
+#define IDS_FAILED 56
+#define IDS_BAD_FILENAME 57
+#define IDS_UNABLE_TO_OPEN_FILE 58
+#define IDS_CACHE_WRITE_FAILED 59
+#define IDS_UNABLE_TO_FIND_DECODER 60
+#define IDS_CANNOT_ADD_TO_THE_DISC 61
+#define IDS_CACHE_READ_FAILED 62
+#define IDS_UNKNOWN_ERROR 63
+#define IDS_ADDING_TRACKS_TO_BURNER_TOTAL_LENGTH_X 64
+#define IDS_PLEASE_INSERT_BLANK_RECORDABLE_CD 65
+#define IDS_COMPLETED 66
+#define IDS_QUEUED 67
+#define IDS_RIPPING 68
+#define IDS_CANCEL_RIP 69
+#define IDS_CD_RIP_QUESTION 70
+#define IDS_INITIALIZING 71
+#define IDS_CALCULATING_REPLAY_GAIN 72
+#define IDS_UNKNOWN_ARTIST 73
+#define IDS_UNKNOWN_ALBUM 74
+#define IDS_UNKNOWN 75
+#define IDS_RIP_COMPLETE 77
+#define IDS_RIP_FAILED 78
+#define IDS_X_TRACKS_RIPPED_IN_X 79
+#define IDS_DONE 80
+#define IDS_ELAPSED_X_REMAINING_X_TOTAL_X 81
+#define IDS_X_KBPS_AT_X_REALTIME 82
+#define IDS_X_OF_X_ELAPSED_X_REMAINING_X 83
+#define IDS_X_PERCENT_RIPPING_FROM_CD 84
+#define IDS_WAITING 85
+#define IDS_STAMPED_DISC_OR_RECORDABLE_THAT_HAS_BEEN_RECORDED 86
+#define IDS_REWRITEABLE_DISC_HAS_DATA_BUT_KEPT_OPEN_FOR_APPEND 87
+#define IDS_REWRITEABLE_DISC_NOT_POSSIBLE_TO_APPEND_DATA 88
+#define IDS_BLANK_REWRITEABLE_DISC 89
+#define IDS_MEDIA_BLANK_DISC 90
+#define IDS_MEDIA_DATA_MODE_1_DAO 91
+#define IDS_MEDIA_KODAK_PHOTO_CD 92
+#define IDS_MEDIA_DATA_MULTISESSION_MODE_1_CLOSED 93
+#define IDS_MEDIA_DATA_MULTISESSION_MODE_2_CLOSED 94
+#define IDS_MEDIA_DATA_MODE_2_DAO 95
+#define IDS_MEDIA_CDRFS 96
+#define IDS_MEDIA_PACKET_WRITING 97
+#define IDS_MEDIA_DATA_MULTISESSION_MODE_1_OPEN 98
+#define IDS_MEDIA_DATA_MULTISESSION_MODE_2_OPEN 99
+#define IDS_MEDIA_AUDIO_DAO_SAO_TAO 100
+#define IDS_MEDIA_AUDIO_REWRITEABLE_DISC_WITH_SESSION_NOT_CLOSED 101
+#define IDS_MEDIA_FIRST_TYPE_OF_ENHANCED_CD_ABORTED 102
+#define IDS_MEDIA_CD_EXTRA 103
+#define IDS_MEDIA_AUDIO_TAO_WITH_SESSION_NOT_WRITTEN 104
+#define IDS_MEDIA_FIRST_TRACK_DATA_OTHERS_AUDIO 105
+#define IDS_MEDIA_MIXED_MODE_MADE_TAO 106
+#define IDR_CONTEXTMENUS 107
+#define IDS_MEDIA_KODAK_PORTFOLIO 107
+#define IDS_MEDIA_VIDEO_CD 108
+#define IDS_MEDIA_CDi 109
+#define IDD_VIEW_CONTAINER 109
+#define IDD_VIEW_CDROM 110
+#define IDS_MEDIA_PLAYSTATION_SONY_GAMES 110
+#define IDD_VIEW_CDROM1 110
+#define IDD_PREFSCDRIPFR 111
+#define IDS_MEDIA_OBSOLETE 111
+#define IDD_PREFS_CDRIP1 112
+#define IDS_MEDIA_OBSOLETE_FOR_RESTRICTED_OVERWRITE_DVD 112
+#define IDD_PREFS_CDRIP2 113
+#define IDS_MEDIA_DVDROM_OR_CLOSED_RECORDABLE 113
+#define IDD_PREFS_CDRIP4 114
+#define IDS_MEDIA_INCREMENTAL_DVD_WITH_APPENDABLE_ZONE 114
+#define IDD_VIEW_CDROM_EXTRACT 115
+#define IDS_MEDIA_APPENDABLE_DVD_OF_ANY_TYPE 115
+#define IDD_VIEW_CDROM_EX2 116
+#define IDS_MEDIA_DVDRAM_CARTRIDGE 116
+#define IDD_VIEW_CDROM_BURN 117
+#define IDS_MEDIA_CD_OTHER_TYPE 117
+#define IDD_BURN_ADD_STATUS 118
+#define IDS_X_DRIVE_X 118
+#define IDD_BURN 119
+#define IDS_X_DRIVE_BRACKET_X 119
+#define IDD_WAITFORCDR 120
+#define IDS_YOU_ARE_CURRENTLY_BURNING_AUDIO_CD_MUST_CANCEL_TO_CLOSE_WINAMP 120
+#define IDD_UPSELL_RIPPING 121
+#define IDS_NOTIFICATION 121
+#define IDD_NOBURN 122
+#define IDS_RIP_AND_BURN 122
+#define IDD_VIEW_RIPBURN 123
+#define IDS_CD_RIPPING 123
+#define IDS_CD_BURNER_ON_X 124
+#define IDD_VIEW_WAIT 124
+#define IDS_EXAMPLE_RIPPED_FILE_FILENAME 125
+#define IDD_VIEW_INFO 125
+#define IDS_EXAMPLE_PLAYLIST_FILENAME 126
+#define IDD_DIALOG1 126
+#define IDD_VIEW_CDROM_DATA 126
+#define IDS_RIPPED_FILENAME_FORMAT_HELP 127
+#define IDS_RIPPPED_PLAYLIST_FORMAT_HELP 128
+#define IDD_COMMANDBAR_DATA 128
+#define IDS_RIPPED_PLAYLIST_FORMAT 129
+#define IDS_RIPPED_FILENAME_FORMAT 130
+#define IDS_CHOOSE_A_FOLDER 131
+#define IDR_DATAVIEW_ACCELERATOR 131
+#define IDR_ACCELERATOR_VIEW 131
+#define IDS_TO_EXTRACT_TO_MP3_NEED_TO_PURCHASE_WINAMP_PRO 132
+#define IDD_FILECOPY_PREPARE 132
+#define IDS_ENCODER 133
+#define IDD_FILECOPY_PROGRESS 133
+#define IDS_OUTPUT_FILE_SETTINGS 134
+#define IDS_PLAYLIST_GENERATION 135
+#define IDS_ERROR_CD_RIP_IN_PROGRESS 136
+#define IDB_BITMAP1 136
+#define IDB_FILECOPY 136
+#define IDS_CD_RIP 137
+#define IDD_DLG_SIMPLEHELP 137
+#define IDD_SIMPLEHELP 137
+#define IDS_ERROR_CD_BURN_IN_PROGRESS 138
+#define IDD_FILECOPY_QUESTION 138
+#define IDS_ERROR_CANNOT_EXTRACT_DATA_CDS 139
+#define IDS_NO_TRACKS_TO_RIP 140
+#define IDS_CD_PLAYBACK_ERROR 141
+#define IDS_CD_EJECT 142
+#define IDS_NO_CD 143
+#define IDS_DATA_TRACK 144
+#define IDS_AUDIO_TRACK 145
+#define IDS_DISC_READ_ERROR 146
+#define IDS_DATA_CD 147
+#define IDS_DRIVE 148
+#define IDS_ARTIST 149
+#define IDS_TRACK 150
+#define IDS_TRACKS 151
+#define IDS_YES 152
+#define IDS_NO 153
+#define IDS_SHOW_INFO 154
+#define IDS_HIDE_INFO 155
+#define IDS_READINGDISC 156
+#define IDS_INFO_RIPPING 157
+#define IDS_DVD_DRIVE 158
+#define IDS_CD_DRIVE 159
+#define IDS_CD_RECORDER 160
+#define IDS_DATA_DISC 161
+#define IDS_STRING162 162
+#define IDS_BLANK_DISC 162
+#define IDS_DRIVE_CAP 163
+#define IDS_RECORDER_CAP 164
+#define IDS_CD_AUDIO 165
+#define IDS_DISC_BLANK 166
+#define IDS_DISC_DATA 167
+#define IDS_CD 168
+#define IDS_DVD 169
+#define IDS_CALCULATING 170
+#define IDS_ML_VIEW_ARTIST_ALBUM 171
+#define IDS_ML_VIEW_YEAR_GENRE 172
+#define IDS_RIPPED_PLAYLIST_FORMAT_CAPTION 173
+#define IDS_RIPPED_FILENAME_FORMAT_CAPTION 174
+#define IDS_COPY_FILENAME_FORMAT_TITLE 175
+#define IDS_COPY_FILENAME_FORMAT_CAPTION 176
+#define IDS_COPY_FILENAME_FORMAT 177
+#define IDS_OPTIONS_SHOW 178
+#define IDS_OPTIONS_HIDE 179
+#define IDS_EXAMPLE 180
+#define IDS_COPY_PREP_MESSAGE_SINGLE_FILE 180
+#define IDS_COPY_PREP_MESSAGE_MULTIPLE_FILES 181
+#define IDS_COPY_TASK_PREPARE 182
+#define IDS_COPY_TASK_COPY 183
+#define IDS_COPY_OP_CALCULATESIZE 184
+#define IDS_COPY_OP_CHECKDESTINATION 185
+#define IDS_COPY_TASK_FINISHED 186
+#define IDS_COPY_ERROR_CAPTION 187
+#define IDS_COPY_ERROR_MESSAGE 188
+#define IDS_COPY_ERRMSG_INITIALIZATION_FAILED 189
+#define IDS_COPY_ERRMSG_DIRECTORYCREATE_FAILED 190
+#define IDS_COPY_ERRMSG_COPYFILE_FAILED 191
+#define IDS_COPY_ERRMSG_TITLEFORMAT_FAILED 192
+#define IDS_COPY_ERRMSG_ADDTOMLDB_FAILED 193
+#define IDS_COPY_ERRMSG_SETATTRIBUTES_FAILED 194
+#define IDS_COPY_ERRMSG_COPYFILE_USERABORT 195
+#define IDS_COPY_ERRMSG_DELETEFILE_FAILED 196
+#define IDS_CONFIRM_CREATE_DESTINATION 197
+#define IDS_DESTINATION_NOT_EXIST_FORMAT 198
+#define IDS_CANCEL 199
+#define IDS_SKIP 200
+#define IDS_OVERWRITE 201
+#define IDS_APPLY_TO_ALL_FILES 202
+#define IDS_CONFIRM_FILE_REPLACE 203
+#define IDS_FILE_REPLACE_FORMAT 204
+#define IDS_CONFIRM_FILE_DELETE 205
+#define IDS_READONLY_FILE_DELETE_FORMAT 206
+#define IDS_SIZE 207
+#define IDS_CREATED 208
+#define IDS_MODIFIED 209
+#define IDS_UNKNOWN_GENRE 210
+#define IDS_YOU_ARE_CURRENTLY_COPYING_DATA_CD_MUST_CANCEL_TO_CLOSE_WINAMP 211
+#define IDS_YOU_ARE_CURRENTLY_RIPPING_AUDIO_CD_MUST_CANCEL_TO_CLOSE_WINAMP 212
+#define IDS_ERROR_RIPPING_TRACK 213
+#define IDS_NO_INFO_AVAILABLE 214
+#define IDC_LIST2 1001
+#define IDC_BUTTON_PLAY 1002
+#define IDC_BUTTON_ENQUEUE 1003
+#define IDC_BUTTON_EXTRACT 1004
+#define IDC_BUTTON_EJECT 1005
+#define IDC_CDINFO 1006
+#define IDC_CDINFO2 1007
+#define IDC_TAB1 1008
+#define IDC_TOTAL_TRACKS 1008
+#define IDC_DESTPATH 1009
+#define IDC_BUTTON1 1010
+#define IDC_FILENAMEFMT 1011
+#define IDC_BTN_BROWSE 1011
+#define IDC_BUTTON2 1012
+#define IDC_UPPERCASEEXT 1013
+#define IDC_CHECK_ML 1014
+#define IDC_EDT_NAMEFORMAT 1014
+#define IDC_TAGFILES 1015
+#define IDC_BTN_HELP 1015
+#define IDC_AUTO_RG 1016
+#define IDC_CHK_EXTUPPERCASE 1016
+#define IDC_EDIT2 1017
+#define IDC_CHK_CUSTOMNAME 1017
+#define IDC_EDIT1 1018
+#define IDC_MESSAGE2 1018
+#define IDC_CHK_ADDTOMLDB 1018
+#define IDC_FMTOUT 1019
+#define IDC_BTN_SHOWINFO 1019
+#define IDC_CHK_REPLAYGAIN 1019
+#define IDC_ENCFORMAT 1020
+#define IDC_LBL_TEXT 1020
+#define IDC_EDIT3 1020
+#define IDC_ENC_CONFIG 1021
+#define IDC_LV_DETAILS 1022
+#define IDC_STATUS 1023
+#define IDC_CTFRAME 1024
+#define IDC_PROGRESS1 1025
+#define IDC_BTN_PLAY 1025
+#define IDC_BTN_PLAYEX 1025
+#define IDC_PROGRESS2 1026
+#define IDC_BTN_ENQUEUE 1026
+#define IDC_STATUS2 1027
+#define IDC_BTN_EJECT 1027
+#define IDC_CHECK1 1028
+#define IDC_LBL_TEST 1028
+#define IDC_CHECK2 1029
+#define IDC_LIST1 1029
+#define IDC_CHECK3 1030
+#define IDC_COMBO2 1030
+#define IDC_CHECK4 1031
+#define IDC_COMBO3 1031
+#define IDC_CANCEL_RIP 1032
+#define IDC_CUSTOM1 1032
+#define IDC_CURTRACK 1033
+#define IDC_RIPOPTS 1034
+#define IDC_ADD 1035
+#define IDC_VDELIM 1035
+#define IDC_HDELIM 1035
+#define IDC_CLEAR 1036
+#define IDC_BURN 1037
+#define IDC_LOGO 1039
+#define IDC_BURN_OPTS 1040
+#define IDC_CANCEL_BURN 1042
+#define IDC_STAT 1043
+#define IDC_COMBO1 1044
+#define IDC_PRO1 1045
+#define IDC_PRO2 1046
+#define IDC_TEXT 1047
+#define IDC_MESSAGE 1048
+#define IDC_LBL 1048
+#define IDC_LBL_STATUS 1048
+#define IDC_LBL_BANNER 1049
+#define IDC_BTN_COPY 1049
+#define IDC_LBL_DRIVES 1050
+#define IDC_LBL_MESSAGE 1050
+#define IDC_LIST_DRIVES 1051
+#define IDC_BTN_OPTIONS 1051
+#define IDC_LBL_INFO_DRIVE 1052
+#define IDC_EDT_PATH 1052
+#define IDC_LBL_INFO_MEDIUM 1053
+#define IDC_GRP_OPTIONS 1053
+#define IDC_LBL_DRIVE_LETTER 1054
+#define IDC_EDT_TEXT 1054
+#define IDC_LBL_DRIVE_DESCRIPTION 1055
+#define IDC_LBL_CAPTION 1055
+#define IDC_LBL_DRIVE_BUS 1056
+#define IDC_LBL_EXAMPLE 1056
+#define IDC_LBL_DRIVE_TYPES 1057
+#define IDC_LBL_EXAMPLE_TITLE 1057
+#define IDC_LBL_MEDIUM_TYPE 1058
+#define IDC_LBL_FREE_TITLE 1058
+#define IDC_LBL_MEDIUM_FORMAT 1059
+#define IDC_LBL_REQUIRED_TITLE 1059
+#define IDC_LBL_MEDIUM_TRACKN 1060
+#define IDC_LBL_FREE 1060
+#define IDC_LBL_MEDIUM_CAPACITY 1061
+#define IDC_LBL_REQUIRED 1061
+#define IDC_LBL_MEDIUM_RECORDABLE 1062
+#define IDC_PRG_TOTAL 1062
+#define IDC_LBL_MEDIUM_ERASEABLE 1063
+#define IDC_LBL_TASK 1063
+#define IDC_LBL_MEDIUM_ADDINFO 1064
+#define IDC_LBL_OPERATION 1064
+#define IDC_LBL_DRIVE_LETTER_VAL 1065
+#define IDC_RB_GAIN_MODE_ALBUM 1065
+#define IDC_CHECKBOX1 1065
+#define IDC_LBL_DRIVE_DESCRIPTION_VAL 1066
+#define IDC_BTN_EXTRA1 1066
+#define IDC_LBL_DRIVE_BUS_VAL 1067
+#define IDC_PIC_ICON 1067
+#define IDC_LBL_DRIVE_TYPES_VAL 1068
+#define IDC_PIC_LOGO 1068
+#define IDC_LBL_MEDIUM_DISC_VAL 1069
+#define IDC_LBL_MEDIUM_FORMAT_VAL 1070
+#define IDC_LBL_MEDIUM_TRACKN_VAL 1071
+#define IDC_LBL_MEDIUM_CAPACITY_VAL 1072
+#define IDC_LBL_MEDIUM_RECORDABLE_VAL 1073
+#define IDC_LBL_MEDIUM_ERASEABLE_VAL 1074
+#define IDC_LBL_MEDIUM_ADDINFO_VAL 1075
+#define IDC_BTN_REFRESH 1076
+#define IDC_LBL_MEDIUM_UPDATE 1077
+#define IDC_LBL_MEDIUM_ADDINFO_VAL2 1078
+#define IDC_LBL_MEDIUM_NOINFO 1078
+// #define IDB_LOGO_SONIC 1101
+#define IDB_LISTITEM_CDDRIVE 1102
+#define IDB_LISTBOX_BACK 1104
+#define IDB_NAVITEM_CDROM 1106
+#define IDB_EJECT_NORMAL 1107
+#define IDB_EJECT_HILITED 1108
+#define IDB_EJECT_PRESSED 1109
+#define IDB_EJECT_DISABLED 1110
+#define IDB_PLAY_NORMAL 1111
+#define IDB_PLAY_HIGHLIGHTED 1112
+#define IDB_PLAY_DISABLED 1113
+#define IDB_PLAY_PRESSED 1114
+#define IDB_ENQUEUE_PRESSED 1115
+#define IDB_EJECT2_PRESSED 1116
+#define IDB_ENQUEUE_NORMAL 1117
+#define IDB_ENQUEUE_HIGHLIGHTED 1118
+#define IDB_ENQUEUE_DISABLED 1119
+#define IDB_EJECT2_NORMAL 1123
+#define IDB_EJECT2_HIGHLIGHTED 1124
+#define IDB_EJECT2_DISABLED 1125
+#define IDB_PLAY_MENU 1126
+#define IDB_ENQUEUE_MENU 1127
+#define ID_EXTRACTMENU_CONFIGURE 40001
+#define ID_EXTRACTMENU_EXTRACTALLTRACKS 40002
+#define ID_EXTRACTMENU_EXTRACTSELECTEDTRACKS 40003
+#define ID_CDROMMENU_PLAYSELECTEDITEMS 40004
+#define ID_CDROMMENU_ENQUEUESELECTEDITEMS 40005
+#define ID_CDROMMENU_PLAYALL 40006
+#define ID_CDROMMENU_ENQUEUEALL 40007
+#define ID_PE_ID3 40008
+#define ID_CDROMMENU_EXTRACT_EXTRACTSELECTEDITEMS 40009
+#define ID_CDROMMENU_EXTRACT_EXTRACTALL 40010
+#define ID_CDROMMENU_EXTRACT_CONFIGURE 40011
+#define ID_CDROMMENU_EJECTCD 40012
+#define ID_BURNADDMENU_FILES 40013
+#define ID_BURNADDMENU_FOLDER 40014
+#define ID_BURNADDMENU_CURRENTPLAYLIST 40015
+#define ID_BURNCONTEXTMENU_PLAYSELECTEDITEMS 40016
+#define ID_BURNCONTEXTMENU_ENQUEUESELECTEDITEMS 40017
+#define ID_BURNCONTEXTMENU_MOVESELECTEDITEMSUP 40018
+#define ID_BURNCONTEXTMENU_MOVESELECTEDITEMSDOWN 40019
+#define ID_BURNCONTEXTMENU_REMOVESELECTEDITEMS 40020
+#define ID_BURNCONTEXTMENU_BURN 40021
+#define ID_RIPOPTIONS_PRIORITY_HIGH 40022
+#define ID_RIPOPTIONS_PRIORITY_ABOVENORMAL 40023
+#define ID_RIPOPTIONS_PRIORITY_NORMAL 40024
+#define ID_RIPOPTIONS_PRIORITY_BELOWNORMAL 40025
+#define ID_RIPOPTIONS_PRIORITY_LOWEST 40026
+#define ID_RIPOPTIONS_PRIORITY_IDLE 40027
+#define ID_RIPOPTIONS_RIPPINGSTATUSWINDOW 40028
+#define ID_RIPOPTIONS_EJECTCDWHENCOMPLETED 40029
+#define ID_RIPOPTIONS_PLAYTRACKSWHENCOMPLETED 40030
+#define ID_RIPOPTIONS_CLOSEVIEWWHENCOMPLETE 40031
+#define ID_BURNOPTIONS_ADDCDTITLESTOLOCALCDDBCACHE 40032
+#define ID_BURNCONTEXTMENU_SELECTALL 40033
+#define ID_CDROMMENU2_SELECTALL 40034
+#define ID_CDROMMENU_SELECTALL 40035
+#define ID_Menu 40036
+#define ID_DATAVIEWOPTIONS_HIDEEXTENSIONS 40045
+#define ID_DATAVIEWOPTIONS_SHOWHIDDENFILES 40046
+#define ID_DATAVIEWOPTIONS_SHOW 40047
+#define ID_EJECT_DISC 40083
+#define ID_COPY_SELECTION 40085
+#define ID_DRIVE_MODE_CHANGED 40086
+#define ID_NAVIGATION_PREFERENCES 40087
+#define ID_NAVIGATION_HELP 40088
+#define ID_CDROMMENU2_HELP 40089
+#define ID_CDROMMENU_EXTRACT_HELP 40090
+#define ID_MINIINFO_SHOW 40104
+#define IDS_NULLSOFT_RIP_AND_BURN 65534
+#define IDS_COPY_ERROR_MESSSAGE 65535
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 216
+#define _APS_NEXT_COMMAND_VALUE 40091
+#define _APS_NEXT_CONTROL_VALUE 1079
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_disc/resources/cdrom.png b/Src/Plugins/Library/ml_disc/resources/cdrom.png
new file mode 100644
index 00000000..3c5d6856
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/cdrom.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/cdrom_32x32_24.bmp b/Src/Plugins/Library/ml_disc/resources/cdrom_32x32_24.bmp
new file mode 100644
index 00000000..f05dc379
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/cdrom_32x32_24.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject1.png b/Src/Plugins/Library/ml_disc/resources/eject1.png
new file mode 100644
index 00000000..7db2d8ad
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject1.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject2.png b/Src/Plugins/Library/ml_disc/resources/eject2.png
new file mode 100644
index 00000000..72bdd140
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject2.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject3.png b/Src/Plugins/Library/ml_disc/resources/eject3.png
new file mode 100644
index 00000000..54f37222
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject3.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject4.png b/Src/Plugins/Library/ml_disc/resources/eject4.png
new file mode 100644
index 00000000..b929fbac
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject4.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject_d.png b/Src/Plugins/Library/ml_disc/resources/eject_d.png
new file mode 100644
index 00000000..6b7c9b45
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject_d.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject_n.png b/Src/Plugins/Library/ml_disc/resources/eject_n.png
new file mode 100644
index 00000000..dd198f2b
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject_n.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/eject_p.png b/Src/Plugins/Library/ml_disc/resources/eject_p.png
new file mode 100644
index 00000000..68aa0c16
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/eject_p.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueue_d.png b/Src/Plugins/Library/ml_disc/resources/enqueue_d.png
new file mode 100644
index 00000000..fe755949
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueue_d.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueue_menu_16x16.png b/Src/Plugins/Library/ml_disc/resources/enqueue_menu_16x16.png
new file mode 100644
index 00000000..d3dd357a
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueue_menu_16x16.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueue_n.png b/Src/Plugins/Library/ml_disc/resources/enqueue_n.png
new file mode 100644
index 00000000..7b7ad057
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueue_n.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueue_p.png b/Src/Plugins/Library/ml_disc/resources/enqueue_p.png
new file mode 100644
index 00000000..12704e8f
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueue_p.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueuem_d.png b/Src/Plugins/Library/ml_disc/resources/enqueuem_d.png
new file mode 100644
index 00000000..0bd93761
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueuem_d.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueuem_n.png b/Src/Plugins/Library/ml_disc/resources/enqueuem_n.png
new file mode 100644
index 00000000..f76d4027
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueuem_n.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/enqueuem_p.png b/Src/Plugins/Library/ml_disc/resources/enqueuem_p.png
new file mode 100644
index 00000000..11f1b4d8
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/enqueuem_p.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/filecopy.png b/Src/Plugins/Library/ml_disc/resources/filecopy.png
new file mode 100644
index 00000000..85d7f746
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/filecopy.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/listbox_back_2x68x24.bmp b/Src/Plugins/Library/ml_disc/resources/listbox_back_2x68x24.bmp
new file mode 100644
index 00000000..6786dbbe
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/listbox_back_2x68x24.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/play_d.png b/Src/Plugins/Library/ml_disc/resources/play_d.png
new file mode 100644
index 00000000..f68d02ad
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/play_d.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/play_menu_16x16.png b/Src/Plugins/Library/ml_disc/resources/play_menu_16x16.png
new file mode 100644
index 00000000..20f265dd
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/play_menu_16x16.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/play_n.png b/Src/Plugins/Library/ml_disc/resources/play_n.png
new file mode 100644
index 00000000..b457b984
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/play_n.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/play_p.png b/Src/Plugins/Library/ml_disc/resources/play_p.png
new file mode 100644
index 00000000..e6a33c91
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/play_p.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/playm_d.png b/Src/Plugins/Library/ml_disc/resources/playm_d.png
new file mode 100644
index 00000000..6c279fb7
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/playm_d.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/playm_n.png b/Src/Plugins/Library/ml_disc/resources/playm_n.png
new file mode 100644
index 00000000..a3b219c3
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/playm_n.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/playm_p.png b/Src/Plugins/Library/ml_disc/resources/playm_p.png
new file mode 100644
index 00000000..ece39e30
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/playm_p.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/rip&burn_logo_228x25x16.bmp b/Src/Plugins/Library/ml_disc/resources/rip&burn_logo_228x25x16.bmp
new file mode 100644
index 00000000..ee89767a
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/rip&burn_logo_228x25x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/resources/sonic_powered_44x22.bmp b/Src/Plugins/Library/ml_disc/resources/sonic_powered_44x22.bmp
new file mode 100644
index 00000000..5ead745e
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/resources/sonic_powered_44x22.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_disc/settings.cpp b/Src/Plugins/Library/ml_disc/settings.cpp
new file mode 100644
index 00000000..e7fcb40c
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/settings.cpp
@@ -0,0 +1,573 @@
+#include "main.h"
+#include "./settings.h"
+#include "./resource.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define SECTION_DEFAULT TEXT("gen_ml_config")
+#define SECTION_DATAVIEW TEXT("data_view")
+// Keys
+#define KEY_EF_PATH TEXT("extractpath")
+#define KEY_EF_TITLEFMT TEXT("extractfmt2")
+#define KEY_EF_PLAYLISTFMT TEXT("extractplfmt2")
+#define KEY_EF_COMMENTTEXT TEXT("tagcomment")
+#define KEY_EF_UPPEREXTENSION TEXT("extractucext")
+#define KEY_EF_ADDMETADATA TEXT("extracttag")
+#define KEY_EF_CALCULATERG TEXT("auto_rg")
+#define KEY_EF_USETOTALTRACKS TEXT("total_tracks")
+#define KEY_EF_ADDTOMLDB TEXT("extractaddml")
+#define KEY_EF_TRACKOFFSET TEXT("trackoffs")
+#define KEY_EF_CREATEM3U TEXT("extractm3u")
+#define KEY_EF_CREATEPLS TEXT("extractpls")
+#define KEY_EF_CREATEMLPL TEXT("extractplml")
+#define KEY_EF_USEM3UEXT TEXT("extractm3uext")
+#define KEY_EF_FOURCC TEXT("extract4cc")
+
+// Defeault values
+#define DEF_EF_PATH GetDefaultExtractPath()
+#define DEF_EF_TITLEFMT TEXT("<Artist> - <Album>\\## - <Trackartist> - <Title>")
+#define DEF_EF_PLAYLISTFMT TEXT("<Artist> - <Album>\\<Artist> - <Album>")
+#define DEF_EF_COMMENTTEXT TEXT("Ripped by Winamp")
+#define DEF_EF_UPPEREXTENSION FALSE
+#define DEF_EF_ADDMETADATA TRUE
+#define DEF_EF_CALCULATERG FALSE
+#define DEF_EF_USETOTALTRACKS FALSE
+#define DEF_EF_ADDTOMLDB TRUE
+#define DEF_EF_TRACKOFFSET 1
+#define DEF_EF_CREATEM3U TRUE
+#define DEF_EF_CREATEPLS FALSE
+#define DEF_EF_CREATEMLPL TRUE
+#define DEF_EF_USEM3UEXT TRUE
+#define DEF_EF_FOURCC mmioFOURCC('A','A','C','f')
+
+
+#define KEY_CF_PATH KEY_EF_PATH
+#define KEY_CF_USETITLEFMT TEXT("copy_use_title_fmt")
+#define KEY_CF_TITLEFMT TEXT("copy_title_fmt")
+#define KEY_CF_ADDTOMLDB TEXT("copy_add_to_mldb")
+#define KEY_CF_CALCULATERG TEXT("copy_calc_gain")
+#define KEY_CF_GAINMODE TEXT("copy_gain_mode")
+
+#define DEF_CF_PATH DEF_EF_PATH
+#define DEF_CF_USETITLEFMT TRUE
+#define DEF_CF_TITLEFMT TEXT("<Artist>\\<Album>\\<Filename><extension>")
+#define DEF_CF_ADDTOMLDB DEF_EF_ADDTOMLDB
+#define DEF_CF_CALCULATERG DEF_EF_CALCULATERG
+#define DEF_CF_GAINMODE 0
+
+
+#define KEY_GF_SHOWINFO TEXT("showinfo")
+#define KEY_GF_SHOWPARENT TEXT("showparent")
+#define KEY_GF_ENQUEUEBYDEFAULT TEXT("enqueuedef")
+
+#define DEF_GF_SHOWINFO FALSE
+#define DEF_GF_SHOWPARENT FALSE
+#define DEF_GF_ENQUEUEBYDEFAULT FALSE
+
+#define KEY_DVF_COLUMNLIST TEXT("column_list")
+#define KEY_DVF_LASTFOLDER TEXT("last_folder")
+#define KEY_DVF_ORDERBY TEXT("column_order_by")
+#define KEY_DVF_ORDERASC TEXT("column_order_asc")
+#define KEY_DVF_VIEWMODE TEXT("view_mode")
+#define KEY_DVF_SHOWAUDIO TEXT("show_audio")
+#define KEY_DVF_SHOWVIDEO TEXT("show_video")
+#define KEY_DVF_SHOWPLAYLIST TEXT("show_playlist")
+#define KEY_DVF_SHOWUNKNOWN TEXT("show_unknown")
+#define KEY_DVF_HIDEEXTENSION TEXT("hide_extension")
+#define KEY_DVF_IGNOREHIDDEN TEXT("ignore_hidden")
+#define KEY_DVF_DIVIDERPOS TEXT("horz_divider_pos")
+
+
+#define DEF_DVF_COLUMNLIST NULL
+#define DEF_DVF_LASTFOLDER NULL
+#define DEF_DVF_ORDERBY 0
+#define DEF_DVF_ORDERASC TRUE
+#define DEF_DVF_VIEWMODE -1
+#define DEF_DVF_SHOWAUDIO TRUE
+#define DEF_DVF_SHOWVIDEO TRUE
+#define DEF_DVF_SHOWPLAYLIST TRUE
+#define DEF_DVF_SHOWUNKNOWN FALSE
+#define DEF_DVF_HIDEEXTENSION TRUE
+#define DEF_DVF_IGNOREHIDDEN TRUE
+#define DEF_DVF_DIVIDERPOS 240
+
+
+// configs
+#define GLOBAL g_config
+#define VIEW g_view_metaconf
+
+// readers
+#define READ_LPTSTR(config) (config)->ReadCbStringEx
+#define READ_QUOTED_LPTSTR(config) (config)->ReadCbQuotedString
+#define GET_INT(config) (config)->ReadIntEx
+#define GET_BOOL(config) (config)->ReadBoolEx
+
+// writers
+#define WRITE_LPCTSTR(config) (config)->WriteStringEx
+#define WRITE_QUOTED_LPCTSTR(config) (config)->WriteQuotedString
+#define WRITE_INT(config) (config)->WriteIntEx
+#define WRITE_BOOL(config) (config)->WriteBoolEx
+
+
+#define CHECK_EXREAD(type, config, section, field, buffer, cb) case field##: READ_##type##(config)(((##type##)buffer), cb, section, KEY_##field, DEF_##field); return S_OK;
+#define CHECK_EXREAD_QUOTED(type, config, section, field, buffer, cb) case field##: READ_QUOTED_##type##(config)(((##type##)buffer), cb, section, KEY_##field, DEF_##field); return S_OK;
+#define CHECK_EXGET(type, config, section, field, result) case field##: *((##type##*)result) = GET_##type##(config)(section, KEY_##field, DEF_##field); return S_OK;
+#define CHECK_EXWRITE(type, config, section, field, value) case field##: WRITE_##type##(config)(section, KEY_##field, value); return S_OK;
+#define CHECK_EXWRITE_QUOTED(type, config, section, field, value) case field##: WRITE_QUOTED_##type##(config)(section, KEY_##field, value); return S_OK;
+
+#define CHECK_DEFAULT(type, field, result) case field##: *((##type##*)result) = DEF_##field; return S_OK;
+#define CHECK_READ(type, config, field, buffer, cb) CHECK_EXREAD(type, config, SECTION_DEFAULT, field, buffer, cb)
+#define CHECK_READ_QUOTED(type, config, field, buffer, cb) CHECK_EXREAD_QUOTED(type, config, SECTION_DEFAULT, field, buffer, cb)
+#define CHECK_GET(type, config, field, result) CHECK_EXGET(type, config, SECTION_DEFAULT, field, result)
+#define CHECK_WRITE(type, config, field, value) CHECK_EXWRITE(type, config, SECTION_DEFAULT, field, value)
+#define CHECK_WRITE_QUOTED(type, config, field, value) CHECK_EXWRITE_QUOTED(type, config, SECTION_DEFAULT, field, value)
+
+#define CHECK_EXREAD_VIEW(type, section, field, buffer, cb) CHECK_EXREAD(type, VIEW, section, field, buffer, cb)
+#define CHECK_EXGET_VIEW(type, section, field, result) CHECK_EXGET(type, VIEW, section, field, result)
+#define CHECK_EXWRITE_VIEW(type, section, field, value) CHECK_EXWRITE(type, VIEW, section, field, value)
+
+#define CHECK_READ_GLOBAL(type, field, buffer, cb) CHECK_READ(type, GLOBAL, field, buffer, cb)
+#define CHECK_READ_QUOTED_GLOBAL(type, field, buffer, cb) CHECK_READ_QUOTED(type, GLOBAL, field, buffer, cb)
+#define CHECK_GET_GLOBAL(type, field, result) CHECK_GET(type, GLOBAL, field, result)
+#define CHECK_READ_VIEW(type, field, buffer, cb) CHECK_READ(type, VIEW, field, buffer, cb)
+#define CHECK_GET_VIEW(type, field, result) CHECK_GET(type, VIEW, field, result)
+#define CHECK_WRITE_GLOBAL(type, field, value) CHECK_WRITE(type, GLOBAL, field, value)
+#define CHECK_WRITE_QUOTED_GLOBAL(type, field, value) CHECK_WRITE_QUOTED(type, GLOBAL, field, value)
+#define CHECK_WRITE_VIEW(type, field, value) CHECK_WRITE(type, VIEW, field, value)
+
+#define STR_CHECK_DEFAULT(field, result) CHECK_DEFAULT(LPCTSTR, field, result)
+#define INT_CHECK_DEFAULT(field, result) CHECK_DEFAULT(INT, field, result)
+#define BOOL_CHECK_DEFAULT(field, result) CHECK_DEFAULT(BOOL, field, result)
+
+#define STR_CHECK_READ_GLOBAL(field, buffer, cb) CHECK_READ_GLOBAL(LPTSTR, field, buffer, cb)
+#define STR_CHECK_READ_QUOTED_GLOBAL(field, buffer, cb) CHECK_READ_QUOTED_GLOBAL(LPTSTR, field, buffer, cb)
+#define INT_CHECK_GET_GLOBAL(field, result) CHECK_GET_GLOBAL(INT, field, result)
+#define BOOL_CHECK_GET_GLOBAL(field, result) CHECK_GET_GLOBAL(BOOL, field, result)
+
+#define STR_CHECK_WRITE_GLOBAL(field, value) CHECK_WRITE_GLOBAL(LPCTSTR, field, value)
+#define STR_CHECK_WRITE_QUOTED_GLOBAL(field, value) CHECK_WRITE_QUOTED_GLOBAL(LPCTSTR, field, value)
+#define INT_CHECK_WRITE_GLOBAL(field, value) CHECK_WRITE_GLOBAL(INT, field, value)
+#define BOOL_CHECK_WRITE_GLOBAL(field, value) CHECK_WRITE_GLOBAL(BOOL, field, value)
+
+#define STR_CHECK_EXREAD_VIEW(section, field, buffer, cb) CHECK_EXREAD_VIEW(LPTSTR, section, field, buffer, cb)
+#define INT_CHECK_EXGET_VIEW(section, field, result) CHECK_EXGET_VIEW(INT, section, field, result)
+#define BOOL_CHECK_EXGET_VIEW(section, field, result) CHECK_EXGET_VIEW(BOOL, section, field, result)
+
+#define STR_CHECK_EXWRITE_VIEW(section, field, value) CHECK_EXWRITE_VIEW(LPCTSTR, section, field, value)
+#define INT_CHECK_EXWRITE_VIEW(section, field, value) CHECK_EXWRITE_VIEW(INT, section, field, value)
+#define BOOL_CHECK_EXWRITE_VIEW(section, field, value) CHECK_EXWRITE_VIEW(BOOL, section, field, value)
+
+#define STR_CHECK_READ_VIEW(field, buffer, cb) CHECK_READ_VIEW(LPTSTR, field, buffer, cb)
+#define INT_CHECK_GET_VIEW(field, result) CHECK_GET_VIEW(INT, field, result)
+#define BOOL_CHECK_GET_VIEW(field, result) CHECK_GET_VIEW(BOOL, field, result)
+
+#define STR_CHECK_WRITE_VIEW(field, value) CHECK_WRITE_VIEW(LPCTSTR, field, value)
+#define INT_CHECK_WRITE_VIEW(field, value) CHECK_WRITE_VIEW(INT, field, value)
+#define BOOL_CHECK_WRITE_VIEW(field, value) CHECK_WRITE_VIEW(BOOL, field, value)
+
+static LPCWSTR GetDefaultExtractPath()
+{
+ static TCHAR m_def_extract_path[MAX_PATH] = { TEXT('\0'), };
+
+ if (L'\0' == m_def_extract_path[0])
+ {
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, m_def_extract_path)))
+ {
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, m_def_extract_path)))
+ {
+ // and if that all fails then do a reasonable default
+ StringCchCopyW(m_def_extract_path, ARRAYSIZE(m_def_extract_path), L"C:\\My Music");
+ }
+ // if there's no valid My Music folder (typically win2k) then default to %my_documents%\my music
+ else PathCombineW(m_def_extract_path, m_def_extract_path, L"My Music");
+ }
+ }
+ return m_def_extract_path;
+}
+
+HRESULT Settings_GetDefault(INT categoryId, INT fieldId, VOID *pValue)
+{
+ switch(categoryId)
+ {
+ case C_EXTRACT:
+ switch(fieldId)
+ {
+ STR_CHECK_DEFAULT(EF_PATH, pValue);
+ STR_CHECK_DEFAULT(EF_TITLEFMT, pValue);
+ STR_CHECK_DEFAULT(EF_PLAYLISTFMT, pValue);
+ STR_CHECK_DEFAULT(EF_COMMENTTEXT, pValue);
+ BOOL_CHECK_DEFAULT(EF_UPPEREXTENSION, pValue);
+ BOOL_CHECK_DEFAULT(EF_ADDMETADATA, pValue);
+ BOOL_CHECK_DEFAULT(EF_CALCULATERG, pValue);
+ BOOL_CHECK_DEFAULT(EF_USETOTALTRACKS, pValue);
+ BOOL_CHECK_DEFAULT(EF_ADDTOMLDB, pValue);
+ INT_CHECK_DEFAULT(EF_TRACKOFFSET, pValue);
+ BOOL_CHECK_DEFAULT(EF_CREATEM3U, pValue);
+ BOOL_CHECK_DEFAULT(EF_CREATEPLS, pValue);
+ BOOL_CHECK_DEFAULT(EF_CREATEMLPL, pValue);
+ BOOL_CHECK_DEFAULT(EF_USEM3UEXT, pValue);
+ INT_CHECK_DEFAULT(EF_FOURCC, pValue);
+ }
+ break;
+ case C_COPY:
+ switch(fieldId)
+ {
+ STR_CHECK_DEFAULT(CF_PATH, pValue);
+ STR_CHECK_DEFAULT(CF_TITLEFMT, pValue);
+ BOOL_CHECK_DEFAULT(CF_USETITLEFMT, pValue);
+ BOOL_CHECK_DEFAULT(CF_ADDTOMLDB, pValue);
+ BOOL_CHECK_DEFAULT(CF_CALCULATERG, pValue);
+ INT_CHECK_DEFAULT(CF_GAINMODE, pValue);
+ }
+ break;
+ case C_GLOBAL:
+ switch(fieldId)
+ {
+ BOOL_CHECK_DEFAULT(GF_SHOWINFO, pValue);
+ BOOL_CHECK_DEFAULT(GF_SHOWPARENT, pValue);
+ BOOL_CHECK_DEFAULT(GF_ENQUEUEBYDEFAULT, pValue);
+ }
+ break;
+ case C_DATAVIEW:
+ switch(fieldId)
+ {
+ STR_CHECK_DEFAULT(DVF_COLUMNLIST, pValue);
+ STR_CHECK_DEFAULT(DVF_LASTFOLDER, pValue);
+ INT_CHECK_DEFAULT(DVF_ORDERBY, pValue);
+ BOOL_CHECK_DEFAULT(DVF_ORDERASC, pValue);
+ INT_CHECK_DEFAULT(DVF_VIEWMODE, pValue);
+ BOOL_CHECK_DEFAULT(DVF_SHOWAUDIO, pValue);
+ BOOL_CHECK_DEFAULT(DVF_SHOWVIDEO, pValue);
+ BOOL_CHECK_DEFAULT(DVF_SHOWPLAYLIST, pValue);
+ BOOL_CHECK_DEFAULT(DVF_SHOWUNKNOWN, pValue);
+ BOOL_CHECK_DEFAULT(DVF_HIDEEXTENSION, pValue);
+ BOOL_CHECK_DEFAULT(DVF_IGNOREHIDDEN, pValue);
+ INT_CHECK_DEFAULT(DVF_DIVIDERPOS, pValue);
+ }
+ break;
+ }
+ return E_INVALIDARG;
+}
+
+
+HRESULT Settings_ReadValue(INT categoryId, INT fieldId, VOID *pValue, INT cbSize)
+{
+ LPCTSTR pszSection;
+ switch(categoryId)
+ {
+ case C_EXTRACT:
+ switch(fieldId)
+ {
+ STR_CHECK_READ_GLOBAL(EF_PATH, pValue, cbSize);
+ STR_CHECK_READ_QUOTED_GLOBAL(EF_TITLEFMT, pValue, cbSize);
+ STR_CHECK_READ_QUOTED_GLOBAL(EF_PLAYLISTFMT, pValue, cbSize);
+ STR_CHECK_READ_QUOTED_GLOBAL(EF_COMMENTTEXT, pValue, cbSize);
+ BOOL_CHECK_GET_GLOBAL(EF_UPPEREXTENSION, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_ADDMETADATA, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_CALCULATERG, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_USETOTALTRACKS, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_ADDTOMLDB, pValue);
+ INT_CHECK_GET_GLOBAL(EF_TRACKOFFSET, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_CREATEM3U, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_CREATEPLS, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_CREATEMLPL, pValue);
+ BOOL_CHECK_GET_GLOBAL(EF_USEM3UEXT, pValue);
+ INT_CHECK_GET_GLOBAL(EF_FOURCC, pValue);
+ }
+ break;
+ case C_COPY:
+ switch(fieldId)
+ {
+ STR_CHECK_READ_GLOBAL(CF_PATH, pValue, cbSize);
+ STR_CHECK_READ_QUOTED_GLOBAL(CF_TITLEFMT, pValue, cbSize);
+ BOOL_CHECK_GET_GLOBAL(CF_USETITLEFMT, pValue);
+ BOOL_CHECK_GET_GLOBAL(CF_ADDTOMLDB, pValue);
+ BOOL_CHECK_GET_GLOBAL(CF_CALCULATERG, pValue);
+ INT_CHECK_GET_GLOBAL(CF_GAINMODE, pValue);
+ }
+ break;
+ case C_GLOBAL:
+ switch(fieldId)
+ {
+ BOOL_CHECK_GET_VIEW(GF_SHOWINFO, pValue);
+ BOOL_CHECK_GET_VIEW(GF_SHOWPARENT, pValue);
+ BOOL_CHECK_GET_GLOBAL(GF_ENQUEUEBYDEFAULT, pValue);
+ }
+ break;
+ case C_DATAVIEW:
+ pszSection = SECTION_DATAVIEW;
+ switch(fieldId)
+ {
+ STR_CHECK_EXREAD_VIEW(pszSection, DVF_COLUMNLIST, pValue, cbSize);
+ STR_CHECK_EXREAD_VIEW(pszSection, DVF_LASTFOLDER, pValue, cbSize);
+ INT_CHECK_EXGET_VIEW(pszSection, DVF_ORDERBY, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_ORDERASC, pValue);
+ INT_CHECK_EXGET_VIEW(pszSection, DVF_VIEWMODE, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_SHOWAUDIO, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_SHOWVIDEO, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_SHOWPLAYLIST, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_SHOWUNKNOWN, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_HIDEEXTENSION, pValue);
+ BOOL_CHECK_EXGET_VIEW(pszSection, DVF_IGNOREHIDDEN, pValue);
+ INT_CHECK_EXGET_VIEW(pszSection, DVF_DIVIDERPOS, pValue);
+ }
+ break;
+ }
+ return E_INVALIDARG;
+}
+
+HRESULT Settings_SetString(INT categoryId, INT fieldId, LPCWSTR pszBuffer)
+{
+ LPCTSTR pszSection;
+ switch(categoryId)
+ {
+ case C_EXTRACT:
+ switch(fieldId)
+ {
+ STR_CHECK_WRITE_QUOTED_GLOBAL(EF_PATH, pszBuffer);
+ STR_CHECK_WRITE_QUOTED_GLOBAL(EF_TITLEFMT, pszBuffer);
+ STR_CHECK_WRITE_QUOTED_GLOBAL(EF_PLAYLISTFMT, pszBuffer);
+ STR_CHECK_WRITE_QUOTED_GLOBAL(EF_COMMENTTEXT, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_UPPEREXTENSION, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_ADDMETADATA, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_CALCULATERG, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_USETOTALTRACKS, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_ADDTOMLDB, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_TRACKOFFSET, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_CREATEM3U, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_CREATEPLS, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_CREATEMLPL, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_USEM3UEXT, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(EF_FOURCC, pszBuffer);
+ }
+ break;
+ case C_COPY:
+ switch(fieldId)
+ {
+ STR_CHECK_WRITE_QUOTED_GLOBAL(CF_PATH, pszBuffer);
+ STR_CHECK_WRITE_QUOTED_GLOBAL(CF_TITLEFMT, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(CF_USETITLEFMT, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(CF_ADDTOMLDB, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(CF_CALCULATERG, pszBuffer);
+ STR_CHECK_WRITE_GLOBAL(CF_GAINMODE, pszBuffer);
+ }
+ break;
+ case C_GLOBAL:
+ switch(fieldId)
+ {
+ STR_CHECK_WRITE_VIEW(GF_SHOWINFO, pszBuffer);
+ STR_CHECK_WRITE_VIEW(GF_SHOWPARENT, pszBuffer);
+ STR_CHECK_WRITE_VIEW(GF_ENQUEUEBYDEFAULT, pszBuffer);
+ }
+ break;
+ case C_DATAVIEW:
+ pszSection = SECTION_DATAVIEW;
+ switch(fieldId)
+ {
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_COLUMNLIST, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_LASTFOLDER, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_ORDERBY, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_ORDERASC, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_VIEWMODE, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_SHOWAUDIO, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_SHOWVIDEO, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_SHOWPLAYLIST, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_SHOWUNKNOWN, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_HIDEEXTENSION, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_IGNOREHIDDEN, pszBuffer);
+ STR_CHECK_EXWRITE_VIEW(pszSection, DVF_DIVIDERPOS, pszBuffer);
+ }
+ break;
+ }
+ return E_INVALIDARG;
+}
+
+
+HRESULT Settings_GetInt(INT categoryId, INT fieldId, INT *pnVal)
+{
+ return Settings_ReadValue(categoryId, fieldId, pnVal, sizeof(INT));
+}
+HRESULT Settings_GetBool(INT categoryId, INT fieldId, BOOL *pbVal)
+{
+ return Settings_ReadValue(categoryId, fieldId, pbVal, sizeof(BOOL));
+}
+
+HRESULT Settings_ReadString(INT categoryId, INT fieldId, LPTSTR pszBuffer, INT cchBufferMax)
+{
+ return Settings_ReadValue(categoryId, fieldId, pszBuffer, cchBufferMax * sizeof(TCHAR));
+}
+
+HRESULT Settings_SetWindowText(INT categoryId, INT fieldId, HWND hwnd)
+{
+ TCHAR szBuffer[8192] = {0};
+ HRESULT hr = Settings_ReadValue(categoryId, fieldId, szBuffer, sizeof(szBuffer));
+ if (S_OK == hr) SetWindowText(hwnd, szBuffer);
+ return hr;
+}
+
+HRESULT Settings_SetWindowInt(INT categoryId, INT fieldId, HWND hwnd)
+{
+ INT nVal;
+ HRESULT hr = Settings_ReadValue(categoryId, fieldId, &nVal, sizeof(nVal));
+
+ if (S_OK == hr)
+ {
+ TCHAR szBuffer[256] = {0};
+ hr = StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), TEXT("%d"), nVal);
+ if (S_OK == hr) SetWindowText(hwnd, szBuffer);
+ }
+ return hr;
+}
+HRESULT Settings_SetDlgItemText(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ HWND hctrl = GetDlgItem(hdlg, nItemId);
+ return (hctrl) ? Settings_SetWindowText(categoryId, fieldId, hctrl) : E_HANDLE;
+}
+
+HRESULT Settings_SetDlgItemInt(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ HWND hctrl = GetDlgItem(hdlg, nItemId);
+ return (hctrl) ? Settings_SetWindowInt(categoryId, fieldId, hctrl) : E_HANDLE;
+}
+
+HRESULT Settings_SetCheckBox(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ INT nVal;
+ HRESULT hr = Settings_GetInt(categoryId, fieldId, &nVal);
+ if (S_OK == hr) hr = (CheckDlgButton(hdlg, nItemId, (0 != nVal) ? BST_CHECKED : BST_UNCHECKED)) ? S_OK : E_HANDLE;
+ return hr;
+}
+
+HRESULT Settings_SetInt(INT categoryId, INT fieldId, INT nValue)
+{
+ TCHAR szBuffer[64] = {0};
+ HRESULT hr = StringCchPrintfW(szBuffer, ARRAYSIZE(szBuffer), TEXT("%d"), nValue);
+ if (S_OK == hr) hr = Settings_SetString(categoryId, fieldId, szBuffer);
+ return hr;
+}
+HRESULT Settings_SetBool(INT categoryId, INT fieldId, BOOL bValue)
+{
+ return Settings_SetInt(categoryId, fieldId, (0 != bValue));
+}
+HRESULT Settings_FromWindowText(INT categoryId, INT fieldId, HWND hwnd)
+{
+ if (!hwnd) return E_HANDLE;
+ INT l = GetWindowTextLength(hwnd);
+ if (l < 1024)
+ {
+ TCHAR szBuffer[1024] = {0};
+ GetWindowText(hwnd, szBuffer, ARRAYSIZE(szBuffer));
+ return Settings_SetString(categoryId, fieldId, szBuffer);
+ }
+ else
+ {
+ LPTSTR pszBuffer = (LPTSTR)calloc((l + 1), sizeof(TCHAR));
+ if (!pszBuffer) return E_OUTOFMEMORY;
+ GetWindowText(hwnd, pszBuffer, sizeof(TCHAR)*(l + 1));
+ HRESULT hr = Settings_SetString(categoryId, fieldId, pszBuffer);
+ free(pszBuffer);
+ return hr;
+ }
+}
+HRESULT Settings_FromDlgItemText(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ HWND hctrl = GetDlgItem(hdlg, nItemId);
+ return (hctrl) ? Settings_FromWindowText(categoryId, fieldId, hctrl) : E_HANDLE;
+}
+
+HRESULT Settings_FromCheckBox(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ UINT v = IsDlgButtonChecked(hdlg, nItemId);
+ return Settings_SetInt(categoryId, fieldId, v);
+}
+
+typedef struct _FBROWSER
+{
+ LPCWSTR pszSelection;
+} FBROWSER;
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM pData)
+{
+ FBROWSER *pfb = (FBROWSER*)pData;
+ switch(uMsg)
+ {
+ case BFFM_INITIALIZED:
+ if (pfb && pfb->pszSelection) SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)pfb->pszSelection);
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ break;
+ }
+ return 0;
+}
+
+HRESULT Settings_BrowseForFolder(INT categoryId, INT fieldId, HWND hDlg, INT nEditId)
+{
+ BROWSEINFO bi = {0};
+ FBROWSER fb;
+ wchar_t szPath[MAX_PATH] = {0};
+
+ HRESULT hr = Settings_ReadString(categoryId,fieldId, szPath, ARRAYSIZE(szPath));
+ if (S_OK != hr) return hr;
+
+ fb.pszSelection = szPath;
+
+ bi.hwndOwner = hDlg;
+ bi.pidlRoot = NULL;
+ bi.pszDisplayName = szPath;
+ bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_A_FOLDER);
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_RETURNFSANCESTORS | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ bi.lParam = (LPARAM)&fb;
+ HRESULT hrCom = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+ if (SUCCEEDED(hrCom))
+ {
+ ITEMIDLIST *idlist = SHBrowseForFolder(&bi);
+ if (idlist)
+ {
+ if (SHGetPathFromIDList(idlist, szPath))
+ {
+ hr = Settings_SetString(categoryId,fieldId, szPath);
+ if (S_OK == hr) SetDlgItemText(hDlg, nEditId, szPath);
+ }
+ else hr = S_FALSE;
+ CoTaskMemFree(idlist);
+ }
+ }
+ else hr = hrCom;
+ if (S_OK == hrCom) CoUninitialize();
+ return hr;
+}
+
+HRESULT Settings_SetDirectoryCtrl(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ TCHAR szBuffer[MAX_PATH] = {0};
+ HRESULT hr;
+ hr = Settings_ReadString(categoryId, fieldId, szBuffer, ARRAYSIZE(szBuffer));
+ if (S_OK != hr) *szBuffer = TEXT('\0');
+ else CleanupDirectoryString(szBuffer);
+ SetDlgItemText(hdlg, nItemId, szBuffer);
+ return hr;
+}
+HRESULT Settings_FromDirectoryCtrl(INT categoryId, INT fieldId, HWND hdlg, INT nItemId)
+{
+ TCHAR szBuffer[MAX_PATH] = {0};
+ GetDlgItemText(hdlg, nItemId, szBuffer, ARRAYSIZE(szBuffer));
+ CleanupDirectoryString(szBuffer);
+ return Settings_SetString(categoryId, fieldId, szBuffer);
+}
+
+
diff --git a/Src/Plugins/Library/ml_disc/settings.h b/Src/Plugins/Library/ml_disc/settings.h
new file mode 100644
index 00000000..86aafa7a
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/settings.h
@@ -0,0 +1,108 @@
+#ifndef NULLOSFT_MEDIALIBRARY_MLDISC_SETTINGS_HEADER
+#define NULLOSFT_MEDIALIBRARY_MLDISC_SETTINGS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ // Groups
+enum
+{
+ C_EXTRACT = 0,
+ C_COPY,
+ C_GLOBAL,
+ C_DATAVIEW,
+};
+
+// Extract Group Fields
+enum
+{
+ EF_PATH = 0,
+ EF_TITLEFMT,
+ EF_PLAYLISTFMT,
+ EF_UPPEREXTENSION,
+ EF_ADDMETADATA,
+ EF_CALCULATERG,
+ EF_USETOTALTRACKS,
+ EF_ADDTOMLDB,
+ EF_TRACKOFFSET,
+ EF_COMMENTTEXT,
+ EF_CREATEM3U,
+ EF_CREATEPLS,
+ EF_CREATEMLPL,
+ EF_USEM3UEXT,
+ EF_FOURCC,
+};
+
+// Copy Gorup fields
+enum
+{
+ CF_PATH = 0,
+ CF_USETITLEFMT,
+ CF_TITLEFMT,
+ CF_ADDTOMLDB,
+ CF_CALCULATERG,
+ CF_GAINMODE,
+};
+
+// Global group
+enum
+{
+ GF_SHOWINFO = 0,
+ GF_SHOWPARENT,
+ GF_ENQUEUEBYDEFAULT,
+};
+// Data View flags
+enum
+{
+ DVF_COLUMNLIST = 0,
+ DVF_ORDERBY,
+ DVF_ORDERASC,
+ DVF_VIEWMODE,
+ DVF_SHOWAUDIO,
+ DVF_SHOWVIDEO,
+ DVF_SHOWPLAYLIST,
+ DVF_SHOWUNKNOWN,
+ DVF_HIDEEXTENSION,
+ DVF_IGNOREHIDDEN,
+ DVF_LASTFOLDER,
+ DVF_DIVIDERPOS,
+};
+
+HRESULT Settings_ReadValue(INT categoryId, INT fieldId, VOID *pValue, INT cbSize);
+HRESULT Settings_GetDefault(INT categoryId, INT fieldId, VOID *pValue);
+HRESULT Settings_ReadString(INT categoryId, INT fieldId, LPTSTR pszBuffer, INT cchBufferMax);
+HRESULT Settings_GetInt(INT categoryId, INT fieldId, INT *pnVal);
+HRESULT Settings_GetBool(INT categoryId, INT fieldId, BOOL *pbVal);
+
+HRESULT Settings_SetWindowText(INT categoryId, INT fieldId, HWND hwnd);
+HRESULT Settings_SetWindowInt(INT categoryId, INT fieldId, HWND hwnd);
+HRESULT Settings_SetDlgItemText(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+HRESULT Settings_SetDlgItemInt(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+HRESULT Settings_SetCheckBox(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+
+HRESULT Settings_SetString(INT categoryId, INT fieldId, LPCWSTR pszBuffer);
+HRESULT Settings_SetInt(INT categoryId, INT fieldId, INT nValue);
+HRESULT Settings_SetBool(INT categoryId, INT fieldId, BOOL bValue);
+HRESULT Settings_FromWindowText(INT categoryId, INT fieldId, HWND hwnd);
+HRESULT Settings_FromDlgItemText(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+HRESULT Settings_FromCheckBox(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+HRESULT Settings_BrowseForFolder(INT categoryId, INT fieldId, HWND hDlg, INT nEditId);
+
+HRESULT Settings_SetDirectoryCtrl(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+HRESULT Settings_FromDirectoryCtrl(INT categoryId, INT fieldId, HWND hdlg, INT nItemId);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#endif // NULLOSFT_MEDIALIBRARY_MLDISC_SETTINGS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/spti.cpp b/Src/Plugins/Library/ml_disc/spti.cpp
new file mode 100644
index 00000000..8a34320a
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/spti.cpp
@@ -0,0 +1,239 @@
+#include "./spti.h"
+#include <ntddscsi.h>
+
+
+#define CDB6GENERIC_LENGTH 6
+#define CDB10GENERIC_LENGTH 10
+
+#define SETBITON 1
+#define SETBITOFF 0
+
+//
+// Mode Sense/Select page constants.
+//
+
+#define MODE_PAGE_ERROR_RECOVERY 0x01
+#define MODE_PAGE_DISCONNECT 0x02
+#define MODE_PAGE_FORMAT_DEVICE 0x03
+#define MODE_PAGE_RIGID_GEOMETRY 0x04
+#define MODE_PAGE_FLEXIBILE 0x05
+#define MODE_PAGE_VERIFY_ERROR 0x07
+#define MODE_PAGE_CACHING 0x08
+#define MODE_PAGE_PERIPHERAL 0x09
+#define MODE_PAGE_CONTROL 0x0A
+#define MODE_PAGE_MEDIUM_TYPES 0x0B
+#define MODE_PAGE_NOTCH_PARTITION 0x0C
+#define MODE_SENSE_RETURN_ALL 0x3f
+#define MODE_SENSE_CURRENT_VALUES 0x00
+#define MODE_SENSE_CHANGEABLE_VALUES 0x40
+#define MODE_SENSE_DEFAULT_VAULES 0x80
+#define MODE_SENSE_SAVED_VALUES 0xc0
+#define MODE_PAGE_DEVICE_CONFIG 0x10
+#define MODE_PAGE_MEDIUM_PARTITION 0x11
+#define MODE_PAGE_DATA_COMPRESS 0x0f
+#define MODE_PAGE_CAPABILITIES 0x2A
+
+//
+// SCSI CDB operation codes
+//
+
+#define SCSIOP_TEST_UNIT_READY 0x00
+#define SCSIOP_REZERO_UNIT 0x01
+#define SCSIOP_REWIND 0x01
+#define SCSIOP_REQUEST_BLOCK_ADDR 0x02
+#define SCSIOP_REQUEST_SENSE 0x03
+#define SCSIOP_FORMAT_UNIT 0x04
+#define SCSIOP_READ_BLOCK_LIMITS 0x05
+#define SCSIOP_REASSIGN_BLOCKS 0x07
+#define SCSIOP_READ6 0x08
+#define SCSIOP_RECEIVE 0x08
+#define SCSIOP_WRITE6 0x0A
+#define SCSIOP_PRINT 0x0A
+#define SCSIOP_SEND 0x0A
+#define SCSIOP_SEEK6 0x0B
+#define SCSIOP_TRACK_SELECT 0x0B
+#define SCSIOP_SLEW_PRINT 0x0B
+#define SCSIOP_SEEK_BLOCK 0x0C
+#define SCSIOP_PARTITION 0x0D
+#define SCSIOP_READ_REVERSE 0x0F
+#define SCSIOP_WRITE_FILEMARKS 0x10
+#define SCSIOP_FLUSH_BUFFER 0x10
+#define SCSIOP_SPACE 0x11
+#define SCSIOP_INQUIRY 0x12
+#define SCSIOP_VERIFY6 0x13
+#define SCSIOP_RECOVER_BUF_DATA 0x14
+#define SCSIOP_MODE_SELECT 0x15
+#define SCSIOP_RESERVE_UNIT 0x16
+#define SCSIOP_RELEASE_UNIT 0x17
+#define SCSIOP_COPY 0x18
+#define SCSIOP_ERASE 0x19
+#define SCSIOP_MODE_SENSE 0x1A
+#define SCSIOP_START_STOP_UNIT 0x1B
+#define SCSIOP_STOP_PRINT 0x1B
+#define SCSIOP_LOAD_UNLOAD 0x1B
+#define SCSIOP_RECEIVE_DIAGNOSTIC 0x1C
+#define SCSIOP_SEND_DIAGNOSTIC 0x1D
+#define SCSIOP_MEDIUM_REMOVAL 0x1E
+#define SCSIOP_READ_CAPACITY 0x25
+#define SCSIOP_READ 0x28
+#define SCSIOP_WRITE 0x2A
+#define SCSIOP_SEEK 0x2B
+#define SCSIOP_LOCATE 0x2B
+#define SCSIOP_WRITE_VERIFY 0x2E
+#define SCSIOP_VERIFY 0x2F
+#define SCSIOP_SEARCH_DATA_HIGH 0x30
+#define SCSIOP_SEARCH_DATA_EQUAL 0x31
+#define SCSIOP_SEARCH_DATA_LOW 0x32
+#define SCSIOP_SET_LIMITS 0x33
+#define SCSIOP_READ_POSITION 0x34
+#define SCSIOP_SYNCHRONIZE_CACHE 0x35
+#define SCSIOP_COMPARE 0x39
+#define SCSIOP_COPY_COMPARE 0x3A
+#define SCSIOP_WRITE_DATA_BUFF 0x3B
+#define SCSIOP_READ_DATA_BUFF 0x3C
+#define SCSIOP_CHANGE_DEFINITION 0x40
+#define SCSIOP_READ_SUB_CHANNEL 0x42
+#define SCSIOP_READ_TOC 0x43
+#define SCSIOP_READ_HEADER 0x44
+#define SCSIOP_PLAY_AUDIO 0x45
+#define SCSIOP_PLAY_AUDIO_MSF 0x47
+#define SCSIOP_PLAY_TRACK_INDEX 0x48
+#define SCSIOP_PLAY_TRACK_RELATIVE 0x49
+#define SCSIOP_PAUSE_RESUME 0x4B
+#define SCSIOP_LOG_SELECT 0x4C
+#define SCSIOP_LOG_SENSE 0x4D
+#define SCSIOP_READ_DISC_INFORMATION 0x51
+
+typedef struct _SCSI_PASS_THROUGH_WITH_BUFFERS
+{
+ SCSI_PASS_THROUGH spt;
+ DWORD filler;
+ UCHAR ucSenseBuf[24];
+ UCHAR ucDataBuf[256];
+}SCSI_PASS_THROUGH_WITH_BUFFERS;
+
+#pragma pack(1)
+
+typedef struct _CDB_START_STOP_UNIT
+{
+ UCHAR OperationCode; // 0x1B - SCSIOP_START_STOP_UNIT
+ UCHAR Immediate : 1;
+ UCHAR Reserved1 : 4;
+ UCHAR Lun : 3;
+ UCHAR Reserved2[2];
+ UCHAR Start : 1;
+ UCHAR LoadEject : 1;
+ UCHAR Reserved3 : 2;
+ UCHAR PowerCondition : 4;
+ UCHAR Control;
+} CDB_START_STOP_UNIT;
+
+
+typedef struct _READ_DISC_INFORMATION
+{
+ UCHAR OperationCode; // 0x51 - SCSIOP_READ_DISC_INFORMATION
+ UCHAR Reserved1 : 5;
+ UCHAR Lun : 3;
+ UCHAR Reserved2[5];
+ UCHAR AllocationLength[2];
+ UCHAR Control;
+} READ_DISC_INFORMATION;
+
+#pragma pack()
+
+
+BOOL SPTI_TestUnitReady(HANDLE hDevice, BYTE *pbSC, BYTE *pbASC, BYTE *pbASCQ, INT timeOutSec)
+{
+ INT length;
+ DWORD returned;
+ SCSI_PASS_THROUGH_WITH_BUFFERS sptwb;
+
+ if (INVALID_HANDLE_VALUE == hDevice) return FALSE;
+
+ ZeroMemory(&sptwb, sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS));
+ sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
+ sptwb.spt.CdbLength = CDB6GENERIC_LENGTH;
+ sptwb.spt.SenseInfoLength = 24;
+ sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sptwb.spt.DataTransferLength = 0;
+ sptwb.spt.TimeOutValue = timeOutSec;
+ sptwb.spt.DataBufferOffset = ((DWORD)(DWORD_PTR)&sptwb.ucDataBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ sptwb.spt.SenseInfoOffset = ((DWORD)(DWORD_PTR)&sptwb.ucSenseBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ sptwb.spt.Cdb[0] = SCSIOP_TEST_UNIT_READY;
+ length = ((DWORD)(DWORD_PTR)&sptwb.ucDataBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, &sptwb, sizeof(SCSI_PASS_THROUGH), &sptwb, length, &returned, FALSE);
+
+ if (pbSC) *pbSC = sptwb.ucSenseBuf[2];
+ if (pbASC) *pbASC = sptwb.ucSenseBuf[12];
+ if (pbASCQ) *pbASCQ = sptwb.ucSenseBuf[13];
+
+ return TRUE;
+}
+BOOL SPTI_StartStopUnit(HANDLE hDevice, BOOL bImmediate, BOOL bLoadEject, BOOL bStart, INT timeOutSec, SENSEINFO *pSense)
+{
+ INT length;
+ BOOL status;
+ DWORD returned;
+ CDB_START_STOP_UNIT cmd;
+ SCSI_PASS_THROUGH_WITH_BUFFERS sptwb;
+
+ UNREFERENCED_PARAMETER(pSense);
+
+ if (INVALID_HANDLE_VALUE == hDevice) return FALSE;
+
+ ZeroMemory(&sptwb, sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS));
+ ZeroMemory(&cmd, sizeof(CDB_START_STOP_UNIT));
+
+ sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
+ sptwb.spt.CdbLength = CDB6GENERIC_LENGTH;
+ sptwb.spt.SenseInfoLength = 24;
+ sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sptwb.spt.DataTransferLength = 0;
+ sptwb.spt.TimeOutValue = timeOutSec;
+ sptwb.spt.DataBufferOffset = ((DWORD)(DWORD_PTR)&sptwb.ucDataBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ sptwb.spt.SenseInfoOffset = ((DWORD)(DWORD_PTR)&sptwb.ucSenseBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ cmd.OperationCode = SCSIOP_START_STOP_UNIT;
+ cmd.Immediate = (bImmediate) ? 1 : 0;
+ cmd.LoadEject= (bLoadEject) ? 1 : 0;
+ cmd.Start = (bStart) ? 1 : 0;
+ CopyMemory(sptwb.spt.Cdb, &cmd, sizeof(CDB_START_STOP_UNIT));
+
+ length = ((DWORD)(DWORD_PTR)&sptwb.ucDataBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ status = DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, &sptwb, sizeof(SCSI_PASS_THROUGH), &sptwb, length, &returned, FALSE);
+
+ return status;
+}
+BOOL SPTI_GetCapabilities(HANDLE hDevice, DWORD *pCap)
+{
+ INT length = 0;
+ BOOL status;
+ DWORD returned = 0;
+ SCSI_PASS_THROUGH_WITH_BUFFERS sptwb;
+
+ if (INVALID_HANDLE_VALUE == hDevice || !pCap) return FALSE;
+
+ *pCap = 0;
+
+ ZeroMemory(&sptwb, sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS));
+ sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
+ sptwb.spt.CdbLength = CDB6GENERIC_LENGTH;
+ sptwb.spt.SenseInfoLength = 24;
+ sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sptwb.spt.DataTransferLength = 192;
+ sptwb.spt.TimeOutValue = 10; //2 sec
+ sptwb.spt.DataBufferOffset = ((DWORD)(DWORD_PTR)&sptwb.ucDataBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ sptwb.spt.SenseInfoOffset = ((DWORD)(DWORD_PTR)&sptwb.ucSenseBuf) - ((DWORD)(DWORD_PTR)&sptwb);
+ sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE;
+ sptwb.spt.Cdb[1] = 0x08; // target shall not return any block descriptors
+ sptwb.spt.Cdb[2] = MODE_PAGE_CAPABILITIES;
+ sptwb.spt.Cdb[4] = 192;
+
+ length = ((DWORD)(DWORD_PTR)&sptwb.ucDataBuf) - ((DWORD)(DWORD_PTR)&sptwb) + sptwb.spt.DataTransferLength;;
+
+ status = DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, &sptwb, sizeof(SCSI_PASS_THROUGH), &sptwb, length, &returned, FALSE);
+ if (!status) return FALSE;
+
+ *pCap = *((DWORD*)&sptwb.ucDataBuf[6]) ;
+
+ return TRUE;
+}
diff --git a/Src/Plugins/Library/ml_disc/spti.h b/Src/Plugins/Library/ml_disc/spti.h
new file mode 100644
index 00000000..8c14e8e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/spti.h
@@ -0,0 +1,35 @@
+#ifndef NULLOSFT_MLDISC_STPI_HEADER
+#define NULLOSFT_MLDISC_STPI_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+
+// 02:04:01 - LOGICAL UNIT IS IN PROCESS OF BECOMING READY
+// 02:3A:00 - MEDIUM NOT PRESENT
+// 02:3A:01 - MEDIUM NOT PRESENT - TRAY CLOSED
+// 02:3A:02 - MEDIUM NOT PRESENT - TRAY OPEN
+// 06:28:00 - MEDIUM NOT PRESENT - NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED
+
+typedef struct _SENSEINFO
+{
+ BYTE bSense;
+ BYTE bASC;
+ BYTE bASCQ;
+} SENSEINFO;
+BOOL SPTI_TestUnitReady(HANDLE hDevice, BYTE *pbSC, BYTE *pbASC, BYTE *pbASCQ, INT timeOutSec);
+BOOL SPTI_StartStopUnit(HANDLE hDevice, BOOL bImmediate, BOOL bLoadEject, BOOL bStart, INT timeOutSec, SENSEINFO *pSense);
+BOOL SPTI_GetCapabilities(HANDLE hDevice, DWORD *pCap);
+
+// links to read:
+// http://www.t10.org/ftp/t10/drafts/mmc3/mmc3r10g.pdf
+// http://www.reactos.org/generated/doxygen/da/db8/scsi_8h-source.html
+// http://www.vbarchiv.net/workshop/workshop78s4.html
+// http://files.codes-sources.com/fichier.aspx?id=26141&f=Graver.h
+// http://files.codes-sources.com/fichier.aspx?id=26141&f=Graver.cpp
+// http://www.hackingtruths.org/files/cd_cracking/SRC/etc/DDK.SPTI/spti.c
+
+#endif //NULLOSFT_MLDISC_STPI_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/version.rc2 b/Src/Plugins/Library/ml_disc/version.rc2
new file mode 100644
index 00000000..efe64048
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,0,0,1
+ 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", "2,0,0,1"
+ VALUE "InternalName", "Nullsoft Rip & Burn"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_disc.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_disc/view_cdrom.cpp b/Src/Plugins/Library/ml_disc/view_cdrom.cpp
new file mode 100644
index 00000000..a980aef0
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/view_cdrom.cpp
@@ -0,0 +1,1014 @@
+#include <shlwapi.h>
+
+#include "main.h"
+#include <windowsx.h>
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/ChildSizer.h"
+#include "../winamp/wa_ipc.h"
+#include "../Winamp/strutil.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/listview.h"
+#include <strsafe.h>
+
+#ifndef LVS_EX_DOUBLEBUFFER
+#define LVS_EX_DOUBLEBUFFER 0x00010000
+#endif
+
+static INT_PTR WINAPI DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+#define TIMER_NOTIFYINFO_ID 1985
+#define TIMER_NOTIFYINFO_DELAY 200
+
+HWND CreateCDViewWindow(HWND hwndParent, DM_NOTIFY_PARAM *phdr)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_CDROM, hwndParent, DlgProc, (LPARAM)phdr);
+}
+
+void TAG_FMT(void *f, void *ff, void *p, char *out, int out_len)
+{
+ waFormatTitle fmt;
+ fmt.out = out;
+ fmt.out_len = out_len;
+ fmt.p = p;
+ fmt.spec = 0;
+ *(void **)&fmt.TAGFUNC = f;
+ *(void **)&fmt.TAGFREEFUNC = ff;
+ *out = 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE);
+}
+
+
+static wchar_t m_artist[128], m_album[128], m_genre[128], m_year[32];
+static int m_start_immediate_extract = 0;
+static int m_atracks, m_dtracks;
+
+
+void CopyComment(wchar_t *&dest, wchar_t *comments)
+{
+ if (comments)
+ {
+ int numCarriageReturns = 0;
+ wchar_t *commentCursor = comments;
+ while (commentCursor && *commentCursor)
+ {
+ if (*commentCursor == '\r')
+ {
+ commentCursor++;
+ if (*commentCursor == '\n')
+ commentCursor++;
+ }
+ if (*commentCursor == '\n')
+ numCarriageReturns++;
+
+ commentCursor = CharNext(commentCursor);
+ }
+ size_t size = commentCursor - comments;
+ dest = (wchar_t *)calloc((size + numCarriageReturns + 1), sizeof(wchar_t));
+ wchar_t *destCursor = dest;
+ commentCursor = comments;
+ while (commentCursor && *commentCursor)
+ {
+ if (*commentCursor == '\r')
+ {
+ *destCursor++ = *commentCursor++;
+ if (*commentCursor == '\n')
+ *destCursor++ = *commentCursor++;
+ }
+ if (*commentCursor == '\n')
+ *destCursor++ = '\r';
+
+ wchar_t *next = CharNextW(commentCursor);
+ while (commentCursor != next)
+ {
+ *destCursor++ = *commentCursor++;
+ }
+ }
+ *destCursor = 0;
+ }
+ else
+ dest = 0;
+}
+
+static void extractFiles(HWND hwndDlg, CHAR cLetter, int all)
+{
+ HWND hwndList;
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ char cMode;
+ wchar_t title[32] = {0}, buf[512] = {0};
+ INT msgTextId, l, i, cnt = 0;
+ cdrip_params *p;
+ wchar_t info[65536] = {0};
+
+ LVITEMW lvitem = {0};
+
+ cMode = DriveManager_GetDriveMode(cLetter);
+
+ switch (cMode)
+ {
+ case DM_MODE_BURNING: msgTextId = IDS_ERROR_CD_RIP_IN_PROGRESS; break;
+ case DM_MODE_RIPPING: msgTextId = IDS_ERROR_CD_BURN_IN_PROGRESS; break;
+ default: msgTextId = 0; break;
+ }
+ if (msgTextId)
+ {
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(msgTextId), WASABI_API_LNGSTRINGW_BUF(IDS_CD_RIP,title,32), 0);
+ return ;
+ }
+
+ if (m_dtracks && !m_atracks)
+ {
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_ERROR_CANNOT_EXTRACT_DATA_CDS),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD_RIP,title,32), 0);
+ return ;
+ }
+
+ l = (hwndList) ? ListView_GetItemCount(hwndList) : 0;
+ if (l)
+ {
+ p = (cdrip_params *)calloc(1, sizeof(cdrip_params));
+ if (!p) return;
+ p->ntracks = l;
+ p->tracks = (wchar_t **)calloc(sizeof(wchar_t*), p->ntracks);
+ p->trackArtists = (wchar_t **)calloc(sizeof(wchar_t*), p->ntracks);
+ p->composers = (wchar_t **)calloc(sizeof(wchar_t*), p->ntracks);
+ p->gracenoteFileIDs = (wchar_t **)calloc(sizeof(wchar_t*), p->ntracks);
+ p->gracenoteExtData = (wchar_t **)calloc(sizeof(wchar_t*), p->ntracks);
+ p->conductors = (wchar_t **)calloc(sizeof(wchar_t*), p->ntracks);
+ p->lengths = (int *)calloc(sizeof(int), p->ntracks);
+ }
+ else p = NULL;
+
+ lvitem.mask = LVIF_TEXT;
+ lvitem.iSubItem = 3;
+ lvitem.cchTextMax = sizeof(buf)/sizeof(char);
+
+ for (i = 0;i < l;i++)
+ {
+ if (all || (LVIS_SELECTED & ListView_GetItemState(hwndList, i, LVIS_SELECTED)))
+ {
+ wchar_t cdFilename[MAX_PATH] = {0};
+ StringCchPrintfW(cdFilename, MAX_PATH, L"cda://%c,%d.cda", cLetter, i + 1);
+ //check if track is Data track
+ {
+ wchar_t buf2[512] = L"";
+ getFileInfoW(cdFilename, L"tracktype", buf2, sizeof(buf2)/sizeof(*buf2));
+ if (lstrcmpiW(buf2, L"audio")) continue; //skip it
+ }
+
+ lvitem.iItem = i;
+ lvitem.pszText = buf;
+ SendMessageW(hwndList, LVM_GETITEMTEXTW, i, (LPARAM)&lvitem);
+ int len = _wtoi(lvitem.pszText) * 60 + _wtoi(wcsstr(lvitem.pszText, L":") + 1); //such hackish :)
+
+ p->total_length_bytes += len * 44100 * 4;
+
+ getFileInfoW(cdFilename, L"title", info, sizeof(info)/sizeof(wchar_t));
+ p->tracks[i] = _wcsdup(info);
+
+ getFileInfoW(cdFilename, L"artist", info, sizeof(info)/sizeof(wchar_t));
+ p->trackArtists[i] = _wcsdup(info);
+
+ getFileInfoW(cdFilename, L"composer", info, sizeof(info)/sizeof(wchar_t));
+ p->composers[i] = _wcsdup(info);
+
+ getFileInfoW(cdFilename, L"conductor", info, sizeof(info)/sizeof(wchar_t));
+ p->conductors[i] = _wcsdup(info);
+
+ getFileInfoW(cdFilename, L"GracenoteFileID", info, sizeof(info)/sizeof(wchar_t));
+ p->gracenoteFileIDs[i] = _wcsdup(info);
+
+ getFileInfoW(cdFilename, L"GracenoteExtData", info, sizeof(info)/sizeof(wchar_t));
+ p->gracenoteExtData[i] = _wcsdup(info);
+
+ p->lengths[i] = len;
+ cnt++;
+ }
+ }
+ if (!cnt)
+ {
+ if (p)
+ {
+ for (int i = 0 ; i < l; i ++) free(p->tracks[i]);
+ free(p->tracks);
+ free(p->trackArtists);
+ free(p->composers);
+ free(p->gracenoteFileIDs);
+ free(p->gracenoteExtData);
+ free(p->conductors);
+ free(p->lengths);
+ free(p);
+ }
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_NO_TRACKS_TO_RIP),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD_RIP,title,32), MB_OK);
+ return ;
+ }
+
+ p->filenames = (wchar_t **)calloc(sizeof(wchar_t *), p->ntracks); // allocate for cdrip to use :)
+ p->tempFilenames = (wchar_t **)calloc(sizeof(wchar_t *), p->ntracks); // allocate for cdrip to use :)
+ p->artist = _wcsdup(m_artist);
+ p->album = _wcsdup(m_album);
+ p->genre = _wcsdup(m_genre);
+ p->year = _wcsdup(m_year);
+
+ wchar_t name[] = L"cda://X.cda";
+ name[6] = cLetter;
+
+ info[0] = 0;
+ getFileInfoW(name, L"publisher", info, sizeof(info)/sizeof(wchar_t));
+ p->publisher = _wcsdup(info);
+
+ info[0] = 0;
+ getFileInfoW(name, L"comment", info, sizeof(info)/sizeof(wchar_t));
+ CopyComment(p->comment, info);
+
+ info[0] = 0;
+ getFileInfoW(name, L"disc", info, sizeof(info)/sizeof(wchar_t));
+ p->disc = _wcsdup(info);
+
+ p->drive_letter = cLetter;
+
+ cdrip_extractFiles(p); // will free p when done with it
+}
+
+static void playFiles(HWND hwndDlg, CHAR cLetter, int enqueue, int all)
+{
+ HWND hwndList;
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ CHAR cMode;
+ INT msgTextId;
+
+ cMode = DriveManager_GetDriveMode(cLetter);
+
+ switch (cMode)
+ {
+ case DM_MODE_BURNING: msgTextId = IDS_ERROR_CD_RIP_IN_PROGRESS; break;
+ case DM_MODE_RIPPING: msgTextId = IDS_ERROR_CD_BURN_IN_PROGRESS; break;
+ default: msgTextId = 0; break;
+ }
+ if (msgTextId)
+ {
+ wchar_t title[64] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(msgTextId),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CD_PLAYBACK_ERROR, title, 64), 0);
+ return ;
+ }
+
+ int cnt = 0;
+ int l = (hwndList) ? ListView_GetItemCount(hwndList) : 0;
+ if (enqueue && all == 1024) all = 0;
+
+ int firstsel = -1;
+ char buf[64] = {0}, titlebuf[2048] = {0};
+ enqueueFileWithMetaStruct s;
+ LVITEMA lvitem = {0};
+
+ lvitem.cchTextMax = sizeof(buf)/sizeof(char);
+
+ for (int i = 0;i < l;i++)
+ {
+ if (all == 1024 && firstsel < 0 && (LVIS_SELECTED & ListView_GetItemState(hwndList, i, LVIS_SELECTED))) firstsel = i;
+
+ if (all || (LVIS_SELECTED & ListView_GetItemState(hwndList, i, LVIS_SELECTED)))
+ {
+ lvitem.iItem = i;
+
+ lvitem.mask = LVIF_PARAM;
+ lvitem.iSubItem = 0;
+ SendMessageW(hwndList, LVM_GETITEMA, 0, (LPARAM)&lvitem);
+ int a = (INT)(INT_PTR)lvitem.lParam;
+ if (a > 0)
+ {
+ if (!cnt)
+ {
+ if (!enqueue) SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+ cnt++;
+ }
+
+ lvitem.mask = LVIF_TEXT;
+ lvitem.iSubItem = 3;
+ lvitem.pszText = buf;
+ SendMessageW(hwndList, LVM_GETITEMTEXTA, i, (LPARAM)&lvitem);
+
+ s.length = atoi(lvitem.pszText) * 60 + atoi(strstr(lvitem.pszText, ":") + 1); //such hackish :)
+
+ StringCchPrintfA(buf, 64, "cda://%c,%d.cda", cLetter, i + 1);
+ TAG_FMT(0, 0, buf, titlebuf, sizeof(titlebuf)/sizeof(*titlebuf));
+ s.filename = buf;
+ s.title = titlebuf;
+ s.ext = NULL;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILE);
+ }
+ }
+ }
+ if (cnt && !enqueue)
+ {
+ if (firstsel >= 0)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, firstsel, IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40047, 0); // stop button, literally
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40045, 0); // play button, literally
+ }
+ else SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ }
+}
+
+void saveCDToItemRecordList(CHAR cLetter, itemRecordList *obj, char *albumname)
+{
+ char fname[64] = {0}, buf2[64] = {0};
+ StringCchPrintfA(fname, 64, "cda://%c.cda", cLetter);
+
+ getFileInfo(fname, "<begin>", buf2, sizeof(buf2)/sizeof(char));
+ getFileInfo(fname, "ntracks", buf2, sizeof(buf2)/sizeof(char));
+
+ int ntracks = atoi(buf2);
+
+ if (ntracks > 0 && ntracks < 256)
+ {
+ obj->Items = 0; obj->Alloc = 0; obj->Size = 0;
+ allocRecordList(obj, ntracks, 0);
+
+ int x;
+ for (x = 0; x < ntracks; x ++)
+ {
+ StringCchPrintfA(fname, 64, "cda://%c,%d.cda", cLetter, x + 1);
+ getFileInfo(fname, "tracktype", buf2, sizeof(buf2)/sizeof(char));
+ if (!lstrcmpiA(buf2, "audio"))
+ {
+ int len = -1;
+ char titlebuf[FILETITLE_SIZE] = {0};
+ mediaLibrary.GetFileInfo(fname, titlebuf, FILETITLE_SIZE, &len);
+ itemRecord *pRec = &obj->Items[obj->Size];
+ ZeroMemory(pRec, sizeof(itemRecord));
+ if (titlebuf) pRec->title = _strdup(titlebuf);
+ pRec->length = len;
+ if (fname) pRec->filename = _strdup(fname);
+ obj->Size++;
+ }
+ }
+ }
+ getFileInfo(fname, "<end>", buf2, sizeof(buf2)/sizeof(char));
+}
+
+
+
+
+static ChildWndResizeItem cdromwnd_rlist[] =
+{
+ {IDC_LIST2, 0x0011},
+ {IDC_CDINFO, 0x0000},
+ {IDC_CDINFO2, 0x0000},
+ {IDC_BUTTON_PLAY, 0x0101},
+ {IDC_BUTTON_ENQUEUE, 0x0101},
+ {IDC_BUTTON_EXTRACT, 0x0101},
+ {IDC_BUTTON_EJECT, 0x0101},
+ {IDC_BTN_SHOWINFO, 0x1111},
+};
+
+typedef struct _VIEWCOLUMN
+{
+ INT stringId;
+ LPTSTR pszConfig;
+ INT defaultWidth;
+} VIEWCOLUMN;
+
+static VIEWCOLUMN viewColumns[] =
+{
+ { IDS_TRACK_NUMBER, TEXT("col_track"), 60 },
+ { IDS_ARTIST, TEXT("col_artist"), 150 },
+ { IDS_TITLE, TEXT("col_title"), 200 },
+ { IDS_LENGTH, TEXT("col_len"), 80 },
+};
+
+static char m_cdrom;
+
+typedef struct _APCPARAM
+{
+ HWND hwndDlg;
+ CHAR cLetter;
+ INT_PTR user;
+}
+APCPARAM;
+
+static void CALLBACK APC_GetCracenoteInfo(ULONG_PTR param);
+
+static void GetGracenoteInfo(HWND hwndDlg, CHAR cLetter, HANDLE hThread = NULL)
+{
+ HWND hwndList;
+ int l, x;
+ LVITEMW lvitem = {0};
+ wchar_t buf[32] = {0}, titlebuf[256] = {0};
+
+ if (hThread)
+ {
+ APCPARAM *pParam = (APCPARAM*)calloc(1, sizeof(APCPARAM));
+ if (pParam)
+ {
+ pParam->cLetter = cLetter;
+ pParam->hwndDlg = hwndDlg;
+ if (!QueueUserAPC(APC_GetCracenoteInfo, hThread, (ULONG_PTR)pParam)) free(pParam);
+ }
+ return;
+ }
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (!hwndList) return;
+
+ l = (INT)SendMessageW(hwndList, LVM_GETITEMCOUNT, 0,0);
+
+ // first, let's try to get artist/album info
+ {
+ StringCchPrintfW(buf, 32, L"cda://%c.cda", cLetter);
+ wchar_t artistbuf[256] = {0}, albumbuf[256] = {0}, yearbuf[256] = {0}, genrebuf[256] = {0};
+ getFileInfoW(buf, L"albumartist", artistbuf, sizeof(artistbuf) / sizeof(artistbuf[0]));
+ getFileInfoW(buf, L"album", albumbuf, sizeof(albumbuf) / sizeof(albumbuf[0]));
+ getFileInfoW(buf, L"genre", genrebuf, sizeof(genrebuf) / sizeof(genrebuf[0]));
+ getFileInfoW(buf, L"year", yearbuf, sizeof(yearbuf) / sizeof(yearbuf[0]));
+
+ wchar_t newbuf[1024] = {0};
+ lstrcpynW(m_artist, artistbuf, sizeof(m_artist)/sizeof(*m_artist));
+ lstrcpynW(m_album, albumbuf, sizeof(m_album)/sizeof(*m_album));
+ lstrcpynW(m_year, yearbuf, sizeof(m_year)/sizeof(*m_year));
+ lstrcpynW(m_genre, genrebuf, sizeof(m_genre)/sizeof(*m_genre));
+ StringCchPrintfW(newbuf, 1024, WASABI_API_LNGSTRINGW(IDS_ML_VIEW_ARTIST_ALBUM), artistbuf, albumbuf);
+ SetDlgItemText(hwndDlg, IDC_CDINFO, newbuf);
+ StringCchPrintfW(newbuf, 1024, WASABI_API_LNGSTRINGW(IDS_ML_VIEW_YEAR_GENRE), yearbuf, genrebuf);
+ SetDlgItemText(hwndDlg, IDC_CDINFO2, newbuf);
+ }
+
+ for (x = 0; x < l; x ++)
+ {
+ lvitem.iItem = x;
+ lvitem.mask = LVIF_PARAM;
+ lvitem.iSubItem = 0;
+ SendMessageW(hwndList, LVM_GETITEMW, 0, (LPARAM)&lvitem);
+
+ int wt = (INT)(INT_PTR)lvitem.lParam;
+
+ if (wt > 0)
+ {
+ StringCchPrintfW(buf, 32, L"cda://%c,%d.cda", cLetter, x + 1);
+
+ lvitem.mask = LVIF_TEXT;
+
+ titlebuf[0] = 0;
+ getFileInfoW(buf, L"title", titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]));
+ if (titlebuf[0])
+ {
+ lvitem.iSubItem = 2;
+ lvitem.pszText = titlebuf;
+ SendMessageW(hwndList, LVM_SETITEMW, 0, (LPARAM)&lvitem);
+ }
+
+ titlebuf[0] = 0;
+ getFileInfoW(buf, L"artist", titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]));
+ if (titlebuf[0])
+ {
+ lvitem.iSubItem = 1;
+ lvitem.pszText = titlebuf;
+ SendMessageW(hwndList, LVM_SETITEMW, 0, (LPARAM)&lvitem);
+ }
+ }
+ }
+
+ if (l && hwndList)
+ UpdateWindow(hwndList);
+
+ if ( Plugin_IsExtractScheduled( cLetter ) )
+ extractFiles( hwndDlg, cLetter, 1 );
+}
+
+static void UpdateCDView(HWND hwndDlg, DM_NOTIFY_PARAM *phdr)
+{
+ HWND hwndList;
+
+ m_cdrom = phdr->cLetter;
+ m_atracks = m_dtracks = 0;
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (!hwndList) return;
+
+ ListView_DeleteAllItems(hwndList);
+
+ SetDlgItemText(hwndDlg, IDC_CDINFO2, L"");
+
+ SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L);
+
+ if (DMOP_MCIINFO == phdr->opCode)
+ {
+ DM_MCI_PARAM *pmci;
+ LVITEMW lvitem = {0};
+ wchar_t buffer[512] = {0};
+ INT strid, param;
+ pmci = (DM_MCI_PARAM*)phdr;
+
+ for (int i = 0; i < pmci->nTracks; i++)
+ {
+ INT time = (0x7FFFFFFF & pmci->pTracks[i])/1000;
+
+ if (0x80000000 & pmci->pTracks[i])
+ {
+ param = i + 1; strid = IDS_AUDIO_TRACK; m_atracks++;
+ }
+ else
+ {
+ param = -1; strid = IDS_DATA_TRACK; m_dtracks++;
+ }
+
+ StringCchPrintfW(buffer, sizeof(buffer)/sizeof(wchar_t), L"%d", i + 1);
+
+ lvitem.mask = LVIF_TEXT | LVIF_PARAM;
+ lvitem.iItem = i;
+ lvitem.iSubItem = 0;
+ lvitem.pszText = buffer;
+ lvitem.lParam = param;
+ INT index = (INT)SendMessageW(hwndList, LVM_INSERTITEMW, 0, (LPARAM)&lvitem);
+
+ if (-1 != index)
+ {
+ lvitem.iItem = index;
+ lvitem.mask = LVIF_TEXT;
+
+ lvitem.iSubItem = 2;
+ lvitem.pszText = WASABI_API_LNGSTRINGW(strid);
+ SendMessageW(hwndList, LVM_SETITEMW, 0, (LPARAM)&lvitem);
+
+ if (time < 0) StringCchCopyW(buffer, sizeof(buffer)/sizeof(wchar_t), L"???");
+ else StringCchPrintfW(buffer, sizeof(buffer)/sizeof(wchar_t), L"%d:%02d", time / 60, time % 60);
+
+ lvitem.iSubItem = 3;
+ lvitem.pszText = buffer;
+ SendMessageW(hwndList, LVM_SETITEMW, 0, (LPARAM)&lvitem);
+ }
+ }
+
+ SetDlgItemText(hwndDlg, IDC_CDINFO, WASABI_API_LNGSTRINGW((m_atracks) ? IDS_CD_AUDIO : ((m_dtracks) ? IDS_DATA_CD : IDS_NO_CD)));
+
+ if (m_atracks) GetGracenoteInfo(hwndDlg, phdr->cLetter, GetCurrentThread());
+ }
+ else SetDlgItemText(hwndDlg, IDC_CDINFO, WASABI_API_LNGSTRINGW(IDS_NO_CD));
+
+ SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L);
+ UpdateWindow(hwndList);
+}
+
+static LRESULT editCDInfo(HWND hwndDlg, CHAR cLetter, int trackNum)
+{
+ wchar_t name[MAX_PATH] = {0};
+ if (trackNum)
+ StringCchPrintfW(name, MAX_PATH, L"cda://%c,%d", cLetter, trackNum);
+ else
+ StringCchPrintfW(name, MAX_PATH, L"cda://%c.cda", cLetter);
+ infoBoxParamW p;
+ p.filename = name;
+ p.parent = hwndDlg;
+ return SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW);
+}
+
+static void NotifyInfoWindow(HWND hwnd, CHAR cLetter, INT nTrack, BOOL bForceRefresh)
+{
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+
+ if (hwndParent)
+ {
+ wchar_t szFileName[MAX_PATH], *p;
+ if (nTrack && S_OK == StringCchPrintfW(szFileName, sizeof(szFileName)/sizeof(wchar_t), L"cda://%c,%d.cda", cLetter, nTrack)) p = szFileName;
+ else p = L"";
+ SendMessageW(hwndParent, WM_SHOWFILEINFO, (WPARAM)((bForceRefresh) ? WISF_FORCE : WISF_NORMAL), (LPARAM)p);
+ }
+}
+
+static BOOL Window_OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
+{
+ HWND hwndList;
+
+ if (!lParam)
+ {
+ DestroyWindow(hwndDlg);
+ return 0;
+ }
+
+ SendMessageW(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(IDC_BTN_SHOWINFO, BN_EX_GETTEXT), (LPARAM)GetDlgItem(hwndDlg, IDC_BTN_SHOWINFO));
+
+ childSizer.Init(hwndDlg, cdromwnd_rlist, sizeof(cdromwnd_rlist) / sizeof(cdromwnd_rlist[0]));
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ if (hwndList)
+ {
+ MLSKINWINDOW sw;
+ LVCOLUMNW column;
+
+ sw.hwndToSkin = hwndList;
+ sw.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ sw.style = SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS | SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &sw);
+
+ column.mask = LVCF_WIDTH | LVCF_TEXT;
+ for (int i = 0; i < sizeof(viewColumns)/sizeof(VIEWCOLUMN); i++)
+ {
+ column.cx = g_view_metaconf->ReadInt(viewColumns[i].pszConfig, viewColumns[i].defaultWidth);
+ column.pszText = WASABI_API_LNGSTRINGW(viewColumns[i].stringId);
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, (WPARAM)0xEFFF, (LPARAM)&column);
+ }
+ }
+
+
+ SetDlgItemText(hwndDlg, IDC_CDINFO, L"");
+ SetDlgItemText(hwndDlg, IDC_CDINFO2, L"");
+
+ NotifyInfoWindow(hwndDlg, ((DM_NOTIFY_PARAM*)lParam)->cLetter, NULL, TRUE); // ignore cache
+
+ UpdateCDView(hwndDlg, (DM_NOTIFY_PARAM*)lParam);
+
+ return FALSE;
+}
+static void Window_OnDestroy(HWND hwndDlg)
+{
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ if (hwndList)
+ {
+ for (int i = 0; i < sizeof(viewColumns)/sizeof(VIEWCOLUMN); i++)
+ {
+ g_view_metaconf->WriteInt(viewColumns[i].pszConfig,
+ (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L));
+ }
+ }
+
+ if (m_cdrom) NotifyInfoWindow(hwndDlg, m_cdrom, NULL, FALSE);
+
+}
+
+
+static void Window_OnSize(HWND hwndDlg, UINT nType, INT cx, INT cy)
+{
+ if (nType != SIZE_MINIMIZED)
+ {
+ childSizer.Resize(hwndDlg, cdromwnd_rlist, sizeof(cdromwnd_rlist) / sizeof(cdromwnd_rlist[0]));
+ InvalidateRect(hwndDlg, NULL, TRUE);
+ }
+}
+
+static void CALLBACK Window_TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ HWND hwndList;
+
+ switch (idEvent)
+ {
+ case TIMER_NOTIFYINFO_ID:
+ KillTimer(hwnd, TIMER_NOTIFYINFO_ID);
+ hwndList = GetDlgItem(hwnd, IDC_LIST2);
+ NotifyInfoWindow(hwnd, m_cdrom,
+ (hwndList) ? (INT)SendMessage(hwndList, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_FOCUSED) + 1: 0,
+ FALSE);
+ break;
+ }
+}
+
+static void Window_OnCommand(HWND hwndDlg, INT eventId, INT ctrlId, HWND hwndCtrl)
+{
+ switch (ctrlId)
+ {
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_PLAY:
+ {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (hwndList)
+ {
+ int selcnt = (INT)SendMessageW(hwndList, LVM_GETSELECTEDCOUNT, 0, 0L);
+ playFiles(hwndDlg, m_cdrom, (IDC_BUTTON_ENQUEUE == ctrlId), (selcnt) ? ((selcnt == 1) ? 1024 : 0) : 1);
+ }
+ }
+ break;
+ case IDC_BUTTON_EXTRACT:
+ {
+ RECT r;
+ GetWindowRect(hwndCtrl, &r);
+ int x = Menu_TrackPopup(plugin.hwndLibraryParent, GetSubMenu(g_context_menus, 0),
+ TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN |
+ TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ r.left, r.top, hwndDlg, NULL);
+ switch (x)
+ {
+ case ID_EXTRACTMENU_EXTRACTSELECTEDTRACKS: extractFiles(hwndDlg, m_cdrom, 0); break;
+ case ID_EXTRACTMENU_EXTRACTALLTRACKS: extractFiles(hwndDlg, m_cdrom, 1); break;
+ case ID_EXTRACTMENU_CONFIGURE: Plugin_ShowRippingPreferences(); break;
+ }
+ UpdateWindow(hwndDlg);
+ Sleep(100);
+ MSG msg;
+ while (PeekMessageW(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ }
+ break;
+ case IDC_BUTTON_EJECT:
+ {
+ wchar_t result[32] = {0};
+ wchar_t name[] = L"cda://X.cda";
+
+ name[6] = m_cdrom;
+ getFileInfoW(name, L"<eject>", result, sizeof(result)/sizeof(wchar_t));
+ }
+ break;
+ case IDC_BTN_SHOWINFO:
+ switch (eventId)
+ {
+ case BN_CLICKED:
+ SendMessageW(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(ctrlId, eventId), (LPARAM)hwndCtrl);
+ break;
+ }
+ break;
+ }
+}
+static void ListView_OnItemChanged(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ if (LVIF_STATE & pnmv->uChanged)
+ {
+ if ((LVIS_FOCUSED & pnmv->uOldState) != (LVIS_FOCUSED & pnmv->uNewState))
+ {
+ KillTimer(hwndDlg, TIMER_NOTIFYINFO_ID);
+ SetTimer(hwndDlg, TIMER_NOTIFYINFO_ID, TIMER_NOTIFYINFO_DELAY, Window_TimerProc);
+
+ }
+ }
+}
+static INT_PTR Window_OnNotify(HWND hwndDlg, INT ctrlId, LPNMHDR phdr)
+{
+ switch (phdr->idFrom)
+ {
+ case IDC_LIST2:
+ switch (phdr->code)
+ {
+ case LVN_ITEMCHANGED: ListView_OnItemChanged(hwndDlg, (NMLISTVIEW*)phdr); break;
+ case LVN_KEYDOWN:
+ {
+ LPNMLVKEYDOWN pnkd = (LPNMLVKEYDOWN)phdr;
+ switch (pnkd->wVKey)
+ {
+ case '3':
+ if (GetAsyncKeyState(VK_MENU)&0x8000)
+ {
+ W_ListView view(GetDlgItem(hwndDlg, IDC_LIST2));
+ if (view.GetSelectedCount() == 0 || view.GetSelectedCount() == view.GetCount())
+ editCDInfo(hwndDlg, m_cdrom, 0);
+
+ int sel =-1;
+ while ((sel = view.GetNextSelected(sel)) != -1)
+ {
+ if (editCDInfo(hwndDlg, m_cdrom, sel+1) == 1)
+ break;
+ }
+
+
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)phdr->hwndFrom, TRUE);
+ }
+ break;
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL)) ListView_SetItemState(phdr->hwndFrom, -1, LVIS_SELECTED, LVIS_SELECTED);
+ break;
+ }
+ }
+ break;
+ case NM_DBLCLK:
+ playFiles(hwndDlg, m_cdrom, (!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)), 1024);
+ break;
+ case LVN_BEGINDRAG:
+ SetCapture(hwndDlg);
+ break;
+ case NM_RETURN:
+ SendMessageW(hwndDlg, WM_COMMAND, ((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^(!!g_config->ReadInt(L"enqueuedef", 0)))
+ ? IDC_BUTTON_ENQUEUE : IDC_BUTTON_PLAY, 0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void Window_OnMouseMove(HWND hwndDlg, INT vKey, POINTS pts)
+{
+ mlDropItemStruct m = {0};
+ if (GetCapture() != hwndDlg) return;
+
+ POINTSTOPOINT(m.p, pts);
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, &m.p, 1);
+
+ m.type = ML_TYPE_CDTRACKS;
+
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_HANDLEDRAG, (WPARAM)&m);
+
+}
+
+static void Window_OnLButtonUp(HWND hwndDlg, INT vKey, POINTS pts)
+{
+ mlDropItemStruct m = {0};
+
+ if (GetCapture() != hwndDlg) return;
+
+ ReleaseCapture();
+
+ m.type = ML_TYPE_CDTRACKS;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ POINTSTOPOINT(m.p, pts);
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, &m.p, 1);
+
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_HANDLEDRAG, (WPARAM)&m);
+
+ if (m.result > 0)
+ {
+ HWND hwndList;
+ LVITEMW lvitem = {0};
+ int i, l, len;
+ itemRecordList myObj = {0};
+ char trackname[] = "cda://X,%d.cda";
+ char name[32] = {0}, total[512] = {0};
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ l = (hwndList) ? (INT)SendMessageW(hwndList, LVM_GETITEMCOUNT, 0,0) : 0;
+ if (l > 256) l = 256;
+
+
+ allocRecordList(&myObj, l, 0);
+
+ lvitem.mask = LVIF_PARAM;
+ lvitem.iSubItem = 0;
+
+ trackname[6] = m_cdrom;
+
+ for (i = 0; i < l; i++)
+ {
+ lvitem.iItem = i;
+ SendMessageW(hwndList, LVM_GETITEMW, 0, (LPARAM)&lvitem);
+ int p = (INT)(INT_PTR)lvitem.lParam;
+
+ if ((LVIS_SELECTED & SendMessageW(hwndList, LVM_GETITEMSTATE, i, LVIS_SELECTED)) && p > 0)
+ {
+
+ StringCchPrintfA(name, sizeof(name)/sizeof(char), trackname, p);
+
+ total[0] = 0;
+ mediaLibrary.GetFileInfo(name, total, sizeof(total)/sizeof(char), &len);
+ memset(myObj.Items + myObj.Size, 0, sizeof(itemRecord));
+ myObj.Items[myObj.Size].filename = _strdup(name);
+ myObj.Items[myObj.Size].length = len;
+ myObj.Items[myObj.Size++].title = _strdup(total);
+ }
+ }
+ if (myObj.Size)
+ {
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*)&myObj;
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_HANDLEDROP, (WPARAM)&m);
+ }
+
+ freeRecordList(&myObj);
+ }
+}
+
+
+
+static void Window_OnQueryInfo(HWND hwnd)
+{
+ KillTimer(hwnd, TIMER_NOTIFYINFO_ID);
+ NotifyInfoWindow(hwnd, m_cdrom, NULL, TRUE);
+ SetTimer(hwnd, TIMER_NOTIFYINFO_ID, TIMER_NOTIFYINFO_DELAY, Window_TimerProc);
+}
+
+
+static void Window_OnFileTagUpdated(HWND hwnd, CHAR cLetter, LPCWSTR pszFileName)
+{
+ INT len, lcid;
+
+ len = (pszFileName) ? lstrlenW(pszFileName) : 0;
+ if (len < 7) return;
+ lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
+ if (CSTR_EQUAL == CompareStringW(lcid, 0, L"cda://", 6, pszFileName, 6) && pszFileName[6] == cLetter)
+ {
+ GetGracenoteInfo(hwnd, cLetter, GetCurrentThread());
+ }
+}
+
+static void Window_OnContextMenu(HWND hwndDlg, HWND hwndFrom, int x, int y)
+{
+ POINT pt = {x,y};
+ W_ListView view(GetDlgItem(hwndDlg, IDC_LIST2));
+
+ if(view.GetCount() == 0) return;
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = view.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ view.GetItemRect(selected, &itemRect);
+ ClientToScreen(view.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(view.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(hwndFrom, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(hwndFrom, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return;
+ }
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, GetSubMenu(g_context_menus, 1),
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ x, y, hwndDlg, NULL);
+ switch (r)
+ {
+ case ID_PE_ID3:
+ {
+ if (view.GetSelectedCount() == 0 || view.GetSelectedCount() == view.GetCount())
+ editCDInfo(hwndDlg, m_cdrom, 0);
+
+ int sel =-1;
+ while ((sel = view.GetNextSelected(sel)) != -1)
+ {
+ if (editCDInfo(hwndDlg, m_cdrom, sel+1) == 1)
+ break;
+ }
+ }
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)hwndFrom, TRUE);
+ break;
+ case ID_CDROMMENU_PLAYSELECTEDITEMS: playFiles(hwndDlg, m_cdrom, 0, 0); break;
+ case ID_CDROMMENU_ENQUEUESELECTEDITEMS: playFiles(hwndDlg, m_cdrom, 1, 0); break;
+ case ID_CDROMMENU_SELECTALL: ListView_SetItemState(hwndFrom, -1, LVIS_SELECTED, LVIS_SELECTED); break;
+ case ID_CDROMMENU_PLAYALL: playFiles(hwndDlg, m_cdrom, 0, 1); break;
+ case ID_CDROMMENU_ENQUEUEALL: playFiles(hwndDlg, m_cdrom, 1, 1); break;
+ case ID_CDROMMENU_EXTRACT_EXTRACTSELECTEDITEMS: extractFiles(hwndDlg, m_cdrom, 0); break;
+ case ID_CDROMMENU_EXTRACT_EXTRACTALL: extractFiles(hwndDlg, m_cdrom, 1); break;
+ case ID_CDROMMENU_EXTRACT_CONFIGURE: Plugin_ShowRippingPreferences(); break;
+ }
+ UpdateWindow(hwndDlg);
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+}
+
+static INT_PTR WINAPI DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR a;
+ a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam);
+ if (a) return a;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG: return Window_OnInitDialog(hwndDlg, (HWND)wParam, lParam);
+ case WM_DESTROY: Window_OnDestroy(hwndDlg); break;
+ case WM_SIZE: Window_OnSize(hwndDlg, (UINT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(wParam)); break;
+ case WM_COMMAND: Window_OnCommand(hwndDlg, HIWORD(wParam), LOWORD(wParam), (HWND)lParam); break;
+ case WM_NOTIFY: return Window_OnNotify(hwndDlg, (INT)wParam, (LPNMHDR) lParam);
+ case WM_MOUSEMOVE: Window_OnMouseMove(hwndDlg, (INT)wParam, MAKEPOINTS(lParam)); break;
+ case WM_LBUTTONUP: Window_OnLButtonUp(hwndDlg, (INT)wParam, MAKEPOINTS(lParam)); break;
+ case WM_CONTEXTMENU: Window_OnContextMenu(hwndDlg, (HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return 1;
+ case WM_ERASEBKGND: return 1;
+ case WM_TAGUPDATED: Window_OnFileTagUpdated(hwndDlg, m_cdrom, (LPCWSTR)lParam); break;
+ case WM_QUERYFILEINFO: Window_OnQueryInfo(hwndDlg); break;
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST2 | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 1);
+ }
+ return 0;
+
+ case WM_EXTRACTDISC:
+ if ((CHAR)wParam == m_cdrom) extractFiles(hwndDlg, m_cdrom, TRUE);
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, ((CHAR)wParam == m_cdrom));
+ return TRUE;
+
+ }
+
+ return 0;
+}
+
+static void CALLBACK APC_GetCracenoteInfo(ULONG_PTR param)
+{
+ GetGracenoteInfo(((APCPARAM*)param)->hwndDlg, ((APCPARAM*)param)->cLetter, NULL);
+ free((APCPARAM*)param);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/view_container.cpp b/Src/Plugins/Library/ml_disc/view_container.cpp
new file mode 100644
index 00000000..d5dad335
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/view_container.cpp
@@ -0,0 +1,573 @@
+#include "main.h"
+#include "./resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/AutoWideFn.h"
+#include "./commandbar.h"
+#include "./settings.h"
+#include "../nu/trace.h"
+
+//#include <primosdk.h>
+#include <windowsx.h>
+#include <strsafe.h>
+
+#define MINIINFO_HEIGHT 100
+
+#define IDC_CHILDVIEW 0x1000
+#define IDC_MINIINFO 0x1001
+#define IDC_COMMAND_BAR 0x1002
+
+#define WINDOWS_SPACING 3
+
+#define CONTAINER_PROPW L"CONTAINER"
+
+typedef struct _CONTAINER
+{
+ CHAR cLetter;
+ INT typeChild;
+ BOOL bInfoVisible;
+ HANDLE hLastQuery;
+} CONTAINER;
+
+static UINT msgNotify = 0;
+#define GetContainer(__hwnd) ((CONTAINER*)GetPropW(__hwnd, CONTAINER_PROPW))
+static INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+// public function
+typedef struct _CONTAINER_PARAM
+{
+ CHAR cLetter;
+ BOOL bQueryInfo;
+}CONTAINER_PARAM;
+
+HWND CreateContainerWindow(HWND hwndParent, CHAR cLetter, BOOL bQueryInfo)
+{
+ CONTAINER_PARAM param;
+ param.cLetter = cLetter;
+ param.bQueryInfo = bQueryInfo;
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_CONTAINER, hwndParent, DlgProc, (LPARAM)&param);
+}
+
+static void ViewContainer_LayoutChildren(HWND hdlg, BOOL bRedraw)
+{
+ RECT rc;
+ HWND hctrl;
+ HDWP hdwp;
+ DWORD flags;
+
+ CONTAINER *pc = GetContainer(hdlg);
+ if (!pc) return;
+
+ if (!GetClientRect(hdlg, &rc)) return;
+ rc.right -= 2;
+
+ flags = SWP_NOACTIVATE | ((bRedraw) ? 0 : SWP_NOREDRAW);
+
+ hdwp = BeginDeferWindowPos(3);
+ if (!hdwp) return;
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_COMMAND_BAR)))
+ {
+ INT height = CommandBar_GetBestHeight(hctrl);
+ hdwp = DeferWindowPos(hdwp, hctrl, HWND_BOTTOM, rc.left, rc.bottom - (height), rc.right - rc.left, height, flags);
+ rc.bottom -= (height + WINDOWS_SPACING);
+ }
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_MINIINFO)) && pc->bInfoVisible)
+ {
+ INT height = MINIINFO_HEIGHT;
+ hdwp = DeferWindowPos(hdwp, hctrl, HWND_TOP, rc.left, rc.bottom - height, rc.right - rc.left, height, flags);
+ rc.bottom -= (height + WINDOWS_SPACING);
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_CHILDVIEW)))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, flags);
+ }
+
+ EndDeferWindowPos(hdwp);
+}
+
+static void CreateChild(HWND hParent, INT typeId, LPARAM param, BOOL fCreateAlways = TRUE)
+{
+ DM_NOTIFY_PARAM hdr;
+ CONTAINER *pc = GetContainer(hParent);
+ if (!pc) return;
+
+ HWND hChild = GetDlgItem(hParent, IDC_CHILDVIEW);
+ if (hChild)
+ {
+ if (pc->typeChild == typeId && !fCreateAlways) return;
+ DestroyWindow(hChild);
+ }
+
+ hChild = NULL;
+ pc->typeChild = 0;
+
+ switch(typeId)
+ {
+ case IDD_VIEW_INFO:
+ hChild = CreateInfoWindow(hParent, (CHAR)(0xFF & param));
+ break;
+ case IDD_VIEW_WAIT:
+ hChild = CreateWaitWindow(hParent, (CHAR)(0xFF & param));
+ break;
+ case IDD_VIEW_CDROM:
+ if (param <= 0xFF)
+ {
+ ZeroMemory(&hdr, sizeof(DM_NOTIFY_PARAM));
+ hdr.cLetter = (CHAR)(0xFF & (INT)param);
+ hdr.opCode = DMOP_GENERAL;
+ param = (INT_PTR)&hdr;
+ }
+ hChild = CreateCDViewWindow(hParent, (DM_NOTIFY_PARAM*)param);
+ break;
+ case IDD_VIEW_CDROM_EX2:
+ hChild = CreateCDRipWindow(hParent, (CHAR)(0xFF & param));
+ break;
+ case IDD_VIEW_CDROM_DATA:
+ hChild = CreateCdDataViewWindow(hParent, (CHAR)(0xFF & param));
+ break;
+ }
+ TRACE_FMT(L"Creating cd view (%d)\n", typeId);
+ if (hChild)
+ {
+ pc->typeChild = typeId;
+ SetWindowLongPtrW(hChild, GWLP_ID, IDC_CHILDVIEW);
+ ViewContainer_LayoutChildren(hParent, TRUE);
+ ShowWindow(hChild, SW_SHOWNA);
+ }
+}
+
+static HWND CreateMiniInfoWindow(HWND hwndParent)
+{
+ RECT rw;
+ HWND hwnd;
+ WEBINFOCREATE wic;
+
+ GetWindowRect(hwndParent, &rw);
+
+ wic.hwndParent = hwndParent;
+ wic.uMsgQuery = WM_QUERYFILEINFO;
+ wic.ctrlId = IDC_MINIINFO;
+ wic.cx = rw.right - rw.left - 3;
+ wic.cy = MINIINFO_HEIGHT;
+ wic.x = 0;
+ wic.y = (rw.bottom - rw.top) - MINIINFO_HEIGHT - 1;
+
+ hwnd = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_CREATEWEBINFO, (WPARAM)&wic);
+ if (hwnd)
+ {
+ SetWindowLongPtrW(hwnd, GWL_EXSTYLE, GetWindowLongPtrW(hwnd, GWL_EXSTYLE) | WS_EX_CLIENTEDGE);
+ MLSkinWindow2(plugin.hwndLibraryParent, hwnd, SKINNEDWND_TYPE_WINDOW, SWS_USESKINCOLORS);
+ }
+
+ return hwnd;
+}
+
+static void CALLBACK FreeAsyncParam(DM_NOTIFY_PARAM *phdr)
+{
+ DM_MCI_PARAM *pmci;
+ switch(phdr->opCode)
+ {
+ case DMOP_MCIINFO:
+ pmci = (DM_MCI_PARAM*)phdr;
+ if (pmci->pTracks) free(pmci->pTracks);
+ break;
+ case DMOP_IMAPIINFO:
+ break;
+ }
+ free(phdr);
+}
+
+static void QueryDriveRecordable(HWND hwnd, CHAR cLetter)
+{
+ CONTAINER *pc = GetContainer(hwnd);
+ if (!pc) return;
+
+ DWORD dwType;
+ dwType = DriveManager_GetDriveType(cLetter);
+ if (0 == (DRIVE_CAP_UNKNOWN & dwType))
+ {
+ CreateChild(hwnd, ((DRIVE_CAP_R | DRIVE_CAP_RW) & dwType) ? IDD_VIEW_CDROM_BURN : IDD_VIEW_CDROM, (LPARAM)cLetter);
+ return;
+ }
+ else
+ {
+ DM_UNITINFO_PARAM *pui;
+
+ pui = (DM_UNITINFO_PARAM*)calloc(1, sizeof(DM_UNITINFO_PARAM));
+ if (pui)
+ {
+ pui->header.callback = (INT_PTR)hwnd;
+ pui->header.uMsg = msgNotify;
+ pui->header.cLetter = cLetter;
+ pui->header.fnFree = FreeAsyncParam;
+ pc->hLastQuery = pui;
+ if (!DriveManager_GetUnitInfo(pui)) CreateChild(hwnd, IDD_VIEW_CDROM, (LPARAM)cLetter);
+ }
+ }
+}
+
+static void QueryDiscInfo(HWND hwnd, CHAR cLetter)
+{
+ DM_MCI_PARAM *pmci;
+ CHAR cMode;
+
+ CONTAINER *pc = GetContainer(hwnd);
+ if (!pc) return;
+
+ cMode = DriveManager_GetDriveMode(cLetter);
+
+ switch(cMode)
+ {
+ case DM_MODE_RIPPING:
+ if (cdrip_isextracting(cLetter)) CreateChild(hwnd, IDD_VIEW_CDROM_EX2, cLetter);
+ else CreateChild(hwnd, IDD_VIEW_INFO, cLetter);
+ return;
+
+ case DM_MODE_COPYING:
+ if (IDD_VIEW_CDROM_DATA == pc->typeChild) return;
+ break;
+ case DM_MODE_READY:
+ if (IDD_VIEW_CDROM_DATA == pc->typeChild) return;
+ break;
+ }
+
+ CreateChild(hwnd, IDD_VIEW_WAIT, cLetter);
+
+ pmci = (DM_MCI_PARAM*)calloc(1, sizeof(DM_MCI_PARAM));
+ if (pmci)
+ {
+ pmci->header.callback = (INT_PTR)hwnd;
+ pmci->header.uMsg = msgNotify;
+ pmci->header.cLetter = cLetter;
+ pmci->header.fnFree = FreeAsyncParam;
+ pmci->header.fFlags = DMF_READY | DMF_MEDIUMPRESENT | DMF_MODE | DMF_TRACKCOUNT | DMF_TRACKSINFO;
+ pmci->nTracks = 256;
+ pmci->pTracks = (DWORD*)calloc(pmci->nTracks, sizeof(DWORD));
+
+ pc->hLastQuery = pmci;
+
+ if (!DriveManager_GetMCIInfo(pmci))
+ {
+ pc->hLastQuery = NULL;
+ // display error dialog
+ }
+ }
+}
+
+static void Medium_OnArrived(HWND hwnd, CHAR cLetter)
+{
+ QueryDiscInfo(hwnd, cLetter);
+}
+
+static void Medium_OnRemoved(HWND hwnd, CHAR cLetter)
+{
+ QueryDriveRecordable(hwnd, cLetter);
+}
+
+static void Drive_OnModeChanged(HWND hwnd, CHAR cLetter, CHAR cMode)
+{
+ QueryDiscInfo(hwnd, cLetter);
+ HWND hctrl = GetDlgItem(hwnd, IDC_CHILDVIEW);
+ if (hctrl) SendMessage(hctrl, WM_COMMAND, MAKEWPARAM(ID_DRIVE_MODE_CHANGED, 0), (LPARAM)hwnd);
+}
+
+static void GetInfo_Completed(HWND hwnd, DM_NOTIFY_PARAM *phdr)
+{
+ DM_MCI_PARAM *pmci;
+ CONTAINER *pc = GetContainer(hwnd);
+ if (!pc || phdr != pc->hLastQuery) return;
+
+ switch(phdr->opCode)
+ {
+ case DMOP_MCIINFO:
+ pmci = (DM_MCI_PARAM*)phdr;
+ if (0 == phdr->result)
+ {
+ if (pmci->bMediumPresent && pmci->nTracks > 0)
+ {
+ int i;
+ for (i = 0; i < pmci->nTracks; i++) if (0x80000000 & pmci->pTracks[i]) break;
+ if (i == pmci->nTracks) CreateChild(hwnd, IDD_VIEW_CDROM_DATA, (LPARAM)phdr->cLetter);
+ else CreateChild(hwnd, IDD_VIEW_CDROM, (LPARAM)phdr);
+ }
+ else QueryDriveRecordable(hwnd, phdr->cLetter);
+ }
+ else {/*go to error view*/ }
+ break;
+ case DMOP_UNITINFO:
+ CreateChild(hwnd, ( 0 == phdr->result && Drive_IsRecorderType(((DM_UNITINFO_PARAM*)phdr)->dwType)) ? IDD_VIEW_CDROM_BURN : IDD_VIEW_CDROM, (LPARAM)phdr->cLetter);
+ break;
+ }
+}
+
+static BOOL ViewContainer_OnGetMiniInfoEnabled(HWND hdlg)
+{
+ return g_config->ReadInt(TEXT("useminiinfo2"), 0);
+}
+
+static INT_PTR Window_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
+{
+ CONTAINER_PARAM *param = (CONTAINER_PARAM*)lParam;
+ CONTAINER *pc = (CONTAINER*)calloc(1, sizeof(CONTAINER));
+ if (pc)
+ {
+ pc->cLetter = param->cLetter;
+ SetPropW(hwnd, CONTAINER_PROPW, pc);
+ }
+
+ MLSkinWindow2(plugin.hwndLibraryParent, hwnd, SKINNEDWND_TYPE_DIALOG, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ BOOL bVal = FALSE;
+ if (S_OK == Settings_GetBool(C_GLOBAL, GF_SHOWINFO, &bVal) && bVal)
+ SendMessageW(hwnd, WM_COMMAND, MAKEWPARAM(IDC_BTN_SHOWINFO, BN_CLICKED), (LPARAM)NULL);
+
+ if (!msgNotify) msgNotify = RegisterWindowMessageW(L"cdrom_notify_msg");
+ Plugin_RegisterListener(hwnd, msgNotify, pc->cLetter);
+
+ if (param->bQueryInfo) QueryDiscInfo(hwnd, pc->cLetter);
+
+ if (WASABI_API_APP)
+ {
+ HACCEL hAccel = WASABI_API_LOADACCELERATORSW(IDR_ACCELERATOR_VIEW);
+ if (hAccel) WASABI_API_APP->app_addAccelerators(hwnd, &hAccel, 1, TRANSLATE_MODE_CHILD);
+ WASABI_API_APP->ActiveDialog_Register(hwnd);
+ }
+
+ return 0;
+}
+
+static void Window_OnDestroy(HWND hdlg)
+{
+ Plugin_UnregisterListener(hdlg);
+ if (WASABI_API_APP) WASABI_API_APP->app_removeAccelerators(hdlg);
+
+ HWND hctrl = GetDlgItem(hdlg, IDC_MINIINFO);
+ if (hctrl && IsWindow(hctrl)) SENDMLIPC(hctrl, ML_IPC_WEBINFO_RELEASE, 0);
+
+ CONTAINER *pc = GetContainer(hdlg);
+ if (pc)
+ {
+ Settings_SetBool(C_GLOBAL, GF_SHOWINFO, pc->bInfoVisible);
+ RemovePropW(hdlg, CONTAINER_PROPW);
+ free(pc);
+ }
+}
+
+static void Window_OnDisplayChange(HWND hdlg, INT dpi, INT resX, INT resY)
+{
+ SendDlgItemMessageW(hdlg, IDC_CHILDVIEW, WM_DISPLAYCHANGE, dpi, MAKELPARAM(resX, resY));
+ SendDlgItemMessageW(hdlg, IDC_MINIINFO, WM_DISPLAYCHANGE, dpi, MAKELPARAM(resX, resY));
+ SendDlgItemMessageW(hdlg, IDC_COMMAND_BAR, WM_DISPLAYCHANGE, dpi, MAKELPARAM(resX, resY));
+}
+
+static void ViewContainer_OnWindowPosChanged(HWND hdlg, WINDOWPOS *pwp)
+{
+ RECT rc, rw;
+ HWND hctrl;
+ HDWP hdwp;
+ DWORD flags;
+
+ CONTAINER *pc = GetContainer(hdlg);
+ if (!pc) return;
+
+ if (SWP_FRAMECHANGED & pwp->flags)
+ {
+ ViewContainer_LayoutChildren(hdlg, (SWP_NOREDRAW & pwp->flags));
+ return;
+ }
+
+ if (0 != (SWP_NOSIZE & pwp->flags)) return;
+
+ if (!GetClientRect(hdlg, &rc)) return;
+
+ rc.right -= 2;
+
+ flags = SWP_NOACTIVATE | SWP_NOZORDER | ((SWP_NOREDRAW | SWP_NOCOPYBITS) & pwp->flags);
+
+ hdwp = BeginDeferWindowPos(3);
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_COMMAND_BAR)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, HWND_BOTTOM, rc.left, rc.bottom - (rw.bottom - rw.top), rc.right - rc.left, (rw.bottom - rw.top), flags);
+ rc.bottom -= (rw.bottom - rw.top + WINDOWS_SPACING);
+ }
+
+ if (pc->bInfoVisible && NULL != (hctrl = GetDlgItem(hdlg, IDC_MINIINFO)) && GetWindowRect(hctrl, &rw))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, HWND_BOTTOM, rc.left, rc.bottom - (rw.bottom - rw.top), rc.right - rc.left, (rw.bottom - rw.top), flags);
+ rc.bottom -= (rw.bottom - rw.top + WINDOWS_SPACING);
+ }
+
+ if (NULL != (hctrl = GetDlgItem(hdlg, IDC_CHILDVIEW)))
+ {
+ hdwp = DeferWindowPos(hdwp, hctrl, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, flags | SWP_NOMOVE);
+ }
+
+ EndDeferWindowPos(hdwp);
+}
+
+static void ViewContainer_ShowMiniInfo(HWND hdlg, BOOL bShow)
+{
+ CONTAINER *pc = GetContainer(hdlg);
+ if (!pc) return;
+
+ pc->bInfoVisible = FALSE;
+
+ HWND hInfo = GetDlgItem(hdlg, IDC_MINIINFO);
+ if (bShow && !hInfo) hInfo = CreateMiniInfoWindow(hdlg);
+
+ if (hInfo)
+ {
+ if (bShow)
+ {
+ pc->bInfoVisible = TRUE;
+ ViewContainer_LayoutChildren(hdlg, TRUE);
+ ShowWindow(hInfo, SW_SHOWNA);
+ UpdateWindow(hInfo);
+ }
+ else
+ {
+ ShowWindow(hInfo, SW_HIDE);
+ ViewContainer_LayoutChildren(hdlg, TRUE);
+ }
+ }
+
+ HWND hctrl = GetDlgItem(hdlg, IDC_CHILDVIEW);
+ if (hctrl && NULL != (hctrl = GetDlgItem(hctrl, IDC_BTN_SHOWINFO)))
+ SetWindowTextW(hctrl, WASABI_API_LNGSTRINGW((bShow) ? IDS_HIDE_INFO : IDS_SHOW_INFO));
+}
+
+static void Window_OnCommand(HWND hdlg, INT eventId, INT ctrlId, HWND hctrl)
+{
+ CONTAINER *pc = GetContainer(hdlg);
+ if (!pc) return;
+
+ switch(ctrlId)
+ {
+ case IDC_BTN_SHOWINFO:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ ViewContainer_ShowMiniInfo(hdlg, !pc->bInfoVisible);
+ break;
+ case BN_EX_GETTEXT:
+ {
+ if (IsWindow(hctrl))
+ {
+ // to ensure we're able to remove this for all views, we check this here
+ if (!ViewContainer_OnGetMiniInfoEnabled(hdlg))
+ DestroyWindow(hctrl);
+ else
+ SetWindowTextW(hctrl, WASABI_API_LNGSTRINGW((pc->bInfoVisible) ? IDS_HIDE_INFO : IDS_SHOW_INFO));
+ }
+ break;
+ }
+ }
+ break;
+ case ID_MINIINFO_SHOW: ViewContainer_ShowMiniInfo(hdlg, !pc->bInfoVisible); break;
+ case ID_EJECT_DISC: DriveManager_Eject(pc->cLetter, DM_EJECT_CHANGE); break;
+ case ID_COPY_SELECTION: SendDlgItemMessageW(hdlg, IDC_CHILDVIEW, WM_COMMAND, MAKEWPARAM(ctrlId, eventId), (LPARAM)hctrl); break;
+ }
+}
+
+static void Window_OnFileTagUpdated(HWND hdlg, WORD wCode, INT_PTR param)
+{
+ HWND hChild = GetDlgItem(hdlg, IDC_CHILDVIEW);
+ if (hChild && IsWindow(hChild))
+ {
+ SendMessageW(hChild, WM_TAGUPDATED, 0,
+ (LPARAM)((IPC_FILE_TAG_MAY_HAVE_UPDATED == wCode) ? AutoWideFn((LPCSTR)param) : (LPCWSTR)param));
+ }
+}
+
+static void Window_OnPluginNotify(HWND hdlg, WORD wCode, INT_PTR param)
+{
+ switch(wCode)
+ {
+ case DMW_MEDIUMARRIVED: Medium_OnArrived(hdlg, (CHAR)param); break;
+ case DMW_MEDIUMREMOVED: Medium_OnRemoved(hdlg, (CHAR)param); break;
+ case DMW_OPCOMPLETED: GetInfo_Completed(hdlg, (DM_NOTIFY_PARAM*)param); break;
+ case DMW_MODECHANGED: Drive_OnModeChanged(hdlg, (CHAR)LOWORD(param), (CHAR)(LOWORD(param) >> 8)); break;
+ case IPC_FILE_TAG_MAY_HAVE_UPDATED:
+ case IPC_FILE_TAG_MAY_HAVE_UPDATEDW: Window_OnFileTagUpdated(hdlg, wCode, param); break;
+ }
+}
+
+static INT_PTR Window_OnSendFileInfo(HWND hdlg, LPCWSTR pszFileName, UINT fFlags)
+{
+ WEBINFOSHOW wis;
+ HWND hInfo = GetDlgItem(hdlg, IDC_MINIINFO);
+ if (!hInfo) return 0;
+
+ wis.pszFileName = pszFileName;;
+ wis.fFlags = fFlags;
+ return (INT_PTR)SENDMLIPC(hInfo, ML_IPC_WEBINFO_SHOWINFO, (WPARAM)&wis);
+}
+
+static void Window_OnQueryFileInfo(HWND hdlg)
+{
+ HWND hChild = GetDlgItem(hdlg, IDC_CHILDVIEW);
+ if(!hChild || !PostMessageW(hChild, WM_QUERYFILEINFO, 0, 0L)) Window_OnSendFileInfo(hdlg, L"", WISF_FORCE);
+}
+
+INT_PTR CALLBACK CommandBar_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+static HWND ViewContainer_OnCreateCommandBar(HWND hdlg, CMDBARCREATESTRUCT *pcbcs)
+{
+ if (!pcbcs || !pcbcs->fnDialogProc) return NULL;
+ HWND hwndBar = WASABI_API_CREATEDIALOGPARAMW(pcbcs->resourceId, hdlg, CommandBar_DialogProc, (LPARAM)pcbcs);
+ if (hwndBar)
+ {
+ SetWindowLongPtrW(hwndBar, GWLP_ID, IDC_COMMAND_BAR);
+ ViewContainer_LayoutChildren(hdlg, TRUE);
+ ShowWindow(hwndBar, SW_SHOWNA);
+ }
+ return hwndBar;
+}
+
+static BOOL ViewContainer_OnDestroyCommandBar(HWND hdlg)
+{
+ HWND hwndBar = GetDlgItem(hdlg, IDC_COMMAND_BAR);
+ if (!hwndBar) return FALSE;
+
+ DestroyWindow(hwndBar);
+ ViewContainer_LayoutChildren(hdlg, TRUE);
+ return TRUE;
+}
+
+static BOOL ViewContainer_OnGetMiniInfoVisible(HWND hdlg)
+{
+ HWND hctrl = GetDlgItem(hdlg, IDC_MINIINFO);
+ return (hctrl && IsWindowVisible(hctrl));
+}
+
+static BOOL ViewContainer_OnExtractDisc(HWND hdlg, WPARAM wParam, LPARAM lParam)
+{
+ HWND hChild = GetDlgItem(hdlg, IDC_CHILDVIEW);
+ return (hChild) ? (BOOL)SendMessageW(hChild, WM_EXTRACTDISC, wParam, lParam) : FALSE;
+}
+
+static INT_PTR WINAPI DlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return (INT_PTR)Window_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ case WM_DESTROY: Window_OnDestroy(hdlg); break;
+ case WM_DISPLAYCHANGE: Window_OnDisplayChange(hdlg, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); break;
+ case WM_WINDOWPOSCHANGED: ViewContainer_OnWindowPosChanged(hdlg, (WINDOWPOS*)lParam); return TRUE;
+ case WM_COMMAND: Window_OnCommand(hdlg, HIWORD(wParam), LOWORD(wParam), (HWND)lParam); break;
+ case WM_QUERYFILEINFO: Window_OnQueryFileInfo(hdlg); return 1;
+ case WM_SHOWFILEINFO: MSGRESULT(hdlg, Window_OnSendFileInfo(hdlg, (LPCWSTR)lParam, (UINT)wParam));
+ case WM_EXTRACTDISC: MSGRESULT(hdlg, ViewContainer_OnExtractDisc(hdlg, wParam, lParam));
+ case VCM_CREATECOMMANDBAR: MSGRESULT(hdlg, ViewContainer_OnCreateCommandBar(hdlg, (CMDBARCREATESTRUCT*)lParam));
+ case VCM_DESTROYCOMMANDBAR: MSGRESULT(hdlg, ViewContainer_OnDestroyCommandBar(hdlg));
+ case VCM_GETCOMMANDBAR: MSGRESULT(hdlg, GetDlgItem(hdlg, IDC_COMMAND_BAR));
+ case VCM_GETMININFOENABLED: MSGRESULT(hdlg, ViewContainer_OnGetMiniInfoEnabled(hdlg));
+ case VCM_GETMININFOVISIBLE: MSGRESULT(hdlg, ViewContainer_OnGetMiniInfoVisible(hdlg));
+ case WM_USER + 0x200: MSGRESULT(hdlg, TRUE);
+ }
+
+ if (msgNotify == uMsg) Window_OnPluginNotify(hdlg, (WORD)wParam, (INT_PTR)lParam);
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/view_data.cpp b/Src/Plugins/Library/ml_disc/view_data.cpp
new file mode 100644
index 00000000..d21def96
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/view_data.cpp
@@ -0,0 +1,675 @@
+#include "main.h"
+#include <windowsx.h>
+#include "resource.h"
+#include "../winamp/wa_ipc.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/menushortcuts.h"
+#include "./commandbar.h"
+#include "./copyfiles.h"
+#include "./settings.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+#define IDT_NOTIFYINFO 1985
+#define IDT_UPDATESTATUS 1986
+#define DELAY_NOTIFYINFO 200
+#define DELAY_UPDATESTATUS 5
+
+#define IDC_FILEVIEW 10000
+
+#define DATAVIEW_PROPW L"DATAVIEW"
+
+#define STRCOMP_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+typedef struct _DATAVIEW
+{
+ CHAR cLetter;
+ WCHAR szSongInfoCache[MAX_PATH];
+} DATAVIEW;
+
+
+static INT_PTR WINAPI DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+INT_PTR WINAPI DataCmdBar_DialogProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void CALLBACK DataViewTimer_OnNotifyInfoElapsed(HWND hdlg, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
+static void CALLBACK DataViewTimer_OnStatusUpdateElapsed(HWND hdlg, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
+
+HWND CreateCdDataViewWindow(HWND hwndParent, CHAR cLetter)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_CDROM_DATA, hwndParent, DlgProc, (LPARAM)cLetter);
+}
+
+#define GetDataView(__hwnd) ((DATAVIEW*)GetPropW(__hwnd, DATAVIEW_PROPW))
+
+static void DataView_NotifyInfoWindow(HWND hdlg, INT iFile, BOOL bForceRefresh)
+{
+ DATAVIEW *pdv = GetDataView(hdlg);
+
+ HWND hParent = GetParent(hdlg);
+ if (hParent)
+ {
+ wchar_t szPath[MAX_PATH], *pszPath;
+ szPath[0] = L'\0';
+ if (-1 != iFile)
+ {
+ HWND hfv = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (hfv)
+ {
+ if (FileView_GetCurrentPath(hfv, szPath, ARRAYSIZE(szPath)) &&
+ S_OK == StringCchCatW(szPath, ARRAYSIZE(szPath), L"\\"))
+ {
+ FVITEM item;
+ item.mask = FVIF_TEXT | FVIF_TYPE;
+ INT l = lstrlenW(szPath);
+ pszPath = szPath + l;
+ item.cchTextMax = l;
+ item.pszText = pszPath;
+ item.cchTextMax = ARRAYSIZE(szPath) - item.cchTextMax;
+ if (!FileView_GetFile(hfv, iFile, &item) || FVFT_AUDIO != item.wType) szPath[0] = L'\0';
+ else if (item.pszText != szPath) StringCchCopy(szPath, ARRAYSIZE(szPath), item.pszText);
+ }
+ else szPath[0] = L'\0';
+ }
+ }
+
+ if (pdv && !bForceRefresh &&
+ CSTR_EQUAL == CompareString(STRCOMP_INVARIANT, NORM_IGNORECASE, pdv->szSongInfoCache, -1, szPath, -1))
+ {
+ return;
+ }
+ SendMessageW(hParent, WM_SHOWFILEINFO, (WPARAM)((bForceRefresh) ? WISF_FORCE : WISF_NORMAL), (LPARAM)szPath);
+ if (pdv) StringCchPrintfW(pdv->szSongInfoCache, sizeof(pdv->szSongInfoCache)/sizeof(pdv->szSongInfoCache[0]), szPath);
+ }
+}
+
+static void DataViewStatus_UpdateText(HWND hdlg)
+{
+ WCHAR szStatus[256] = {0};
+
+ HWND hFileView = GetDlgItem(hdlg, IDC_FILEVIEW);
+
+ HWND hbar = ViewContainer_GetCmdBar(GetParent(hdlg));
+ if (!hbar) return;
+
+ if (hFileView)
+ FileView_GetStatusText(hFileView, szStatus, sizeof(szStatus)/sizeof(szStatus[0]));
+ DataView_NotifyInfoWindow(hdlg, -1, FALSE);
+ SetWindowTextW(hbar, szStatus);
+}
+typedef struct _COLUMN
+{
+ INT id;
+ INT width;
+} COLUMN;
+
+static COLUMN defaultColumns[] =
+{
+ { FVCOLUMN_NAME, 200 },
+ { FVCOLUMN_SIZE, 82 },
+ { FVCOLUMN_TYPE, 64 },
+ { FVCOLUMN_MODIFIED, 140 },
+ { FVCOLUMN_EXTENSION, 64 },
+};
+
+static LPTSTR DataView_ColumnsToStr(LPTSTR pszText, size_t cchTextMax, COLUMN *pColumns, INT count)
+{
+ if (!pszText || cchTextMax < 1) return NULL;
+ pszText[0] = TEXT('\0');
+
+ if (!pColumns) return pszText;
+ LPTSTR pc = pszText;
+ for(int i = 0; i < count; i++)
+ {
+ HRESULT hr = StringCchPrintfEx(pc, cchTextMax, &pc, &cchTextMax, STRSAFE_IGNORE_NULLS,
+ TEXT("%c(%d, %d)"), ((0 == i) ? TEXT(' ') : TEXT(',')), pColumns[i].id, pColumns[i].width);
+ if (S_OK != hr) return NULL;
+ }
+ return pszText;
+}
+
+static void DataView_LoadFileViewColumns(HWND hdlg)
+{
+ UINT szColumns[256] = {0};
+ HWND hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (!hctrl) return;
+
+ INT count = FileView_GetColumnArray(hctrl, ARRAYSIZE(szColumns), szColumns);
+ for (int i = count-1; i >= 0; i--) FileView_DeleteColumn(hctrl, szColumns[i]);
+ TCHAR szColumnList[4096] = {0};
+
+ if (S_OK != Settings_ReadValue(C_DATAVIEW, DVF_COLUMNLIST, szColumnList, sizeof(szColumnList)) ||
+ TEXT('\0') == *szColumnList)
+ {
+ DataView_ColumnsToStr(szColumnList, ARRAYSIZE(szColumnList), defaultColumns, ARRAYSIZE(defaultColumns));
+ }
+
+ for (LPCTSTR pc = szColumnList, pBlock = NULL; TEXT('\0') != *pc; pc++)
+ {
+ if (TEXT('(') == *pc) pBlock = (pc + 1);
+ else if (TEXT(')') == *pc)
+ {
+ if (pBlock && pBlock != pc)
+ {
+ FVCOLUMN fvc;
+ fvc.mask = 0;
+ while (TEXT(' ') == *pBlock && pBlock != pc) pBlock++;
+ if (pBlock != pc)
+ {
+ fvc.id = StrToInt(pBlock);
+ while (TEXT(',') != *pBlock && pBlock != pc) pBlock++;
+ if (pBlock != pc)
+ {
+ while ((TEXT(',') == *pBlock || TEXT(' ') == *pBlock) && pBlock != pc) pBlock++;
+ if (pBlock != pc)
+ {
+ fvc.width = StrToInt(pBlock);
+ if (fvc.width > 0) fvc.mask |= FVCF_WIDTH;
+ }
+ }
+ FileView_InsertColumn(hctrl, &fvc);
+ }
+ }
+ pBlock = NULL;
+ }
+ }
+
+ INT orderBy, orderAsc;
+ Settings_GetInt(C_DATAVIEW, DVF_ORDERBY, &orderBy);
+ Settings_GetBool(C_DATAVIEW, DVF_ORDERASC, &orderAsc);
+ FileView_SetSort(hctrl, orderBy, orderAsc);
+}
+
+static void DataView_SaveFileViewColumns(HWND hdlg)
+{
+ UINT szOrder[256] = {0};
+ COLUMN szColumns[256] = {0};
+ TCHAR szBuffer[1024] = {0};
+
+ HWND hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (!hctrl) return;
+
+ INT count = FileView_GetColumnArray(hctrl, ARRAYSIZE(szOrder), szOrder);
+ for (int i = 0; i < count; i++)
+ {
+ szColumns[i].id = szOrder[i];
+ szColumns[i].width = FileView_GetColumnWidth(hctrl, szOrder[i]);
+ }
+ DataView_ColumnsToStr(szBuffer, ARRAYSIZE(szBuffer), szColumns, count);
+ Settings_SetString(C_DATAVIEW, DVF_COLUMNLIST, szBuffer);
+
+ DWORD sort = FileView_GetSort(hctrl);
+ Settings_SetInt(C_DATAVIEW, DVF_ORDERBY, LOWORD(sort));
+ Settings_SetBool(C_DATAVIEW, DVF_ORDERASC, HIWORD(sort));
+
+
+}
+static UINT DataView_ReadFileViewStyle()
+{
+ UINT style = 0;
+ INT nVal;
+
+ style = FVS_DETAILVIEW;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_VIEWMODE, &nVal))
+ {
+ switch(nVal)
+ {
+ case FVS_ICONVIEW:
+ case FVS_LISTVIEW:
+ style = nVal;
+ break;
+ }
+ }
+
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_SHOWAUDIO, &nVal) && nVal) style |= FVS_SHOWAUDIO;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_SHOWVIDEO, &nVal) && nVal) style |= FVS_SHOWVIDEO;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_SHOWPLAYLIST, &nVal) && nVal) style |= FVS_SHOWPLAYLIST;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_SHOWUNKNOWN, &nVal) && nVal) style |= FVS_SHOWUNKNOWN;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_HIDEEXTENSION, &nVal) && nVal) style |= FVS_HIDEEXTENSION;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_IGNOREHIDDEN, &nVal) && nVal) style |= FVS_IGNOREHIDDEN;
+ if (S_OK == Settings_GetInt(C_GLOBAL, GF_ENQUEUEBYDEFAULT, &nVal) && nVal)
+ style |= FVS_ENQUEUE;
+
+ return style;
+}
+
+static void DataView_SaveFileViewStyle(UINT uStyle)
+{
+ Settings_SetInt(C_DATAVIEW, DVF_VIEWMODE, (FVS_VIEWMASK & uStyle));
+ Settings_SetBool(C_DATAVIEW, DVF_SHOWAUDIO, (FVS_SHOWAUDIO & uStyle));
+ Settings_SetBool(C_DATAVIEW, DVF_SHOWVIDEO, (FVS_SHOWVIDEO & uStyle));
+ Settings_SetBool(C_DATAVIEW, DVF_SHOWPLAYLIST, (FVS_SHOWPLAYLIST & uStyle));
+ Settings_SetBool(C_DATAVIEW, DVF_SHOWUNKNOWN, (FVS_SHOWUNKNOWN & uStyle));
+ Settings_SetBool(C_DATAVIEW, DVF_HIDEEXTENSION, (FVS_HIDEEXTENSION & uStyle));
+ Settings_SetBool(C_DATAVIEW, DVF_IGNOREHIDDEN, (FVS_IGNOREHIDDEN & uStyle));
+}
+
+static BOOL DataView_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam)
+{
+ RECT rc;
+ HWND hctrl;
+
+ DATAVIEW *pdv = (DATAVIEW*)calloc(1, sizeof(DATAVIEW));
+ if (pdv)
+ {
+ pdv->cLetter = (CHAR)lParam;
+ pdv->szSongInfoCache[0] = L'\0';
+ SetPropW(hdlg, DATAVIEW_PROPW, pdv);
+ }
+
+ GetClientRect(hdlg, &rc);
+
+ MLSkinWindow2(plugin.hwndLibraryParent, hdlg, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ MLFILEVIEWCREATESTRUCT fvcs;
+ fvcs.hwndParent = hdlg;
+ fvcs.hwndInsertAfter = hdlg;
+ fvcs.fStyle = DataView_ReadFileViewStyle();
+ fvcs.x = 0;
+ fvcs.y = 0;
+ fvcs.cx = rc.right - rc.left;
+ fvcs.cy = rc.bottom - rc.top - 4;
+ hctrl = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_CREATEFILEVIEW, (WPARAM)&fvcs);
+
+ if (hctrl)
+ {
+ wchar_t szRoot[] = L"x:";
+ if (pdv) szRoot[0] = pdv->cLetter;
+ //szRoot[0] = L'C';
+
+ FileView_SetRoot(hctrl, szRoot);
+ SetWindowLongPtrW(hctrl, GWLP_ID, IDC_FILEVIEW);
+
+ DataView_LoadFileViewColumns(hdlg);
+ SetWindowLongPtrW(hctrl, GWL_STYLE, GetWindowLongPtrW(hctrl, GWL_STYLE) | WS_VISIBLE);
+
+ INT nPos;
+ if (S_OK == Settings_GetInt(C_DATAVIEW, DVF_DIVIDERPOS, &nPos))
+ FileView_SetDividerPos(hctrl, nPos, FVRF_NOREDRAW);
+ }
+
+ ViewContainer_CreateCmdBar(GetParent(hdlg), hdlg, IDD_COMMANDBAR_DATA, DataCmdBar_DialogProc, (ULONG_PTR)hctrl);
+
+ DataView_NotifyInfoWindow(hdlg, -1, TRUE); // ignore cache
+ SendMessage(hdlg, WM_COMMAND, MAKEWPARAM(ID_DRIVE_MODE_CHANGED, 0), 0L);
+
+ TCHAR szRoot[MAX_PATH] = {0};
+ if (hctrl && S_OK == Settings_ReadString(C_DATAVIEW, DVF_LASTFOLDER, szRoot, ARRAYSIZE(szRoot)))
+ FileView_SetCurrentPath(hctrl, szRoot, TRUE);
+
+ return FALSE;
+}
+
+static void DataView_OnDestroy(HWND hdlg)
+{
+ KillTimer(hdlg, IDT_NOTIFYINFO);
+ DataView_NotifyInfoWindow(hdlg, -1, FALSE);
+
+ HWND hView = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (hView)
+ {
+ WCHAR szRoot[MAX_PATH] = {0};
+ FileView_GetCurrentPath(hView, szRoot, ARRAYSIZE(szRoot));
+ Settings_SetString(C_DATAVIEW, DVF_LASTFOLDER, szRoot);
+
+ DataView_SaveFileViewColumns(hdlg);
+ DataView_SaveFileViewStyle(FileView_GetStyle(hView));
+
+ Settings_SetInt(C_DATAVIEW, DVF_DIVIDERPOS, FileView_GetDividerPos(hView));
+
+ }
+ DATAVIEW *pdv = GetDataView(hdlg);
+ if (pdv)
+ {
+ RemovePropW(hdlg, DATAVIEW_PROPW);
+ free(pdv);
+ }
+ ViewContainer_DestroyCmdBar(GetParent(hdlg));
+}
+
+static void CALLBACK DataViewTimer_OnStatusUpdateElapsed(HWND hdlg, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ KillTimer(hdlg, idEvent);
+ DataViewStatus_UpdateText(hdlg);
+
+ BOOL enablePlay = FALSE;
+ HWND hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+
+ if (hctrl && FileView_GetSelectedCount(hctrl) > 0)
+ {
+ enablePlay = (-1 != FileView_GetNextFile(hctrl, -1, FVNF_PLAYABLE | FVNF_SELECTED));
+ }
+
+ HWND hbar = ViewContainer_GetCmdBar(GetParent(hdlg));
+ if (hbar)
+ {
+ if (NULL != (hctrl = GetDlgItem(hbar, IDC_BTN_PLAYEX))) EnableWindow(hctrl, enablePlay);
+ if (NULL != (hctrl = GetDlgItem(hbar, IDC_BTN_COPY))) EnableWindow(hctrl, enablePlay);
+ }
+
+}
+
+static void CALLBACK DataViewTimer_OnNotifyInfoElapsed(HWND hdlg, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ KillTimer(hdlg, IDT_NOTIFYINFO);
+ HWND hFileView = GetDlgItem(hdlg, IDC_FILEVIEW);
+
+ INT iFile = -1;
+ if (hFileView)
+ {
+ iFile = FileView_GetSelectedCount(hFileView);
+ if (1 != iFile) iFile = -1;
+ else iFile = FileView_GetNextFile(hFileView, -1, FVNF_FOCUSED);
+ }
+
+ DataView_NotifyInfoWindow(hdlg, iFile, FALSE);
+}
+
+static void DataView_OnWindowPosChanged(HWND hdlg, WINDOWPOS *pwp)
+{
+ if (0 == (SWP_NOSIZE & pwp->flags))
+ {
+ HWND hctrl;
+ RECT rc, rw;
+ GetClientRect(hdlg, &rc);
+
+ hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (hctrl && GetWindowRect(hctrl, &rw))
+ {
+ MapWindowPoints(HWND_DESKTOP, hdlg, (POINT*)&rw, 2);
+ SetWindowPos(hctrl, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | ((SWP_NOREDRAW | SWP_NOCOPYBITS) & pwp->flags));
+ }
+ }
+}
+
+
+
+static void DataView_OnCopySelection(HWND hdlg)
+{
+ LPWSTR *ppszFiles = NULL;
+ ULONGLONG *pFSizes;
+ INT count, iFile;
+ size_t cchPath, cchFile;
+ WCHAR szPath[MAX_PATH] = {0};
+ FVITEM file = {0};
+
+ HWND hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (!hctrl) return;
+
+
+ count = FileView_GetSelectedCount(hctrl);
+ if (count < 1) return;
+
+ if (!FileView_GetCurrentPath(hctrl, szPath, sizeof(szPath)/sizeof(szPath[0])) ||
+ S_OK != StringCchLengthW(szPath, sizeof(szPath)/sizeof(szPath[0]), &cchPath)) return;
+
+ ppszFiles = (LPWSTR*)CoTaskMemAlloc(sizeof(LPWSTR)*count);
+ pFSizes = (ULONGLONG*)CoTaskMemAlloc(sizeof(ULONGLONG)*count);
+ if (!ppszFiles || !pFSizes)
+ {
+ if (ppszFiles) CoTaskMemFree(ppszFiles);
+ if (pFSizes) CoTaskMemFree(pFSizes);
+ return;
+ }
+
+ iFile = -1;
+ count = 0;
+ file.mask = FVIF_TEXT | FVIF_SIZE;
+ TCHAR szBuffer[MAX_PATH] = {0};
+ while (-1 != (iFile = FileView_GetNextFile(hctrl, iFile, FVNF_SELECTED | FVNF_PLAYABLE)))
+ {
+ file.pszText = szBuffer;
+ file.cchTextMax = ARRAYSIZE(szBuffer);
+ if (FileView_GetFile(hctrl, iFile, &file))
+ {
+ cchFile = (file.pszText) ? lstrlenW(file.pszText) : 0;
+ if (cchFile)
+ {
+ pFSizes[count] = (ULONGLONG)(((__int64)file.dwSizeHigh << 32) | file.dwSizeLow);
+ ppszFiles[count] = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR)*(cchPath + cchFile + 4));
+ if (ppszFiles[count])
+ {
+ PathCombineW(ppszFiles[count], szPath, file.pszText);
+ count++;
+ }
+ }
+ }
+ }
+
+ if (!MLDisc_CopyFiles(hdlg, ppszFiles, pFSizes, count))
+ {
+ if (ppszFiles)
+ {
+ for (int i = 0; i < count; i++) CoTaskMemFree(ppszFiles[i]);
+ CoTaskMemFree(ppszFiles);
+ }
+ if (pFSizes) CoTaskMemFree(pFSizes);
+ }
+}
+
+static void DataView_OnDriveModeChanged(HWND hdlg)
+{
+ DATAVIEW *pdv = GetDataView(hdlg);
+ if (!pdv) return;
+
+ HWND hbar;
+ if (NULL != (hbar = ViewContainer_GetCmdBar(GetParent(hdlg))))
+ {
+ HWND hctrl;
+ if (NULL != (hctrl = GetDlgItem(hbar, IDC_BTN_EJECT)))
+ {
+ UINT cMode = DriveManager_GetDriveMode(pdv->cLetter);
+ EnableWindow(hctrl, (DM_MODE_READY == cMode));
+ }
+ }
+
+}
+
+static void DataView_OnCommand(HWND hdlg, INT eventId, INT ctrlId, HWND hwndCtrl)
+{
+ switch(ctrlId)
+ {
+ case ID_COPY_SELECTION:
+ DataView_OnCopySelection(hdlg);
+ break;
+ case ID_DRIVE_MODE_CHANGED:
+ DataView_OnDriveModeChanged(hdlg);
+ break;
+ }
+}
+
+static void FileView_OnFolderChanged(HWND hdlg, NMHDR *phdr)
+{
+ DataView_NotifyInfoWindow(hdlg, -1, FALSE);
+ SetTimer(hdlg, IDT_UPDATESTATUS, DELAY_UPDATESTATUS, DataViewTimer_OnStatusUpdateElapsed);
+}
+
+static void FileView_OnStatusChanged(HWND hdlg, NMHDR *phdr)
+{
+ SetTimer(hdlg, IDT_UPDATESTATUS, DELAY_UPDATESTATUS, DataViewTimer_OnStatusUpdateElapsed);
+}
+
+static void FileView_OnFileStateChanged(HWND hdlg, NMFVSTATECHANGED *pnmsc)
+{
+ SetTimer(hdlg, IDT_UPDATESTATUS, DELAY_UPDATESTATUS, DataViewTimer_OnStatusUpdateElapsed);
+ SetTimer(hdlg, IDT_NOTIFYINFO, DELAY_NOTIFYINFO, DataViewTimer_OnNotifyInfoElapsed);
+}
+
+static void FileView_OnDoubleClick(HWND hdlg, NMFVFILEACTIVATE *pnma)
+{
+ if (-1 == pnma->iFile) return;
+
+ HWND hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (!hctrl) return;
+
+ SendMessageW(hctrl, WM_COMMAND,
+ MAKEWPARAM(FileView_GetActionCommand(hctrl, (FVS_ENQUEUE & (FileView_GetStyle(hctrl)) ? FVA_ENQUEUE : FVA_PLAY)), 0), 0L);
+}
+
+static void FileView_OnUninitOptionsMenu(HWND hdlg, HMENU hMenu, UINT uCommand)
+{
+ HWND hParent = GetParent(hdlg);
+ if (!hMenu || !hParent || !ViewContainer_GetMiniInfoEnabled(hParent)) return;
+ INT index = GetMenuItemCount(hMenu);
+
+ MENUITEMINFOW mii = { sizeof(MENUITEMINFOW), };
+ mii.fMask = MIIM_ID;
+ while (index--)
+ {
+ if (GetMenuItemInfoW(hMenu, index, TRUE, &mii) && ID_MINIINFO_SHOW == mii.wID)
+ {
+ if (DeleteMenu(hMenu, index, MF_BYPOSITION))
+ DeleteMenu(hMenu, --index, MF_BYPOSITION);
+ break;
+ }
+ }
+}
+
+static void FileView_OnInitOptionsMenu(HWND hdlg, HMENU hMenu)
+{
+ INT index;
+ HWND hParent;
+
+ hParent = GetParent(hdlg);
+ if (!hMenu || !hParent || !ViewContainer_GetMiniInfoEnabled(hParent)) return;
+
+ MENUITEMINFOW mii = { sizeof(MENUITEMINFOW), };
+
+ index = GetMenuItemCount(hMenu);
+
+ mii.fMask = MIIM_TYPE;
+ mii.fType = MFT_SEPARATOR;
+
+ if (InsertMenuItemW(hMenu, index, TRUE, &mii))
+ {
+ wchar_t szText[1024] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SHOW_INFO, szText, sizeof(szText)/sizeof(szText[0]));
+
+ if (WASABI_API_APP)
+ {
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(GetParent(hdlg), szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendShortcutText(szText, sizeof(szText)/sizeof(szText[0]), ID_MINIINFO_SHOW, szAccel, c, MSF_REPLACE);
+ }
+
+ index++;
+ mii.fMask = MIIM_STRING | MIIM_STATE | MIIM_ID;
+ mii.dwTypeData = szText;
+ mii.wID = ID_MINIINFO_SHOW;
+ mii.fState = (ViewContainer_GetMiniInfoVisible(hParent)) ? MFS_CHECKED : MFS_UNCHECKED;
+
+ InsertMenuItemW(hMenu, index, TRUE, &mii);
+ }
+}
+
+static void FileView_OnInitFileContextMenu(HWND hdlg, HMENU hMenu, HWND hView)
+{
+ HWND hbar = ViewContainer_GetCmdBar(GetParent(hdlg));
+ HWND hButton = (hbar) ? GetDlgItem(hbar, IDC_BTN_COPY) : NULL;
+ if (!hButton) return;
+
+ wchar_t szText[1024] = {0};
+ GetWindowTextW(hButton, szText, sizeof(szText)/sizeof(szText[0]));
+
+ if (WASABI_API_APP)
+ {
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(GetParent(hdlg), szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendShortcutText(szText, sizeof(szText)/sizeof(szText[0]), ID_COPY_SELECTION, szAccel, c, MSF_REPLACE);
+ }
+
+ MENUITEMINFOW mii = { sizeof(MENUITEMINFOW), };
+ mii.fMask = MIIM_STRING | MIIM_STATE | MIIM_ID;
+ mii.dwTypeData = szText;
+ mii.wID = ID_COPY_SELECTION; // TODO: make id uniqueue
+ mii.fState = (IsWindowEnabled(hButton)) ? MFS_ENABLED : MFS_DISABLED;
+
+ InsertMenuItemW(hMenu, 2, TRUE, &mii);
+}
+
+static BOOL FileView_OnInitMenu(HWND hdlg, NMFVMENU *pnm)
+{
+ switch(pnm->uMenuType)
+ {
+ case FVMENU_OPTIONS: FileView_OnInitOptionsMenu(hdlg, pnm->hMenu); break;
+ case FVMENU_FILEOPCONTEXT: FileView_OnInitFileContextMenu(hdlg, pnm->hMenu, pnm->hdr.hwndFrom); break;
+ }
+ return FALSE;
+}
+
+static void FileView_OnUninitMenu(HWND hdlg, NMFVMENU *pnm)
+{
+ switch(pnm->uMenuType)
+ {
+ case FVMENU_OPTIONS: FileView_OnUninitOptionsMenu(hdlg, pnm->hMenu, pnm->uCommand); break;
+ case FVMENU_FILEOPCONTEXT: DeleteMenu(pnm->hMenu, ID_COPY_SELECTION, MF_BYCOMMAND); break;
+ }
+}
+
+static BOOL FileView_OnMenuCommand(HWND hdlg, NMFVMENU *pnm)
+{
+ switch(pnm->uCommand)
+ {
+ case ID_MINIINFO_SHOW:
+ SendMessageW(GetParent(hdlg), WM_COMMAND, MAKEWPARAM(ID_MINIINFO_SHOW, 0), 0L);
+ return TRUE;
+ case ID_COPY_SELECTION:
+ SendMessageW(hdlg, WM_COMMAND, MAKEWPARAM(ID_COPY_SELECTION, 0), 0L);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static INT_PTR DataView_OnNotify(HWND hdlg, INT ctrlId, NMHDR *phdr)
+{
+ switch(phdr->idFrom)
+ {
+ case IDC_FILEVIEW:
+ switch(phdr->code)
+ {
+ case FVN_FOLDERCHANGED: FileView_OnFolderChanged(hdlg, phdr); break;
+ case FVN_STATECHANGED: FileView_OnFileStateChanged(hdlg, (NMFVSTATECHANGED*)phdr); break;
+ case FVN_STATUSCHANGED: FileView_OnStatusChanged(hdlg, phdr); break;
+ case NM_DBLCLK: FileView_OnDoubleClick(hdlg, (NMFVFILEACTIVATE*)phdr); break;
+ case FVN_INITMENU: return FileView_OnInitMenu(hdlg, (NMFVMENU*)phdr);
+ case FVN_UNINITMENU: FileView_OnUninitMenu(hdlg, (NMFVMENU*)phdr); break;
+ case FVN_MENUCOMMAND: return FileView_OnMenuCommand(hdlg, (NMFVMENU*)phdr);
+ }
+ break;
+ }
+ return 0;
+}
+
+
+static void DataView_OnQueryInfo(HWND hdlg)
+{
+ KillTimer(hdlg, IDT_NOTIFYINFO);
+ DataView_NotifyInfoWindow(hdlg, -1, TRUE);
+ SetTimer(hdlg, IDT_NOTIFYINFO, DELAY_NOTIFYINFO, DataViewTimer_OnNotifyInfoElapsed);
+}
+
+static void DataView_OnDisplayChange(HWND hdlg)
+{
+ HWND hctrl = GetDlgItem(hdlg, IDC_FILEVIEW);
+ if (!hctrl) return;
+ BOOL bVal;
+ Settings_GetBool(C_GLOBAL, GF_ENQUEUEBYDEFAULT, &bVal);
+ FileView_SetStyle(hctrl, (bVal) ? FVS_ENQUEUE : 0L, FVS_ENQUEUE);
+}
+
+static INT_PTR WINAPI DlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG: return DataView_OnInitDialog(hdlg, (HWND)wParam, lParam);
+ case WM_DESTROY: DataView_OnDestroy(hdlg); break;
+ case WM_WINDOWPOSCHANGED: DataView_OnWindowPosChanged(hdlg, (WINDOWPOS*)lParam); return TRUE;
+ case WM_COMMAND: DataView_OnCommand(hdlg, HIWORD(wParam), LOWORD(wParam), (HWND)lParam); break;
+ case WM_NOTIFY: MSGRESULT(hdlg, DataView_OnNotify(hdlg, (INT)wParam, (LPNMHDR) lParam));
+ case WM_QUERYFILEINFO: DataView_OnQueryInfo(hdlg); break;
+ case WM_DISPLAYCHANGE: DataView_OnDisplayChange(hdlg); break;
+
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/view_info.cpp b/Src/Plugins/Library/ml_disc/view_info.cpp
new file mode 100644
index 00000000..609405f9
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/view_info.cpp
@@ -0,0 +1,127 @@
+#include "main.h"
+#include "./resource.h"
+#include "../nu/DialogSkinner.h"
+
+#include <windowsx.h>
+
+static HBRUSH hbBack = NULL;
+
+static INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+// public function
+HWND CreateInfoWindow(HWND hwndParent, CHAR cLetter)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_INFO, hwndParent, DlgProc, (LPARAM)cLetter);
+}
+
+static INT_PTR Window_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
+{
+ SendMessageW(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(IDC_BTN_SHOWINFO, BN_EX_GETTEXT), (LPARAM)GetDlgItem(hwnd, IDC_BTN_SHOWINFO));
+ return 0;
+}
+
+static void Window_OnDestroy(HWND hwnd)
+{
+ if (hbBack)
+ {
+ DeleteObject(hbBack);
+ hbBack = NULL;
+ }
+}
+
+static void Window_OnDisplayChange(HWND hwnd, INT dpi, INT resX, INT resY)
+{
+ if (hbBack)
+ {
+ DeleteObject(hbBack);
+ hbBack = NULL;
+ }
+ RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
+}
+
+static void Window_OnWindowPosChanged(HWND hwnd, WINDOWPOS *pwp)
+{
+ HWND hwndCtrl;
+ RECT rw;
+
+ hwndCtrl = GetDlgItem(hwnd, IDC_BTN_SHOWINFO);
+ if(hwndCtrl)
+ {
+ GetWindowRect(hwndCtrl, &rw);
+ OffsetRect(&rw, -rw.left, -rw.top);
+ SetWindowPos(hwndCtrl, NULL, pwp->cx - rw.right, pwp->cy - rw.bottom, 0, 0,
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS);
+ }
+
+ hwndCtrl = GetDlgItem(hwnd, IDC_LBL_TEXT);
+ if(hwndCtrl)
+ {
+ GetWindowRect(hwndCtrl, &rw);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rw, 2);
+ SetWindowPos(hwndCtrl, NULL, 0, 0, pwp->cx - rw.left - 2, pwp->cy - 22 - rw.top,
+ SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS);
+ }
+ if (0 == (SWP_NOREDRAW & pwp->flags))
+ {
+ InvalidateRect(hwnd, NULL, TRUE);
+ RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN);
+ }
+}
+
+static void Window_OnCommand(HWND hwnd, INT eventId, INT ctrlId, HWND hwndCtrl)
+{
+ switch(ctrlId)
+ {
+ case IDC_BTN_SHOWINFO:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ SendMessageW(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(ctrlId, eventId),(LPARAM)hwndCtrl);
+ break;
+ }
+ break;
+ }
+}
+
+static HBRUSH Window_OnStaticColor(HWND hwnd, HDC hdc)
+{
+ if (!hbBack) hbBack = CreateSolidBrush(dialogSkinner.Color(WADLG_ITEMBG));
+ SetTextColor(hdc, dialogSkinner.Color(WADLG_ITEMFG));
+ SetBkColor(hdc, dialogSkinner.Color(WADLG_ITEMBG));
+ return hbBack;
+}
+
+static void Window_OnPaint(HWND hwnd)
+{
+ int tab[] = { IDC_LBL_TEXT | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwnd, tab, 1);
+}
+
+static void Window_OnQueryInfo(HWND hwnd)
+{
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ if (hwndParent) SendMessageW(hwndParent, WM_SHOWFILEINFO, (WPARAM)WISF_NORMAL, (LPARAM)L"");
+}
+
+static INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR result;
+
+ if (WM_CTLCOLORSTATIC == uMsg) return (INT_PTR)Window_OnStaticColor(hwnd, (HDC)wParam);
+
+ result = dialogSkinner.Handle(hwnd, uMsg, wParam, lParam);
+ if (result) return result;
+
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return (INT_PTR)Window_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: Window_OnDestroy(hwnd); break;
+ case WM_DISPLAYCHANGE: Window_OnDisplayChange(hwnd, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); break;
+ case WM_WINDOWPOSCHANGED: Window_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); break;
+ case WM_COMMAND: Window_OnCommand(hwnd, HIWORD(wParam), LOWORD(wParam), (HWND)lParam); break;
+ case WM_PAINT: Window_OnPaint(hwnd); break;
+ case WM_QUERYFILEINFO: Window_OnQueryInfo(hwnd); break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/view_ripburn.cpp b/Src/Plugins/Library/ml_disc/view_ripburn.cpp
new file mode 100644
index 00000000..b8e55e9c
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/view_ripburn.cpp
@@ -0,0 +1,567 @@
+#include "main.h"
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/ChildSizer.h"
+#include "config.h"
+#include ".\driveListBox.h"
+#include ".\infoBox.h"
+#include ".\primosdk_helper.h"
+#include <strsafe.h>
+
+static ChildWndResizeItem ripburn_rlist[]=
+{
+ {IDC_LBL_DRIVES, 0x0000},
+ {IDC_LIST_DRIVES, 0x0001},
+ {IDC_LBL_INFO_DRIVE, 0x0010},
+ {IDC_LBL_INFO_MEDIUM, 0x0011},
+ {IDC_LBL_DRIVE_LETTER_VAL, 0x0010},
+ {IDC_LBL_DRIVE_DESCRIPTION_VAL,0x0010},
+ {IDC_LBL_DRIVE_BUS_VAL, 0x0010},
+ {IDC_LBL_DRIVE_TYPES_VAL, 0x0010},
+ {IDC_LBL_MEDIUM_UPDATE, 0x0010},
+ {IDC_LBL_MEDIUM_CAPACITY_VAL, 0x0010},
+ {IDC_LBL_MEDIUM_TRACKN_VAL, 0x0010},
+ {IDC_LBL_MEDIUM_ERASEABLE_VAL, 0x0010},
+ {IDC_LBL_MEDIUM_RECORDABLE_VAL,0x0010},
+ {IDC_LBL_MEDIUM_FORMAT_VAL, 0x0010},
+ {IDC_LBL_MEDIUM_ADDINFO_VAL, 0x0010},
+ {IDC_LBL_MEDIUM_DISC_VAL, 0x0010},
+ {IDC_BTN_REFRESH, 0x0101},
+};
+
+static DriveListBox *driveListBox = NULL;
+static MLInfoBox *driveInfo = NULL;
+static MLInfoBox *mediumInfo = NULL;
+
+static HBRUSH lblHeaderBrush = NULL;
+static HBRUSH lblValueBrush = NULL;
+
+static UINT msgNotify = 0;
+static CHAR activeDrive = 0x00;
+
+static void CALLBACK FreeAsyncParam(DM_NOTIFY_PARAM *phdr)
+{
+ DM_UNITINFO_PARAM *pui = NULL;
+ DM_UNITINFO2_PARAM *pui2 = NULL;
+ if(!phdr) return;
+
+ switch(phdr->opCode)
+ {
+ case DMOP_UNITINFO:
+ pui = (DM_UNITINFO_PARAM*)phdr;
+ if (pui->pszDesc) free(pui->pszDesc);
+ break;
+ case DMOP_UNITINFO2:
+ pui2 = (DM_UNITINFO2_PARAM*)phdr;
+ if (pui2->pdwTypes) free(pui2->pdwTypes);
+ break;
+ }
+ free(phdr);
+}
+
+static void UpdateDriveInfo(HWND hwndDlg, CHAR cLetter)
+{
+ DM_NOTIFY_PARAM header = {0};
+ DM_UNITINFO_PARAM *pui = NULL;
+ DM_UNITINFO2_PARAM *pui2 = NULL;
+ DM_DISCINFOEX_PARAM *pdi = NULL;
+ DM_DISCINFO2_PARAM *pdi2 = NULL;
+ wchar_t message[128] = {0};
+
+ activeDrive = cLetter;
+
+ if(!PrimoSDKHelper_IsLoaded())
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_INFO_AVAILABLE,message,128);
+ }
+
+ SetDlgItemTextA(hwndDlg, IDC_LBL_DRIVE_LETTER_VAL, &cLetter);
+ SetDlgItemText(hwndDlg, IDC_LBL_DRIVE_DESCRIPTION_VAL, message);
+ SetDlgItemText(hwndDlg, IDC_LBL_DRIVE_BUS_VAL, message);
+ SetDlgItemText(hwndDlg, IDC_LBL_DRIVE_TYPES_VAL, message);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_DISC_VAL, NULL);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_CAPACITY_VAL, NULL);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_FORMAT_VAL, NULL);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_ERASEABLE_VAL, NULL);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_RECORDABLE_VAL, NULL);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_TRACKN_VAL, NULL);
+ SetDlgItemTextA(hwndDlg, IDC_LBL_MEDIUM_ADDINFO_VAL, NULL);
+
+ if (0 == activeDrive) return;
+
+ ZeroMemory(&header, sizeof(DM_NOTIFY_PARAM));
+
+ header.callback = (INT_PTR)hwndDlg;
+ header.uMsg = msgNotify;
+ header.cLetter = cLetter;
+ header.fnFree = FreeAsyncParam;
+
+ // request unitinfo
+ pui = (DM_UNITINFO_PARAM*)calloc(1, sizeof(DM_UNITINFO_PARAM));
+ if (pui)
+ {
+ CopyMemory(&pui->header, &header, sizeof(DM_NOTIFY_PARAM));
+ pui->header.fFlags = DMF_DESCRIPTION;
+ pui->cchDesc = 128;
+ pui->pszDesc = (CHAR*)calloc(pui->cchDesc, sizeof(CHAR));
+ DriveManager_GetUnitInfo(pui);
+ }
+
+ // request unitinfo2
+ pui2 = (DM_UNITINFO2_PARAM*)calloc(1, sizeof(DM_UNITINFO2_PARAM));
+ if (pui2)
+ {
+ CopyMemory(&pui2->header, &header, sizeof(DM_NOTIFY_PARAM));
+ pui2->header.fFlags = DMF_TYPES;
+ pui2->nTypes = 32;
+ pui2->pdwTypes = (DWORD*)calloc(pui2->nTypes, sizeof(DWORD));
+ DriveManager_GetUnitInfo2(pui2);
+ }
+
+ // request discinfoex
+ pdi = (DM_DISCINFOEX_PARAM*)calloc(1, sizeof(DM_DISCINFOEX_PARAM));
+ if (pdi)
+ {
+ CopyMemory(&pdi->header, &header, sizeof(DM_NOTIFY_PARAM));
+ pdi->header.fFlags = DMF_DRIVEMODE_DAO | DMF_MEDIUMTYPE | DMF_MEDIUMFORMAT | DMF_TRACKS | DMF_USED | DMF_FREE;
+ DriveManager_GetDiscInfoEx(pdi);
+ }
+
+ // request discinfo2
+ pdi2 = (DM_DISCINFO2_PARAM*)calloc(1, sizeof(DM_DISCINFO2_PARAM));
+ if (pdi2)
+ {
+ CopyMemory(&pdi2->header, &header, sizeof(DM_NOTIFY_PARAM));
+ pdi2->header.fFlags = DMF_MEDIUM | DMF_MEDIUMEX;
+ DriveManager_GetDiscInfo2(pdi2);
+ }
+}
+
+static BOOL CALLBACK EnumerateNavItemsCB(HNAVITEM hItem, DRIVE *pDrive, LPARAM param)
+{
+ if (!param) return FALSE;
+ if (pDrive) PostMessageW((HWND)param, msgNotify, (WPARAM)DMW_DRIVEADDED, (LPARAM)pDrive->cLetter);
+ return TRUE;
+}
+
+static void SwitchControlVisible(HWND hwndDlg, INT ctrlId, RECT *prcParent, BOOL hide, BOOL bInvalidate = FALSE)
+{
+ HWND hwndCtrl = GetDlgItem(hwndDlg, ctrlId);
+
+ if (hwndCtrl)
+ {
+ if (hide) ShowWindow(hwndCtrl, SW_HIDE);
+ else
+ {
+ RECT rc;
+ GetWindowRect(hwndCtrl, &rc);
+
+ BOOL bVisible = ((prcParent->right > rc.right) && (prcParent->bottom > rc.bottom));
+ if (bVisible != IsWindowVisible(hwndCtrl)) ShowWindow(hwndCtrl, (bVisible) ? SW_SHOWNORMAL : SW_HIDE);
+ if (bVisible && bInvalidate) InvalidateRect(hwndCtrl, NULL, TRUE);
+ }
+ }
+}
+
+static void ripburn_OnDisplayChanges(HWND hwndDlg)
+{
+ driveListBox->SetColors(dialogSkinner.Color(WADLG_ITEMBG),
+ dialogSkinner.Color(WADLG_ITEMBG),
+ dialogSkinner.Color(WADLG_ITEMFG),
+ dialogSkinner.Color(WADLG_ITEMFG),
+ dialogSkinner.Color(WADLG_WNDFG));
+ driveInfo->SetColors( dialogSkinner.Color(WADLG_ITEMBG),
+ dialogSkinner.Color(WADLG_LISTHEADER_FONTCOLOR),
+ dialogSkinner.Color(WADLG_LISTHEADER_BGCOLOR));
+ mediumInfo->SetColors( dialogSkinner.Color(WADLG_ITEMBG),
+ dialogSkinner.Color(WADLG_LISTHEADER_FONTCOLOR),
+ dialogSkinner.Color(WADLG_LISTHEADER_BGCOLOR));
+
+ if (lblHeaderBrush) DeleteObject(lblHeaderBrush);
+ lblHeaderBrush = NULL;
+
+ if (lblValueBrush) DeleteObject(lblValueBrush);
+ lblValueBrush = NULL;
+
+ // fixes the view not updating correctly on colour theme changes, etc
+ // NOTE: ideal would be using a LayoutWindows(..) method which would
+ // help to resolve this as things can be offloaded to gen_ml...
+ RECT rc;
+ GetClientRect(hwndDlg, &rc);
+ RedrawWindow(hwndDlg, &rc, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN | RDW_ERASENOW | RDW_UPDATENOW);
+}
+
+static void ripburn_OnInitDialog(HWND hwndDlg)
+{
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST_DRIVES);
+
+ driveListBox = new DriveListBox(IDC_LIST_DRIVES);
+ driveListBox->SetImages(plugin.hDllInstance, IDB_LISTBOX_BACK, IDB_LISTITEM_CDDRIVE);
+ driveListBox->Init(hwndList);
+
+ driveInfo = new MLInfoBox();
+ driveInfo->Init(GetDlgItem(hwndDlg, IDC_LBL_INFO_DRIVE));
+
+ mediumInfo = new MLInfoBox();
+ mediumInfo->Init(GetDlgItem(hwndDlg, IDC_LBL_INFO_MEDIUM));
+
+ UpdateDriveInfo(hwndDlg, 0);
+
+ childSizer.Init(hwndDlg,ripburn_rlist,sizeof(ripburn_rlist)/sizeof(ripburn_rlist[0]));
+ ripburn_OnDisplayChanges(hwndDlg);
+
+ if (!msgNotify) msgNotify = RegisterWindowMessageW(L"ripburn_notify_msg");
+
+ Plugin_EnumerateNavItems(EnumerateNavItemsCB, (LPARAM)hwndDlg);
+ Plugin_RegisterListener(hwndDlg, msgNotify, 0);
+}
+
+static void ripburn_OnDestroy(HWND hwndDlg)
+{
+ Plugin_UnregisterListener(hwndDlg);
+
+ HWND hwndLB = GetDlgItem(hwndDlg, IDC_LIST_DRIVES);
+ if (hwndLB)
+ {
+ INT index = (int)(INT_PTR)SendMessageW(hwndLB, LB_GETCURSEL, 0,0);
+ DWORD data = (LB_ERR != index) ? (DWORD)SendMessageW(hwndLB, LB_GETITEMDATA, index, 0) : 0;
+ if (data) g_config->WriteInt(L"last_drive", (CHAR)(0xFF & data));
+ }
+
+ if (lblHeaderBrush) DeleteObject(lblHeaderBrush);
+ lblHeaderBrush = NULL;
+ if (lblValueBrush) DeleteObject(lblValueBrush);
+ lblValueBrush = NULL;
+
+ if (driveListBox) delete(driveListBox);
+ driveListBox = NULL;
+ if (driveInfo) delete(driveInfo);
+ driveInfo = NULL;
+ if (mediumInfo) delete(mediumInfo);
+ mediumInfo = NULL;
+}
+
+static void ripburn_OnSize(HWND hwndDlg, int cx, int cy)
+{
+ RECT box;
+
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_LBL_INFO_DRIVE), &box);
+ BOOL hide = FALSE;
+
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_LETTER, &box, FALSE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_DESCRIPTION, &box, FALSE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_BUS, &box, FALSE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_TYPES,&box, FALSE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_LETTER_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_DESCRIPTION_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_BUS_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_DRIVE_TYPES_VAL, &box, hide, TRUE);
+
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_LBL_INFO_MEDIUM), &box);
+ hide = IsWindowVisible(GetDlgItem(hwndDlg, IDC_LBL_MEDIUM_UPDATE));
+ if (hide) InvalidateRect(GetDlgItem(hwndDlg, IDC_LBL_MEDIUM_UPDATE), NULL, TRUE);
+
+ if(PrimoSDKHelper_IsLoaded())
+ /*{
+ ShowWindow(GetDlgItem(hwndDlg, IDC_LBL_MEDIUM_UPDATE), SW_SHOW);
+ }
+ else*/
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_LBL_MEDIUM_UPDATE), SW_HIDE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_CAPACITY_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_FORMAT_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_ERASEABLE_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_RECORDABLE_VAL,&box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_TRACKN_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_DISC_VAL, &box, hide, TRUE);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_ADDINFO_VAL, &box, hide, TRUE);
+
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_TYPE, &box, hide);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_CAPACITY, &box, hide);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_FORMAT, &box, hide);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_ERASEABLE, &box, hide);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_RECORDABLE, &box, hide);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_TRACKN, &box, hide);
+ SwitchControlVisible(hwndDlg, IDC_LBL_MEDIUM_ADDINFO, &box, hide);
+ }
+}
+
+static int LabelColoring(HDC hdc, HWND hwndCtrl)
+{
+ switch(GetDlgCtrlID(hwndCtrl))
+ {
+ case IDC_LBL_DRIVES:
+ if(!lblHeaderBrush) lblHeaderBrush = CreateSolidBrush(dialogSkinner.Color(WADLG_LISTHEADER_BGCOLOR));
+ SetBkMode(hdc, TRANSPARENT);
+ SetTextColor(hdc, dialogSkinner.Color(WADLG_LISTHEADER_FONTCOLOR));
+ return (BOOL)(INT_PTR)lblHeaderBrush;
+ case IDC_LBL_MEDIUM_NOINFO:
+ case IDC_LBL_MEDIUM_CAPACITY_VAL:
+ case IDC_LBL_MEDIUM_TRACKN_VAL:
+ case IDC_LBL_MEDIUM_ERASEABLE_VAL:
+ case IDC_LBL_MEDIUM_RECORDABLE_VAL:
+ case IDC_LBL_MEDIUM_FORMAT_VAL:
+ case IDC_LBL_MEDIUM_DISC_VAL:
+ case IDC_LBL_MEDIUM_ADDINFO_VAL:
+ case IDC_LBL_DRIVE_LETTER_VAL:
+ case IDC_LBL_DRIVE_DESCRIPTION_VAL:
+ case IDC_LBL_DRIVE_BUS_VAL:
+ case IDC_LBL_DRIVE_TYPES_VAL:
+ case IDC_LBL_MEDIUM_UPDATE:
+ if(!lblValueBrush) lblValueBrush = CreateSolidBrush(dialogSkinner.Color(WADLG_ITEMBG));
+ SetBkColor(hdc, dialogSkinner.Color(WADLG_ITEMBG));
+ SetTextColor(hdc, dialogSkinner.Color(WADLG_ITEMFG));
+ return (BOOL)(INT_PTR)lblValueBrush;
+ case IDC_LBL_MEDIUM_CAPACITY:
+ case IDC_LBL_MEDIUM_TRACKN:
+ case IDC_LBL_MEDIUM_ERASEABLE:
+ case IDC_LBL_MEDIUM_RECORDABLE:
+ case IDC_LBL_MEDIUM_FORMAT:
+ case IDC_LBL_MEDIUM_ADDINFO:
+ case IDC_LBL_DRIVE_LETTER:
+ case IDC_LBL_DRIVE_DESCRIPTION:
+ case IDC_LBL_DRIVE_BUS:
+ case IDC_LBL_DRIVE_TYPES:
+ case IDC_LBL_MEDIUM_TYPE:
+ if(!lblValueBrush) lblValueBrush = CreateSolidBrush(dialogSkinner.Color(WADLG_ITEMBG));
+ SetBkMode(hdc, TRANSPARENT);
+ // SetBkColor(hdc, dialogSkinner.Color(WADLG_ITEMBG));
+ SetTextColor(hdc, dialogSkinner.Color(WADLG_ITEMFG));
+ return (BOOL)(INT_PTR)lblValueBrush;
+ }
+ return FALSE;
+}
+
+static void Drive_OnAdded(HWND hwndDlg, CHAR cLetter)
+{
+ HWND hwndLB = GetDlgItem(hwndDlg, IDC_LIST_DRIVES);
+ if (IsWindow(hwndLB))
+ {
+ wchar_t str[] = {cLetter, 0x00};
+ INT index = (INT)SendMessageW(hwndLB, LB_ADDSTRING, 0, (LPARAM)str);
+ if (LB_ERR != index)
+ {
+ SendMessageW(hwndLB, LB_SETITEMDATA, index, (LPARAM)cLetter);
+
+ INT idxSelection = (int)(INT_PTR)SendMessageW(hwndLB, LB_GETCURSEL, 0,0);
+ if (LB_ERR == idxSelection && cLetter == g_config->ReadInt(L"last_drive", cLetter))
+ {
+ if (LB_ERR != SendMessageW(hwndLB, LB_SETCURSEL, index, 0L))
+ {
+ UpdateDriveInfo(hwndDlg, cLetter);
+ }
+ }
+
+ // request unitinfo
+ DM_UNITINFO_PARAM *pui = (DM_UNITINFO_PARAM*)calloc(1, sizeof(DM_UNITINFO_PARAM));
+ if (pui)
+ {
+ pui->header.callback = (INT_PTR)hwndDlg;
+ pui->header.uMsg = msgNotify;
+ pui->header.cLetter = cLetter;
+ pui->header.fnFree = FreeAsyncParam;
+ DriveManager_GetUnitInfo(pui);
+ }
+ }
+ }
+}
+
+static INT GetListBoxIndex(HWND hwndLB, CHAR cLetter)
+{
+ wchar_t str[] = {cLetter, 0x00};
+ return (cLetter && hwndLB) ? (INT)SendMessageW(hwndLB, LB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)str) : LB_ERR;
+}
+
+static void Drive_OnRemoved(HWND hwndDlg, CHAR cLetter)
+{
+ HWND hwndLB = GetDlgItem(hwndDlg, IDC_LIST_DRIVES);
+
+ if (IsWindow(hwndLB))
+ {
+ INT index = GetListBoxIndex(hwndLB, cLetter);
+ if (LB_ERR != index) SendMessageW(hwndLB, LB_DELETESTRING, (WPARAM)index, 0L);
+ }
+}
+
+static void GetInfo_Completed(HWND hwndDlg, DM_NOTIFY_PARAM *phdr)
+{
+ wchar_t szBuffer[256] = {0};
+
+ DM_UNITINFO_PARAM *pui = NULL;
+ DM_UNITINFO2_PARAM *pui2 = NULL;
+ DM_DISCINFOEX_PARAM *pdi = NULL;
+ DM_DISCINFO2_PARAM *pdi2 = NULL;
+
+ switch(phdr->opCode)
+ {
+ case DMOP_UNITINFO:
+ pui = (DM_UNITINFO_PARAM*)phdr;
+ if (0 == phdr->result)
+ {
+ HWND hwndLB = GetDlgItem(hwndDlg, IDC_LIST_DRIVES);
+ if (hwndLB)
+ {
+ INT idxLB = GetListBoxIndex(hwndLB, phdr->cLetter);
+ if (LB_ERR != idxLB)
+ {
+ DWORD data = MAKELONG(phdr->cLetter, pui->dwType);
+ if (data != (DWORD)SendMessage(hwndLB ,LB_GETITEMDATA, idxLB, 0))
+ {
+ if (LB_ERR != SendMessageW(hwndLB, LB_SETITEMDATA, idxLB, (LPARAM)data))
+ {
+ RECT rc;
+ SendMessageW(hwndLB, LB_GETITEMRECT, idxLB, (LPARAM)&rc);
+ InvalidateRect(hwndLB, &rc, FALSE);
+ UpdateWindow(hwndLB);
+ }
+ }
+ }
+ }
+ if (activeDrive == phdr->cLetter && pui->pszDesc) SetDlgItemTextA(hwndDlg, IDC_LBL_DRIVE_DESCRIPTION_VAL, (pui->cchDesc > 0) ? pui->pszDesc : "");
+ }
+ break;
+ case DMOP_UNITINFO2:
+ pui2 = (DM_UNITINFO2_PARAM*)phdr;
+ if (0 == phdr->result && activeDrive == phdr->cLetter)
+ {
+ SetDlgItemTextW(hwndDlg, IDC_LBL_DRIVE_BUS_VAL, Drive_GetBusTypeString(pui2->dwBusType));
+ szBuffer[0] = 0x00;
+ for (int i = 0; i < pui2->nTypes; i++)
+ {
+ if (0 != i) StringCchCatW(szBuffer, sizeof(szBuffer)/sizeof(wchar_t), L", ");
+ StringCchCatW(szBuffer, sizeof(szBuffer)/sizeof(wchar_t), Drive_GetTypeString(pui2->pdwTypes[i]));
+ }
+ SetDlgItemTextW(hwndDlg, IDC_LBL_DRIVE_TYPES_VAL, szBuffer);
+ }
+ break;
+ case DMOP_DISCINFO:
+ pdi = (DM_DISCINFOEX_PARAM*)phdr;
+ if (0 == phdr->result && activeDrive == phdr->cLetter)
+ {
+ StringCchPrintfW(szBuffer, sizeof(szBuffer)/sizeof(wchar_t),
+ WASABI_API_LNGSTRINGW(IDS_X_OF_X_SECTORS_FREE),
+ pdi->dwFree, pdi->dwUsed + pdi->dwFree);
+ SetDlgItemTextW(hwndDlg, IDC_LBL_MEDIUM_CAPACITY_VAL, szBuffer);
+ SetDlgItemInt(hwndDlg, IDC_LBL_MEDIUM_TRACKN_VAL, pdi->dwTracks, FALSE);
+ SetDlgItemText(hwndDlg, IDC_LBL_MEDIUM_ERASEABLE_VAL, WASABI_API_LNGSTRINGW((pdi->bErasable) ? IDS_YES : IDS_NO));
+ SetDlgItemText(hwndDlg, IDC_LBL_MEDIUM_RECORDABLE_VAL, WASABI_API_LNGSTRINGW((Medium_IsRecordableType(pdi->dwMediumType)) ? IDS_YES : IDS_NO));
+ SetDlgItemText(hwndDlg, IDC_LBL_MEDIUM_ADDINFO_VAL, Medium_GetTypeString(pdi->dwMediumType));
+ SetDlgItemText(hwndDlg, IDC_LBL_MEDIUM_FORMAT_VAL, Medium_GetFormatString(pdi->dwMediumFormat));
+ }
+ break;
+ case DMOP_DISCINFO2:
+ pdi2 = (DM_DISCINFO2_PARAM*)phdr;
+ if (0 == phdr->result && activeDrive == phdr->cLetter)
+ {
+ SetDlgItemTextW(hwndDlg, IDC_LBL_MEDIUM_DISC_VAL, Medium_GetPhysicalTypeString(pdi2->dwMediumEx));
+ }
+ break;
+ }
+}
+
+static void View_OnPluginNotify(HWND hwndDlg, WORD wCode, INT_PTR param)
+{
+ switch(wCode)
+ {
+ case DMW_DRIVEADDED: Drive_OnAdded(hwndDlg, (CHAR)param); break;
+ case DMW_DRIVEREMOVED: Drive_OnRemoved(hwndDlg, (CHAR)param); break;
+ case DMW_MEDIUMARRIVED:
+ case DMW_MEDIUMREMOVED: if ((CHAR)param == activeDrive) UpdateDriveInfo(hwndDlg, activeDrive); break;
+ case DMW_OPCOMPLETED:
+ SendMessage(hwndDlg, WM_SIZE, 0, 0);
+ GetInfo_Completed(hwndDlg, (DM_NOTIFY_PARAM*)param);
+ break;
+ }
+}
+
+static INT_PTR ListBox_OnKeyPressed(HWND hwndDlg, HWND hwndLB, WORD wKey, INT iCurret)
+{
+ switch(wKey)
+ {
+ case VK_F5: DriveManager_Update(TRUE); return -2;
+ case VK_SPACE:
+ PostMessageW(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_LIST_DRIVES,LBN_DBLCLK), (LPARAM)hwndLB);
+ return -2;
+ }
+ if (wKey >= 'A' && wKey <= 'Z')
+ {
+ INT index = GetListBoxIndex(hwndLB, (CHAR)wKey);
+ return (LB_ERR != index) ? index : -2;
+ }
+
+ return -1; // do default
+}
+
+INT_PTR CALLBACK view_ripburnDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ INT_PTR a;
+
+ if (uMsg == WM_CTLCOLORSTATIC )
+ {
+ a = LabelColoring((HDC)wParam, (HWND) lParam);
+ if (a) return a;
+ }
+
+ a = driveListBox->HandleMsgProc(uMsg,wParam,lParam); if (a) return a;
+ a = dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ ripburn_OnInitDialog(hwndDlg);
+ break;
+ case WM_DISPLAYCHANGE:
+ ripburn_OnDisplayChanges(hwndDlg);
+ break;
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ {
+ childSizer.Resize(hwndDlg,ripburn_rlist,sizeof(ripburn_rlist)/sizeof(ripburn_rlist[0]));
+ ripburn_OnSize(hwndDlg, LOWORD(lParam), HIWORD(lParam));
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_LIST_DRIVES:
+ if (HIWORD(wParam) == LBN_SELCHANGE)
+ {
+ INT index = (int)(INT_PTR)SendMessage((HWND)lParam, LB_GETCURSEL, 0,0);
+ DWORD data = (LB_ERR != index) ? (DWORD)SendMessage((HWND)lParam ,LB_GETITEMDATA, index, 0) : 0;
+ if (data) UpdateDriveInfo(hwndDlg, (CHAR)(0xFF & data));
+ }
+ else if (HIWORD(wParam) == LBN_DBLCLK)
+ {
+ INT index = (int)(INT_PTR)SendMessage((HWND)lParam, LB_GETCURSEL, 0,0);
+ DWORD data = (LB_ERR != index) ? (DWORD)SendMessage((HWND)lParam ,LB_GETITEMDATA, index, 0) : 0;
+ HNAVITEM hItem = (data) ? Plugin_GetNavItemFromLetter((CHAR)(0xFF & data)) : NULL;
+ if (hItem) MLNavItem_Select(plugin.hwndLibraryParent, hItem);
+ }
+ break;
+ case IDC_BTN_REFRESH:
+ if (HIWORD(wParam) == BN_CLICKED) DriveManager_Update(TRUE);
+ break;
+ }
+ break;
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST_DRIVES | DCW_SUNKENBORDER,
+ IDC_LBL_DRIVES | DCW_SUNKENBORDER,
+ IDC_LBL_INFO_DRIVE | DCW_SUNKENBORDER,
+ IDC_LBL_INFO_MEDIUM | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 4);
+ }
+ return 0;
+ case WM_DESTROY:
+ ripburn_OnDestroy(hwndDlg);
+ break;
+ case WM_ERASEBKGND:
+ return 0;
+ case WM_VKEYTOITEM:
+ return ListBox_OnKeyPressed(hwndDlg, (HWND)lParam, LOWORD(wParam), HIWORD(wParam));
+ }
+ if (msgNotify == uMsg)
+ View_OnPluginNotify(hwndDlg, (WORD)wParam, (INT_PTR)lParam);
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_disc/view_wait.cpp b/Src/Plugins/Library/ml_disc/view_wait.cpp
new file mode 100644
index 00000000..17a8fa6f
--- /dev/null
+++ b/Src/Plugins/Library/ml_disc/view_wait.cpp
@@ -0,0 +1,140 @@
+#include "main.h"
+#include "./resource.h"
+#include "../nu/DialogSkinner.h"
+
+#include <windowsx.h>
+
+static HBRUSH hbBack = NULL;
+#define TIMER_SHOWTEXT_ID 1985
+#define TIMER_SHOWTEXT_DELAY 1000
+
+static INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+// public function
+HWND CreateWaitWindow(HWND hwndParent, CHAR cLetter)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_WAIT, hwndParent, DlgProc, (LPARAM)cLetter);
+}
+
+static void CALLBACK Window_TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ switch(idEvent)
+ {
+ case TIMER_SHOWTEXT_ID:
+ KillTimer(hwnd, TIMER_SHOWTEXT_ID);
+ SetDlgItemTextW(hwnd, IDC_LBL_TEXT, WASABI_API_LNGSTRINGW(IDS_READINGDISC));
+ break;
+ }
+}
+
+static INT_PTR Window_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
+{
+ SendMessageW(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(IDC_BTN_SHOWINFO, BN_EX_GETTEXT), (LPARAM)GetDlgItem(hwnd, IDC_BTN_SHOWINFO));
+ SetTimer(hwnd, TIMER_SHOWTEXT_ID, TIMER_SHOWTEXT_DELAY, Window_TimerProc);
+ return 0;
+}
+
+static void Window_OnDestroy(HWND hwnd)
+{
+ if (hbBack)
+ {
+ DeleteObject(hbBack);
+ hbBack = NULL;
+ }
+}
+
+static void Window_OnDisplayChange(HWND hwnd, INT dpi, INT resX, INT resY)
+{
+ if (hbBack)
+ {
+ DeleteObject(hbBack);
+ hbBack = NULL;
+ }
+ RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
+}
+
+static void Window_OnWindowPosChanged(HWND hwnd, WINDOWPOS *pwp)
+{
+ HWND hwndCtrl;
+ RECT rw;
+
+ hwndCtrl = GetDlgItem(hwnd, IDC_BTN_SHOWINFO);
+ if(hwndCtrl)
+ {
+ GetWindowRect(hwndCtrl, &rw);
+ OffsetRect(&rw, -rw.left, -rw.top);
+ SetWindowPos(hwndCtrl, NULL, pwp->cx - rw.right, pwp->cy - rw.bottom, 0, 0,
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS);
+ }
+
+ hwndCtrl = GetDlgItem(hwnd, IDC_LBL_TEXT);
+ if(hwndCtrl)
+ {
+ GetWindowRect(hwndCtrl, &rw);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rw, 2);
+ SetWindowPos(hwndCtrl, NULL, 0, 0, pwp->cx - rw.left - 2, pwp->cy - 22 - rw.top,
+ SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS);
+ }
+ if (0 == (SWP_NOREDRAW & pwp->flags))
+ {
+ InvalidateRect(hwnd, NULL, TRUE);
+ RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN);
+ }
+}
+
+static void Window_OnCommand(HWND hwnd, INT eventId, INT ctrlId, HWND hwndCtrl)
+{
+ switch(ctrlId)
+ {
+ case IDC_BTN_SHOWINFO:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ SendMessageW(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(ctrlId, eventId),(LPARAM)hwndCtrl);
+ break;
+ }
+ break;
+ }
+}
+
+static HBRUSH Window_OnStaticColor(HWND hwnd, HDC hdc)
+{
+ if (!hbBack) hbBack = CreateSolidBrush(dialogSkinner.Color(WADLG_ITEMBG));
+ SetTextColor(hdc, dialogSkinner.Color(WADLG_ITEMFG));
+ SetBkColor(hdc, dialogSkinner.Color(WADLG_ITEMBG));
+ return hbBack;
+}
+
+static void Window_OnPaint(HWND hwnd)
+{
+ int tab[] = { IDC_LBL_TEXT | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwnd, tab, 1);
+}
+
+static void Window_OnQueryInfo(HWND hwnd)
+{
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ if (hwndParent) SendMessageW(hwndParent, WM_SHOWFILEINFO, (WPARAM)WISF_NORMAL, (LPARAM)L"");
+}
+
+static INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR result;
+
+ if (WM_CTLCOLORSTATIC == uMsg) return (INT_PTR)Window_OnStaticColor(hwnd, (HDC)wParam);
+ result = dialogSkinner.Handle(hwnd, uMsg, wParam, lParam);
+ if (result) return result;
+
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return (INT_PTR)Window_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: Window_OnDestroy(hwnd); break;
+ case WM_DISPLAYCHANGE: Window_OnDisplayChange(hwnd, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); break;
+ case WM_WINDOWPOSCHANGED: Window_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); break;
+ case WM_COMMAND: Window_OnCommand(hwnd, HIWORD(wParam), LOWORD(wParam), (HWND)lParam); break;
+ case WM_PAINT: Window_OnPaint(hwnd); break;
+ case WM_QUERYFILEINFO: Window_OnQueryInfo(hwnd); break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/AtomParse.h b/Src/Plugins/Library/ml_downloads/AtomParse.h
new file mode 100644
index 00000000..ab76a1f9
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/AtomParse.h
@@ -0,0 +1,51 @@
+#ifndef NULLSOFT_ATOMPARSEH
+#define NULLSOFT_ATOMPARSEH
+#if 0
+#include "RFCDate.h"
+#include "XMLNode.h"
+#include "Feeds.h"
+#include "../nu/AutoChar.h"
+#include "ChannelSync.h"
+
+void ReadAtomItem(XMLNode *item, Channel &channel)
+{
+}
+
+void ReadAtomChannel(XMLNode *node, Channel &newChannel)
+{
+ XMLNode *curNode = 0;
+ curNode = node->Get(L"title");
+ if (curNode)
+ newChanneltitle = curNode->content;
+
+ curNode = node->Get(L"subtitle");
+ if (curNode)
+ newChannel.description = curNode->content;
+
+ XMLNode::NodeList &links = node->GetList(L"link");
+ XMLNode::NodeList::iterator linkItr;
+ for (linkItr=links.begin();linkItr!=links.end();linkItr++)
+ {
+ if ((*linkItr)->properties[L"rel"].empty()
+ || (*linkItr)->properties[L"rel"]== L"alternate")
+ newChannel.link = (*linkItr)->properties[L"href"];
+ }
+
+ XMLNode::NodeList &entries = node->GetList(L"entry");
+ XMLNode::NodeList::iterator entryItr;
+ for (entryItr=entries.begin();entryItr!=entries.end();entryItr++)
+ {
+ }
+}
+
+void ReadAtom(XMLNode *atom, ChannelSync *sync)
+{
+ sync->BeginChannelSync();
+ Channel newChannel;
+ ReadAtomChannel(atom, newChannel);
+ sync->NewChannel(newChannel);
+
+ sync->EndChannelSync();
+}
+#endif
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DESIGN.txt b/Src/Plugins/Library/ml_downloads/DESIGN.txt
new file mode 100644
index 00000000..47148235
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DESIGN.txt
@@ -0,0 +1,12 @@
+
+
+
+
+major components:
+
+Cloud
+-----
+
+The cloud is the object which is responsible for doing period updates on RSS feeds.
+It automatically scans the feeds. You can manually refresh via the Refresh(string url) method
+
diff --git a/Src/Plugins/Library/ml_downloads/Defaults.cpp b/Src/Plugins/Library/ml_downloads/Defaults.cpp
new file mode 100644
index 00000000..0478d92b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Defaults.cpp
@@ -0,0 +1,43 @@
+#include "main.h"
+#include "Defaults.h"
+#include <shlobj.h>
+wchar_t defaultDownloadPath[MAX_PATH] = {0};
+bool needToMakePodcastsView=true;
+
+static BOOL UtilGetSpecialFolderPath( HWND hwnd, wchar_t *path, int folder )
+{
+ ITEMIDLIST *pidl; // Shell Item ID List ptr
+ IMalloc *imalloc; // Shell IMalloc interface ptr
+ BOOL result; // Return value
+
+ if ( SHGetSpecialFolderLocation( hwnd, folder, &pidl ) != NOERROR )
+ return FALSE;
+
+ result = SHGetPathFromIDList( pidl, path );
+
+ if ( SHGetMalloc( &imalloc ) == NOERROR )
+ {
+ imalloc->Free( pidl );
+ imalloc->Release();
+ }
+
+ return result;
+}
+
+static void BuildDefaultDownloadPath( HWND hwnd )
+{
+ wchar_t defaultPath[ MAX_PATH ] = L"";
+ if ( !UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_MYMUSIC ) )
+ UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_PERSONAL );
+
+ lstrcpyn( defaultDownloadPath, defaultPath, MAX_PATH );
+}
+
+/*
+Requires an HWND because some of the shell functions require one. Theoretically it could be NULL
+*/
+void BuildDefaults( HWND hwnd )
+{
+ BuildDefaultDownloadPath( hwnd );
+}
+
diff --git a/Src/Plugins/Library/ml_downloads/Defaults.h b/Src/Plugins/Library/ml_downloads/Defaults.h
new file mode 100644
index 00000000..52afbd60
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Defaults.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_DEFAULTSH
+#define NULLSOFT_DEFAULTSH
+#include <windows.h>
+
+extern wchar_t defaultDownloadPath[MAX_PATH];
+
+#define DOWNLOADSSOURCEWIDTHDEFAULT 200
+#define DOWNLOADSTITLEWIDTHDEFAULT 200
+#define DOWNLOADSPROGRESSWIDTHDEFAULT 100
+#define DOWNLOADSDATEWIDTHDEFAULTS 100
+#define DOWNLOADSSIZEWIDTHDEFAULTS 100
+#define DOWNLOADSPATHWIDTHDEFAULTS 200
+
+extern int downloadsSourceWidth,
+ downloadsTitleWidth,
+ downloadsProgressWidth,
+ downloadsPathWidth,
+ downloadsSizeWidth,
+ downloadsDateWidth;
+
+extern bool needToMakePodcastsView;
+
+void BuildDefaults(HWND);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadManager.cpp b/Src/Plugins/Library/ml_downloads/DownloadManager.cpp
new file mode 100644
index 00000000..61f734b2
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadManager.cpp
@@ -0,0 +1 @@
+#include "main.h" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadStatus.cpp b/Src/Plugins/Library/ml_downloads/DownloadStatus.cpp
new file mode 100644
index 00000000..fb11a773
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadStatus.cpp
@@ -0,0 +1,158 @@
+#include "main.h"
+#include "api__ml_downloads.h"
+#include "DownloadStatus.h"
+#include "DownloadsDialog.h"
+
+#include <strsafe.h>
+
+DownloadStatus downloadStatus;
+
+using namespace Nullsoft::Utility;
+
+DownloadStatus::Status::Status()
+{
+ Init();
+}
+
+DownloadStatus::Status::Status( size_t _downloaded, size_t _maxSize, const wchar_t *_source, const wchar_t *_title, const wchar_t *_path )
+{
+ Init();
+
+ downloaded = _downloaded;
+ maxSize = _maxSize;
+ source = _wcsdup( _source );
+ title = _wcsdup( _title );
+ path = _wcsdup( _path );
+}
+
+const DownloadStatus::Status &DownloadStatus::Status::operator =( const DownloadStatus::Status &copy )
+{
+ Reset();
+ Init();
+
+ downloaded = copy.downloaded;
+ maxSize = copy.maxSize;
+ source = _wcsdup( copy.source );
+ title = _wcsdup( copy.title );
+ path = _wcsdup( copy.path );
+ killswitch = copy.killswitch;
+
+ return *this;
+}
+
+DownloadStatus::Status::~Status()
+{
+ Reset();
+}
+
+void DownloadStatus::Status::Init()
+{
+ downloaded = 0;
+ maxSize = 0;
+ killswitch = 0;
+ source = 0;
+ title = 0;
+ path = 0;
+}
+
+void DownloadStatus::Status::Reset()
+{
+ if ( source )
+ {
+ free( source );
+ source = 0;
+ }
+
+ if ( title )
+ {
+ free( title );
+ title = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+}
+
+void DownloadStatus::AddDownloadThread(DownloadToken token, const wchar_t *source, const wchar_t *title, const wchar_t *path)
+{
+ {
+ AutoLock lock(statusLock);
+ downloads[token] = Status(0,0,source,title,path);
+ DownloadsUpdated(downloads[token],token);
+ }
+
+ static pluginMessage p = {ML_MSG_DOWNLOADS_VIEW_POSITION, 0, 0, 0};
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE);
+}
+
+void DownloadStatus::DownloadThreadDone(DownloadToken token)
+{
+ {
+ AutoLock lock(statusLock);
+ downloads.erase(token);
+ }
+
+ static pluginMessage p = {ML_MSG_DOWNLOADS_VIEW_POSITION, 0, 0, 0};
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE);
+}
+
+bool DownloadStatus::UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize)
+{
+ AutoLock lock(statusLock);
+ downloads[token].downloaded = downloaded;
+ downloads[token].maxSize = maxSize;
+
+ return !!downloads[token].killswitch;
+}
+
+bool DownloadStatus::CurrentlyDownloading()
+{
+ AutoLock lock(statusLock);
+ return !downloads.empty();
+}
+
+void DownloadStatus::GetStatusString( wchar_t *status, size_t len )
+{
+ AutoLock lock( statusLock );
+ Downloads::iterator itr;
+ size_t bytesDownloaded = 0, bytesTotal = 0, numDownloads = 0;
+ bool unknownTotal = false;
+ for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
+ {
+ Status &dlstatus = itr->second;
+ if ( dlstatus.maxSize )
+ {
+ numDownloads++;
+ bytesDownloaded += dlstatus.downloaded;
+ bytesTotal += dlstatus.maxSize;
+ }
+ else // don't have a max size
+ {
+ if ( dlstatus.downloaded ) // if we've downloaded some then we just don't know the total
+ {
+ unknownTotal = true;
+ numDownloads++;
+ bytesDownloaded += dlstatus.downloaded;
+ }
+ }
+ }
+
+ if ( 0 == numDownloads )
+ {
+ status[ 0 ] = L'\0';
+ }
+ else
+ {
+ wchar_t buf[ 128 ] = { 0 };
+ if ( unknownTotal || bytesTotal == 0 )
+ StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_COMPLETE ), numDownloads, numDownloads, WASABI_API_LNG->FormattedSizeString( buf, ARRAYSIZE( buf ), bytesDownloaded ) );
+ else
+ {
+ wchar_t buf2[ 128 ] = { 0 };
+ StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PROGRESS ), numDownloads, WASABI_API_LNG->FormattedSizeString( buf, ARRAYSIZE( buf ), bytesDownloaded ), WASABI_API_LNG->FormattedSizeString( buf2, ARRAYSIZE( buf2 ), bytesTotal ), MulDiv( (int)bytesDownloaded, 100, (int)bytesTotal ) );
+ }
+ }
+}
diff --git a/Src/Plugins/Library/ml_downloads/DownloadStatus.h b/Src/Plugins/Library/ml_downloads/DownloadStatus.h
new file mode 100644
index 00000000..8e8eedbb
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadStatus.h
@@ -0,0 +1,41 @@
+#ifndef NULLSOFT_DOWNLOADSTATUSH
+#define NULLSOFT_DOWNLOADSTATUSH
+
+#include "../nu/AutoLock.h"
+#include <map>
+
+
+class DownloadStatus
+{
+public:
+ class Status
+ {
+ public:
+ Status();
+ ~Status();
+ const Status &operator =(const Status &copy);
+ Status(size_t _downloaded, size_t _maxSize, const wchar_t *source, const wchar_t *title, const wchar_t *path);
+ size_t downloaded, maxSize;
+
+ int killswitch;
+ wchar_t *source;
+ wchar_t *title;
+ wchar_t *path;
+ private:
+
+ void Init();
+ void Reset();
+ };
+
+ void AddDownloadThread(DownloadToken token, const wchar_t *source, const wchar_t *title, const wchar_t *path);
+ void DownloadThreadDone(DownloadToken token);
+ bool UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize);
+ bool CurrentlyDownloading();
+ void GetStatusString(wchar_t *status, size_t len);
+ typedef std::map<DownloadToken, Status> Downloads;
+ Downloads downloads;
+ Nullsoft::Utility::LockGuard statusLock;
+};
+
+extern DownloadStatus downloadStatus;
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadThread.cpp b/Src/Plugins/Library/ml_downloads/DownloadThread.cpp
new file mode 100644
index 00000000..563c9d3d
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadThread.cpp
@@ -0,0 +1,65 @@
+#include "Main.h"
+
+#pragma warning(disable:4786)
+
+#include "DownloadThread.h"
+#include "api__ml_downloads.h"
+#include <api/service/waServiceFactory.h>
+#include "errors.h"
+#include <strsafe.h>
+
+DownloadThread::DownloadThread() : parser( 0 ), parserFactory( 0 )
+{
+ parserFactory = plugin.service->service_getServiceByGuid( obj_xmlGUID );
+ if ( parserFactory )
+ parser = (obj_xml *)parserFactory->getInterface();
+
+ if ( parser )
+ {
+ parser->xmlreader_setCaseSensitive();
+ parser->xmlreader_registerCallback( L"*", &xmlDOM );
+ parser->xmlreader_open();
+ }
+}
+
+DownloadThread::~DownloadThread()
+{
+ if ( parser )
+ {
+ parser->xmlreader_unregisterCallback( &xmlDOM );
+ parser->xmlreader_close();
+ }
+
+ if ( parserFactory && parser )
+ parserFactory->releaseInterface( parser );
+
+ parserFactory = 0;
+ parser = 0;
+}
+
+#define FILE_BUFFER_SIZE 32768
+void DownloadThread::DownloadFile( const wchar_t *fileName )
+{
+ if ( !parser )
+ return; // no sense in continuing if there's no parser available
+
+ HANDLE file = CreateFile( fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
+ if ( file == INVALID_HANDLE_VALUE )
+ return;
+
+ while ( true )
+ {
+ char data[ FILE_BUFFER_SIZE ] = { 0 };
+ DWORD bytesRead = 0;
+ if ( ReadFile( file, data, FILE_BUFFER_SIZE, &bytesRead, NULL ) && bytesRead )
+ {
+ parser->xmlreader_feed( (void *)data, bytesRead );
+ }
+ else
+ break;
+ }
+
+ CloseHandle( file );
+ parser->xmlreader_feed( 0, 0 );
+ ReadNodes( fileName );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadThread.h b/Src/Plugins/Library/ml_downloads/DownloadThread.h
new file mode 100644
index 00000000..31fad863
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadThread.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_DOWNLOADTHREADH
+#define NULLSOFT_DOWNLOADTHREADH
+
+#include "../xml/obj_xml.h"
+#include "../xml/XMLDOM.h"
+#include "../nu/Alias.h"
+#include "api__ml_downloads.h"
+#include <api/service/waServiceFactory.h>
+
+
+class DownloadThread
+{
+public:
+ DownloadThread();
+ virtual ~DownloadThread();
+
+ virtual void ReadNodes(const wchar_t *url) = 0;
+
+ void DownloadFile(const wchar_t *fileName);
+protected:
+ XMLDOM xmlDOM;
+private:
+ obj_xml *parser;
+ waServiceFactory *parserFactory;
+
+};
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp
new file mode 100644
index 00000000..ffd2ca52
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp
@@ -0,0 +1,125 @@
+#include "Main.h"
+
+//#include "../Components/wac_downloads/wac_downloads_download_manager.h"
+
+using namespace Nullsoft::Utility;
+
+DownloadViewCallback::DownloadViewCallback()
+{}
+
+void DownloadViewCallback::OnInit( DownloadToken token )
+{
+ // ---- Inform the download status service of our presence----
+ downloadStatus.AddDownloadThread( token, WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), WAC_API_DOWNLOADMANAGER->GetLocation( token ) );
+}
+
+void DownloadViewCallback::OnConnect( DownloadToken token )
+{}
+
+void DownloadViewCallback::OnData( DownloadToken token, void *data, size_t datalen )
+{
+ if ( token == NULL )
+ return;
+
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+ size_t totalSize = http->content_length();
+ size_t downloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ // if killswitch is turned on, then cancel download
+ downloadStatus.UpdateStatus( token, downloaded, totalSize );
+ dirty++;
+}
+
+void DownloadViewCallback::OnCancel( DownloadToken token )
+{
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+
+ DownloadedFile *data = new DownloadedFile( AutoWide( http->get_url() ), WAC_API_DOWNLOADMANAGER->GetLocation( token ), WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), DownloadedFile::DOWNLOAD_CANCELED, _time64( 0 ) );
+
+ data->totalSize = http->content_length();
+ data->bytesDownloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ dirty++;
+}
+
+void DownloadViewCallback::OnError( DownloadToken token, int error )
+{
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+
+ DownloadedFile *data = new DownloadedFile( AutoWide( http->get_url() ), WAC_API_DOWNLOADMANAGER->GetLocation( token ), WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), DownloadedFile::DOWNLOAD_FAILURE, _time64( 0 ) );
+
+ data->totalSize = http->content_length();
+ data->bytesDownloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ dirty++;
+}
+
+void DownloadViewCallback::OnFinish( DownloadToken token )
+{
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+
+ DownloadedFile *data = new DownloadedFile( AutoWide( http->get_url() ), WAC_API_DOWNLOADMANAGER->GetLocation( token ), WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), DownloadedFile::DOWNLOAD_SUCCESS, _time64( 0 ) );
+
+ data->totalSize = http->content_length();
+ data->bytesDownloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+
+ //AddDownloadData(*data);
+
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ dirty++;
+}
+
+
+size_t DownloadViewCallback::AddRef()
+{
+ return this->_ref_count.fetch_add( 1 );
+}
+
+size_t DownloadViewCallback::Release()
+{
+ std::size_t l_ref_countr = this->_ref_count.fetch_sub( 1 );
+ if ( l_ref_countr == 0 )
+ delete( this );
+
+ return l_ref_countr;
+}
+
+DownloadViewCallback::~DownloadViewCallback()
+{}
+
+#define CBCLASS DownloadViewCallback
+START_DISPATCH;
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONFINISH, OnFinish )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCANCEL, OnCancel )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONERROR, OnError )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONDATA, OnData )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCONNECT, OnConnect )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONINIT, OnInit )
+CB( ADDREF, AddRef )
+CB( RELEASE, Release )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadViewCallback.h b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.h
new file mode 100644
index 00000000..6ecce16b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.h
@@ -0,0 +1,37 @@
+#include <atomic>
+
+#include "Main.h"
+#include "Downloaded.h"
+
+#include "DownloadStatus.h"
+#include "DownloadsDialog.h"
+#include "api__ml_downloads.h"
+#include "api/service/waServiceFactory.h"
+#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
+
+
+class DownloadViewCallback : public ifc_downloadManagerCallback
+{
+public:
+ DownloadViewCallback();
+
+ void OnInit( DownloadToken token );
+ void OnConnect( DownloadToken token );
+ void OnData( DownloadToken token, void *data, size_t datalen );
+ void OnCancel( DownloadToken token );
+ void OnError( DownloadToken token, int error );
+ void OnFinish( DownloadToken token );
+
+ size_t AddRef();
+ size_t Release();
+
+private: // private destructor so no one accidentally calls delete directly on this reference counted object
+ ~DownloadViewCallback();
+
+protected:
+ RECVS_DISPATCH;
+
+private:
+ std::atomic<std::size_t> _ref_count = 1;
+};
+
diff --git a/Src/Plugins/Library/ml_downloads/Downloaded.cpp b/Src/Plugins/Library/ml_downloads/Downloaded.cpp
new file mode 100644
index 00000000..dc5a0e19
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Downloaded.cpp
@@ -0,0 +1,141 @@
+#include "main.h"
+#include "Downloaded.h"
+
+DownloadList downloadedFiles;
+using namespace Nullsoft::Utility;
+Nullsoft::Utility::LockGuard downloadedLock;
+
+DownloadedFile::DownloadedFile()
+{
+ Init();
+}
+
+DownloadedFile::DownloadedFile( const wchar_t *_url, const wchar_t *_path, const wchar_t *_source, const wchar_t *_title, int downloadStatus, __time64_t downloadDate )
+{
+ Init();
+
+ this->downloadStatus = downloadStatus;
+ this->downloadDate = downloadDate;
+
+ SetSource( _source );
+ SetTitle( _title );
+ SetPath( _path );
+ SetURL( _url );
+}
+
+DownloadedFile::DownloadedFile( const DownloadedFile &copy )
+{
+ Init();
+
+ operator =( copy );
+}
+
+DownloadedFile::~DownloadedFile()
+{
+ Reset();
+}
+
+
+void DownloadedFile::Init()
+{
+ url = 0;
+ path = 0;
+ source = 0;
+ title = 0;
+ bytesDownloaded = 0;
+ totalSize = 0;
+ downloadDate = 0;
+}
+
+void DownloadedFile::Reset()
+{
+ if ( url )
+ {
+ free( url );
+ url = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+
+ if ( source )
+ {
+ free( source );
+ source = 0;
+ }
+
+ if ( title )
+ {
+ free( title );
+ title = 0;
+ }
+}
+
+void DownloadedFile::SetPath( const wchar_t *_path )
+{
+ if ( path )
+ free( path );
+
+ path = _wcsdup( _path );
+}
+
+void DownloadedFile::SetURL( const wchar_t *_url )
+{
+ if ( url )
+ free( url );
+
+ url = _wcsdup( _url );
+}
+
+void DownloadedFile::SetTitle( const wchar_t *_title )
+{
+ if ( title )
+ free( title );
+
+ title = _wcsdup( _title );
+}
+
+void DownloadedFile::SetSource( const wchar_t *_source )
+{
+ if ( source )
+ free( source );
+
+ source = _wcsdup( _source );
+}
+
+const DownloadedFile &DownloadedFile::operator =( const DownloadedFile &copy )
+{
+ Reset();
+ Init();
+
+ SetSource( copy.source );
+ SetTitle( copy.title );
+
+ bytesDownloaded = copy.bytesDownloaded;
+ totalSize = copy.totalSize;
+ downloadStatus = copy.downloadStatus;
+ downloadDate = copy.downloadDate;
+
+ SetPath( copy.path );
+ SetURL( copy.url );
+
+ return *this;
+}
+
+wchar_t *GetDownloadStatus( int downloadStatus )
+{
+ switch ( downloadStatus )
+ {
+ case DownloadedFile::DOWNLOAD_SUCCESS:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_SUCCESS );
+ case DownloadedFile::DOWNLOAD_FAILURE:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_FAILURE );
+ case DownloadedFile::DOWNLOAD_CANCELED:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_CANCELED );
+ default:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_FAILURE );
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/Downloaded.h b/Src/Plugins/Library/ml_downloads/Downloaded.h
new file mode 100644
index 00000000..17897f19
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Downloaded.h
@@ -0,0 +1,86 @@
+#ifndef NULLSOFT_DOWNLOADEDH
+#define NULLSOFT_DOWNLOADEDH
+
+#include "../nu/AutoLock.h"
+#include "../nu/AutoCharFn.h"
+#include "..\..\General\gen_ml/ml.h"
+#include <vector>
+#include "../nu/MediaLibraryInterface.h"
+
+class DownloadedFile
+{
+public:
+ DownloadedFile();
+ DownloadedFile( const wchar_t *_url, const wchar_t *_path, const wchar_t *_source, const wchar_t *_title, int downloadStatus, __time64_t downloadDate );
+ DownloadedFile( const DownloadedFile &copy );
+ ~DownloadedFile();
+
+ const DownloadedFile &operator =( const DownloadedFile &copy );
+
+ void SetPath( const wchar_t *_path );
+ void SetURL( const wchar_t *_url );
+ void SetTitle( const wchar_t *_title );
+ void SetSource( const wchar_t *_source );
+
+ enum
+ {
+ DOWNLOAD_FAILURE = 0,
+ DOWNLOAD_SUCCESS = 1,
+ DOWNLOAD_CANCELED = 2,
+ };
+
+ size_t bytesDownloaded = 0;
+ size_t totalSize = 0;
+
+ int downloadStatus = 0;
+ __time64_t downloadDate = 0;
+
+ wchar_t *path = 0;
+ wchar_t *url = 0;
+ wchar_t *source = 0;
+ wchar_t *title = 0;
+
+
+private:
+ void Init();
+ void Reset();
+};
+
+class DownloadList
+{
+public:
+ typedef std::vector<DownloadedFile> DownloadedFileList;
+ typedef DownloadedFileList::iterator iterator;
+ typedef DownloadedFileList::const_iterator const_iterator;
+
+ operator Nullsoft::Utility::LockGuard &() { return downloadedLock; }
+
+ void Remove( size_t index ) { downloadList.erase( downloadList.begin() + index ); }
+
+ bool RemoveAndDelete( int index )
+ {
+ SendMessage( mediaLibrary.library, WM_ML_IPC, (WPARAM)downloadList[ index ].path, ML_IPC_DB_REMOVEITEMW );
+
+ if ( !DeleteFile( downloadList[ index ].path ) && GetLastError() != ERROR_FILE_NOT_FOUND )
+ return false;
+
+ downloadList.erase( downloadList.begin() + index );
+
+ return true;
+ }
+
+ DownloadedFileList downloadList;
+ Nullsoft::Utility::LockGuard downloadedLock;
+
+ iterator begin() { return downloadList.begin(); }
+ iterator end() { return downloadList.end(); }
+};
+
+extern DownloadList downloadedFiles;
+extern int downloadsItemSort;
+extern bool downloadsSortAscending;
+
+void CleanupDownloads();
+wchar_t *GetDownloadStatus( int downloadStatus );
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp b/Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp
new file mode 100644
index 00000000..adb2b008
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp
@@ -0,0 +1,1657 @@
+#include "main.h"
+#include "api__ml_downloads.h"
+#include "RFCDate.h"
+#include "Downloaded.h"
+#include "DownloadStatus.h"
+#include "Defaults.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "..\..\General\gen_ml/menu.h"
+#include <vector>
+#include "../nu/menushortcuts.h"
+#include <commctrl.h>
+#include <shlwapi.h>
+#include <shellapi.h>
+#include <strsafe.h>
+#include <algorithm>
+
+HWND downloads_window = 0;
+extern int downloads_treeItem;
+extern int no_auto_hide;
+int groupBtn = 1, enqueuedef = 0, customAllowed = 0;
+HMENU g_context_menus2 = NULL;
+static viewButtons view;
+
+#ifndef HDF_SORTUP
+#define HDF_SORTUP 0x0400
+#define HDF_SORTDOWN 0x0200
+#endif // !HDF_SORTUP
+
+using namespace Nullsoft::Utility;
+
+enum
+{
+ COL_TITLE = 0,
+ COL_PROGRESS,
+ COL_DATE,
+ COL_SOURCE,
+ COL_SIZE,
+ COL_PATH,
+ NUM_COLUMNS,
+};
+
+int downloadsSourceWidth = DOWNLOADSSOURCEWIDTHDEFAULT;
+int downloadsTitleWidth = DOWNLOADSTITLEWIDTHDEFAULT;
+int downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
+int downloadsDateWidth = DOWNLOADSDATEWIDTHDEFAULTS;
+int downloadsSizeWidth = DOWNLOADSSIZEWIDTHDEFAULTS;
+int downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
+
+
+W_ListView downloadList;
+int downloadsItemSort = 2; // -1 means no sort active
+bool downloadsSortAscending = false;
+
+enum
+{
+ DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR = 0,
+};
+
+class DownloadListItem
+{
+public:
+ DownloadedFile *f = NULL;
+ DownloadToken token = NULL;
+ wchar_t *source = 0;
+ wchar_t *title = 0;
+ wchar_t *path = 0;
+ wchar_t status[ 20 ] = { 0 };
+
+ DownloadListItem( DownloadedFile *fi )
+ {
+ f = new DownloadedFile( *fi );
+ ZeroMemory( status, sizeof( status ) );
+ }
+
+ DownloadListItem( DownloadToken p_token, const wchar_t *p_source, const wchar_t *p_title, const wchar_t *p_path, size_t p_downloaded, size_t p_maxSize ) : token( p_token )
+ {
+ if ( p_maxSize )
+ StringCchPrintf( status, 20, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PERCENT ), (int)( p_downloaded / ( p_maxSize / 100 ) ) );
+ else
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( p_token ) )
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_PENDING, status, 20 );
+ else
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOADING, status, 20 );
+ }
+
+ source = p_source ? _wcsdup( p_source ) : NULL;
+ title = p_title ? _wcsdup( p_title ) : NULL;
+ path = p_path ? _wcsdup( p_path ) : NULL;
+ }
+
+ ~DownloadListItem()
+ {
+ clean();
+
+ if ( f )
+ delete f;
+ }
+
+ void clean()
+ {
+ if ( source )
+ {
+ free( source );
+ source = 0;
+ }
+
+ if ( title )
+ {
+ free( title );
+ title = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+ }
+};
+
+static std::vector<DownloadListItem*> listContents;
+
+bool GetDownload( int &download )
+{
+ download = ListView_GetNextItem( downloadList.getwnd(), download, LVNI_ALL | LVNI_SELECTED );
+ if ( download == -1 )
+ return false;
+ else
+ return true;
+}
+
+void Downloads_Play( bool enqueue = false )
+{
+ int download = -1;
+ AutoLock lock( downloadedFiles );
+ while ( GetDownload( download ) )
+ {
+ if ( !enqueue )
+ {
+ if ( listContents[ download ]->f )
+ mediaLibrary.PlayFile( listContents[ download ]->f->path );
+ else if ( listContents[ download ]->path )
+ mediaLibrary.PlayFile( listContents[ download ]->path );
+
+ enqueue = true;
+ }
+ else
+ {
+ if ( listContents[ download ]->f )
+ mediaLibrary.EnqueueFile( listContents[ download ]->f->path );
+ else if ( listContents[ download ]->path )
+ mediaLibrary.EnqueueFile( listContents[ download ]->path );
+ }
+ }
+}
+
+void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token )
+{
+ listContents.push_back( new DownloadListItem( token, s.source, s.title, s.path, s.downloaded, s.maxSize ) );
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+}
+
+void DownloadsUpdated( DownloadToken token, const DownloadedFile *f )
+{
+ for ( DownloadListItem *l_content : listContents )
+ {
+ if ( l_content->token == token )
+ {
+ l_content->token = 0;
+ if ( f )
+ {
+ l_content->f = new DownloadedFile( *f );
+
+ l_content->clean();
+ }
+ else
+ lstrcpyn( l_content->status, L"Error", 20 );
+
+ break;
+ }
+ }
+
+ PostMessage( downloadList.getwnd(), LVM_REDRAWITEMS, 0, listContents.size() );
+}
+
+void DownloadsUpdated()
+{
+ for ( DownloadListItem *l_content : listContents )
+ delete l_content;
+
+ listContents.clear();
+
+ for ( DownloadedFile &l_download : downloadedFiles.downloadList )
+ listContents.push_back( new DownloadListItem( &l_download ) );
+
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ for ( DownloadStatus::Downloads::iterator itr = downloadStatus.downloads.begin(); itr != downloadStatus.downloads.end(); itr++ )
+ {
+ listContents.push_back( new DownloadListItem( itr->first, itr->second.source, itr->second.title, itr->second.path, itr->second.downloaded, itr->second.maxSize ) );
+ }
+ }
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+// Navigation_ShowService( SERVICE_DOWNLOADS, SHOWMODE_AUTO );
+}
+
+static void CleanupDownloads()
+{
+ {
+ AutoLock lock( downloadedFiles );
+ DownloadList::DownloadedFileList &downloads = downloadedFiles.downloadList;
+ DownloadList::iterator itr, next;
+ for ( itr = downloads.begin(); itr != downloads.end();)
+ {
+ next = itr;
+ ++next;
+ if ( !PathFileExists( itr->path ) )
+ downloads.erase( itr );
+ else
+ itr = next;
+ }
+ }
+
+// Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void Downloads_UpdateStatusBar(HWND hwndDlg)
+{
+ wchar_t status[256]=L"";
+ downloadStatus.GetStatusString(status, 256);
+ SetWindowText(GetDlgItem(hwndDlg, IDC_STATUS), status);
+}
+
+void Downloads_Paint(HWND hwndDlg)
+{
+ int tab[] = { IDC_DOWNLOADLIST | DCW_SUNKENBORDER, };
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_REMOVE, IDC_CLEANUP, IDC_STATUS,
+ GROUP_MAIN, IDC_DOWNLOADLIST
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+
+ if (rc.right == rc.left || rc.bottom == rc.top)
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect( &rg, rc.left, rc.top, rc.right, rc.top );
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDC_PLAY, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(GetDlgItem(hwnd, IDC_PLAY), buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ break;
+ }
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+
+ if ( !pl->hwnd )
+ continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ case IDC_REMOVE:
+ case IDC_CLEANUP:
+ if ( IDC_CUSTOM != pl->id || customAllowed )
+ {
+ if ( groupBtn && pl->id == IDC_PLAY && enqueuedef == 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && pl->id == IDC_ENQUEUE && enqueuedef != 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && ( pl->id == IDC_PLAY || pl->id == IDC_ENQUEUE ) && customAllowed )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowTextW( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
+ LONG width = LOWORD( idealSize ) + WASABI_API_APP->getScaleX( 6 );
+
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+
+ pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ if ( SWP_SHOWWINDOW & pl->flags )
+ rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_STATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > 16) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_DOWNLOADLIST:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ), rg.right - rg.left + WASABI_API_APP->getScaleY( 1 ), ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+
+ pl++;
+ }
+ else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
+ {
+ ValidateRect( hwnd, &ri );
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+
+ if (hdwp)
+ EndDeferWindowPos(hdwp);
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for(pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn(hwnd, NULL);
+}
+
+void Downloads_DisplayChange(HWND hwndDlg)
+{
+ ListView_SetTextColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ downloadList.SetFont(dialogSkinner.GetFont());
+ LayoutWindows(hwndDlg, TRUE);
+}
+
+static void DownloadsDialog_SkinControls(HWND hwnd, const INT *itemList, INT itemCount, UINT skinType, UINT skinStyle)
+{
+ MLSKINWINDOW skinWindow = {0};
+ skinWindow.style = skinStyle;
+ skinWindow.skinType = skinType;
+
+ for(INT i = 0; i < itemCount; i++)
+ {
+ skinWindow.hwndToSkin = GetDlgItem(hwnd, itemList[i]);
+ if (NULL != skinWindow.hwndToSkin)
+ {
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+ }
+ }
+}
+
+static void DownloadDialog_InitializeList(HWND hwnd)
+{
+ HWND hControl = GetDlgItem(hwnd, IDC_DOWNLOADLIST);
+ if (NULL == hControl) return;
+
+ UINT styleEx = (UINT)GetWindowLongPtr(hControl, GWL_EXSTYLE);
+ SetWindowLongPtr(hControl, GWL_EXSTYLE, styleEx & ~WS_EX_NOPARENTNOTIFY);
+
+ styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP;
+ SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+ SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
+
+ MLSKINWINDOW skinWindow;
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.hwndToSkin = hControl;
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+}
+
+bool COL_SOURCE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->f->source), -1, (item2->f->source), -1));
+ else if (!item1->f && !item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->source), -1, (item2->source), -1));
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?item1->f->source:item1->source), -1,
+ // (item2->f?item2->f->source:item2->source), -1));
+}
+
+bool COL_TITLE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->f->title), -1, (item2->f->title), -1));
+ else if (!item1->f && !item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->title), -1, (item2->title), -1));
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?item1->f->title:item1->title), -1,
+ // (item2->f?item2->f->title:item2->title), -1));
+}
+
+bool COL_PROGRESS_Sort( const DownloadListItem *item1, const DownloadListItem *item2 )
+{
+ if ( item1->f && item2->f )
+ return ( item1->f->downloadStatus > item2->f->downloadStatus );
+ else if ( !item1->f && !item2->f )
+ return ( item1->token < item2->token );
+ else if ( !item1->f )
+ return ( FALSE == downloadsSortAscending ) ? 0 : 1;
+ else //if (!item2->f)
+ return ( FALSE == downloadsSortAscending ) ? 1 : 0;
+
+ //return ((item1->f?item1->f->downloadStatus:-1) < (item2->f?item2->f->downloadStatus:-1));
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?GetDownloadStatus(item1->f->downloadStatus):item1->status), -1,
+ // (item2->f?GetDownloadStatus(item2->f->downloadStatus):item2->status), -1));
+}
+
+bool COL_PATH_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->f->path), -1, (item2->f->path), -1));
+ else if (!item1->f && !item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->path), -1, (item2->path), -1));
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?item1->f->path:item1->path), -1,
+ // (item2->f?item2->f->path:item2->path), -1));
+}
+
+bool COL_DATE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return item1->f->downloadDate < item2->f->downloadDate;
+ else if (!item1->f && !item2->f)
+ return item1->token < item2->token;
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+}
+
+bool COL_SIZE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return item1->f->totalSize < item2->f->totalSize;
+ else if (!item1->f && !item2->f)
+ return item1->token < item2->token;
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+}
+
+static BOOL Downloads_SortItems(int sortColumn)
+{
+ AutoLock lock (downloadedFiles);
+ switch (sortColumn)
+ {
+ case COL_TITLE:
+ std::sort(listContents.begin(), listContents.end(), COL_TITLE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_PROGRESS:
+ std::sort(listContents.begin(), listContents.end(), COL_PROGRESS_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_DATE:
+ std::sort(listContents.begin(), listContents.end(), COL_DATE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_SOURCE:
+ std::sort(listContents.begin(), listContents.end(), COL_SOURCE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_SIZE:
+ std::sort(listContents.begin(), listContents.end(), COL_SIZE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_PATH:
+ std::sort(listContents.begin(), listContents.end(), COL_PATH_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void Downloads_SetListSortColumn(HWND hwnd, INT listId, INT index, BOOL fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL == hItems) return;
+
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL == hHeader) return;
+
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+ // reset first (ml req)
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (index != i && FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item))
+ {
+ if (0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
+ SNDMSG(hHeader, HDM_SETITEM, i, (LPARAM)&item);
+ }
+ }
+ }
+
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, index, (LPARAM)&item))
+ {
+ INT fmt = item.fmt & ~(HDF_SORTUP | HDF_SORTDOWN);
+ fmt |= (FALSE == fAscending) ? HDF_SORTDOWN : HDF_SORTUP;
+ if (fmt != item.fmt)
+ {
+ item.fmt = fmt;
+ SNDMSG(hHeader, HDM_SETITEM, index, (LPARAM)&item);
+ }
+ }
+}
+
+static BOOL Downloads_Sort(HWND hwnd, INT iColumn, bool fAscending)
+{
+ BOOL result = TRUE;
+ downloadsSortAscending = fAscending;
+ Downloads_SortItems(iColumn);
+ Downloads_SetListSortColumn(hwnd, IDC_DOWNLOADLIST, iColumn, fAscending);
+
+ if (FALSE != result)
+ {
+ HWND hItems = GetDlgItem(hwnd, IDC_DOWNLOADLIST);
+ if (NULL != hItems)
+ InvalidateRect(hItems, NULL, TRUE);
+ }
+
+ return TRUE;
+}
+
+void Downloads_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+static void Downloads_ManageButtons( HWND hwndDlg )
+{
+ int has_selection = downloadList.GetSelectedCount();
+
+ const int buttonids[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_REMOVE };
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ HWND controlHWND = GetDlgItem( hwndDlg, buttonids[ i ] );
+ EnableWindow( controlHWND, has_selection );
+ }
+}
+
+void Downloads_Init(HWND hwndDlg)
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+ downloads_window = hwndDlg;
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_DOWNLOAD_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ g_context_menus2 = WASABI_API_LOADMENU(IDR_MENU1);
+ groupBtn = ML_GROUPBTN_VAL();
+ enqueuedef = (ML_ENQDEF_VAL() == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_CUSTOM, IDC_ENQUEUE), (INT_PTR)L"ml_downloads"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_CUSTOM, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ MLSkinWindow2(hLibrary, hwndDlg, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ const INT szControls[] = {IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM};
+ DownloadsDialog_SkinControls(hwndDlg, szControls, ARRAYSIZE(szControls), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0));
+
+ const INT szControlz[] = {IDC_REMOVE, IDC_CLEANUP, IDC_STATUS};
+ DownloadsDialog_SkinControls(hwndDlg, szControlz, ARRAYSIZE(szControlz), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ DownloadDialog_InitializeList(hwndDlg);
+ Downloads_UpdateStatusBar(hwndDlg);
+
+ downloadList.setwnd(GetDlgItem(hwndDlg, IDC_DOWNLOADLIST));
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_TITLE), downloadsTitleWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_PROGRESS), downloadsProgressWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_DATE), downloadsDateWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_SOURCE), downloadsSourceWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_SIZE), downloadsSizeWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_PATH), downloadsPathWidth);
+
+ DownloadsUpdated();
+
+ downloadList.SetVirtualCount((int)listContents.size());
+ Downloads_UpdateButtonText(hwndDlg, enqueuedef == 1);
+ Downloads_ManageButtons(hwndDlg);
+ Downloads_DisplayChange(hwndDlg);
+ Downloads_Sort(hwndDlg, downloadsItemSort, downloadsSortAscending);
+ SetTimer(hwndDlg, DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR , 1000, 0);
+}
+
+void Downloads_Timer( HWND hwndDlg, UINT timerId )
+{
+ switch ( timerId )
+ {
+ case DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR:
+ Downloads_UpdateStatusBar( hwndDlg );
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ for ( DownloadListItem *l_content : listContents )
+ {
+ if ( l_content->token )
+ {
+ size_t d = downloadStatus.downloads[ l_content->token ].downloaded;
+ size_t s = downloadStatus.downloads[ l_content->token ].maxSize;
+
+ if ( s )
+ StringCchPrintf( l_content->status, 20, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PERCENT ), (int)( d / ( s / 100 ) ) );
+ else
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( l_content->token ) )
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_PENDING, l_content->status, 20 );
+ else
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOADING, l_content->status, 20 );
+ }
+
+ PostMessage( downloadList.getwnd(), LVM_REDRAWITEMS, 0, listContents.size() );
+ }
+ }
+ }
+ break;
+ }
+}
+
+static INT Downloads_GetListSortColumn(HWND hwnd, INT listId, bool *fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL != hItems)
+ {
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL != hHeader)
+ {
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item) &&
+ 0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ if (NULL != fAscending)
+ {
+ *fAscending = (0 != (HDF_SORTUP & item.fmt));
+ }
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+void Downloads_Destroy( HWND hwndDlg )
+{
+ downloads_window = 0;
+ downloadsSourceWidth = downloadList.GetColumnWidth( COL_SOURCE );
+ downloadsTitleWidth = downloadList.GetColumnWidth( COL_TITLE );
+ downloadsProgressWidth = downloadList.GetColumnWidth( COL_PROGRESS );
+ downloadsPathWidth = downloadList.GetColumnWidth( COL_PATH );
+ downloadsDateWidth = downloadList.GetColumnWidth( COL_DATE );
+ downloadsSizeWidth = downloadList.GetColumnWidth( COL_SIZE );
+
+ for ( DownloadListItem *l_content : listContents )
+ delete l_content;
+
+ listContents.clear();
+
+ downloadList.setwnd( NULL );
+
+ bool fAscending;
+ downloadsItemSort = Downloads_GetListSortColumn( hwndDlg, IDC_DOWNLOADLIST, &fAscending );
+ downloadsSortAscending = ( -1 != downloadsItemSort ) ? ( FALSE != fAscending ) : true;
+
+ int activeDownloads = 0;
+ int historyDownloads = 0;
+ {
+ Nullsoft::Utility::AutoLock historylock( downloadedFiles.downloadedLock );
+ Nullsoft::Utility::AutoLock statuslock( downloadStatus.statusLock );
+ historyDownloads = (int)downloadedFiles.downloadList.size();
+ activeDownloads = (int)downloadStatus.downloads.size();
+ }
+
+ if ( !activeDownloads && !historyDownloads && !no_auto_hide )
+ {
+ HNAVITEM hItem = MLNavCtrl_FindItemById( plugin.hwndLibraryParent, downloads_treeItem );
+ if ( hItem )
+ {
+ MLNavCtrl_DeleteItem( plugin.hwndLibraryParent, hItem );
+ downloads_treeItem = 0;
+ }
+ }
+}
+
+void Downloads_Remove( bool del = false, HWND parent = NULL )
+{
+ int d = -1;
+ int r = 0;
+ while ( GetDownload( d ) )
+ {
+ int download = d - r;
+ DownloadListItem *item = listContents[ download ];
+ if ( item->f )
+ {
+ AutoLock lock( downloadedFiles );
+ int j = 0;
+ for ( DownloadList::iterator i = downloadedFiles.begin(); i != downloadedFiles.end(); ++i )
+ {
+ if ( !_wcsicmp( i->path, item->f->path ) )
+ {
+ if ( del )
+ {
+ if ( !downloadedFiles.RemoveAndDelete( j ) )
+ MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_DELETEFAILED ), downloadedFiles.downloadList[ j ].path, 0 );
+ }
+ else
+ downloadedFiles.Remove( j );
+
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ dirty++;
+
+ break;
+ }
+
+ ++j;
+ }
+ }
+ else if ( item->token )
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ downloadStatus.downloads[ item->token ].killswitch = 1;
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ }
+ else
+ {
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ }
+ }
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+ downloadList.UnselectAll();
+ // Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void Downloads_Delete( HWND parent )
+{
+ wchar_t message[ 256 ] = { 0 };
+ int c = downloadList.GetSelectedCount();
+
+ if ( !c )
+ return;
+ else if ( c == 1 )
+ WASABI_API_LNGSTRINGW_BUF( IDS_PERM_DELETE_ARE_YOU_SURE, message, 256 );
+ else
+ StringCchPrintf( message, 256, WASABI_API_LNGSTRINGW( IDS_PERM_DELETE_THESE_ARE_YOU_SURE ), c );
+
+ if ( MessageBox( NULL, message, WASABI_API_LNGSTRINGW( IDS_DELETION ), MB_ICONWARNING | MB_YESNO ) == IDNO )
+ return;
+
+ Downloads_Remove( true, parent );
+}
+
+void Downloads_CleanUp(HWND hwndDlg)
+{
+ wchar_t titleStr[64] = {0};
+ if ( MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_CLEAR_ALL_FINISHED_DOWNLOADS ), WASABI_API_LNGSTRINGW_BUF( IDS_CLEAN_UP_LIST, titleStr, 64 ), MB_ICONWARNING | MB_YESNO ) == IDNO )
+ return;
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.clear();
+ }
+
+ dirty++;
+
+ DownloadsUpdated();
+}
+
+void Downloads_InfoBox( HWND parent )
+{
+ int download = -1;
+ if ( GetDownload( download ) )
+ {
+ const wchar_t *fn;
+ if ( listContents[ download ]->f )
+ fn = listContents[ download ]->f->path;
+ else
+ fn = listContents[ download ]->path;
+
+ if ( fn )
+ {
+ infoBoxParamW p = { parent, fn };
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW );
+ }
+ }
+}
+
+void Downloads_SelectAll()
+{
+ int l = downloadList.GetCount();
+ for ( int i = 0; i < l; i++ )
+ downloadList.SetSelected( i );
+}
+
+static void exploreItemFolder( HWND hwndDlg )
+{
+ if ( downloadList.GetSelectionMark() >= 0 )
+ {
+ int download = -1;
+ while ( GetDownload( download ) )
+ {
+ wchar_t *file;
+ if ( listContents[ download ]->f )
+ file = listContents[ download ]->f->path;
+ else
+ file = listContents[ download ]->path;
+
+ WASABI_API_EXPLORERFINDFILE->AddFile( file );
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+void Downloads_Cancel()
+{
+ int l_selected_count = downloadList.GetSelectedCount();
+ for ( int i = -1; i < l_selected_count; ++i )
+ {
+ if ( GetDownload( i ) )
+ {
+ dirty++;
+ if ( !listContents[ i ]->f )
+ WAC_API_DOWNLOADMANAGER->CancelDownload( listContents[ i ]->token );
+
+ break; // Workaround for 5.9.1 to avoid crash if cancel of many downloads in same time
+ }
+ }
+}
+
+int we_are_drag_and_dropping = 0;
+
+static void Downloads_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
+{
+ bool fAscending;
+ INT iSort = Downloads_GetListSortColumn(hwnd, IDC_DOWNLOADLIST, &fAscending);
+ fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : true;
+ Downloads_Sort(hwnd, plv->iSubItem, fAscending);
+}
+
+LRESULT DownloadList_Notify( LPNMHDR l, HWND hwndDlg )
+{
+ switch ( l->code )
+ {
+ case LVN_COLUMNCLICK:
+ Downloads_OnColumnClick( hwndDlg, (NMLISTVIEW *)l );
+ break;
+ case NM_DBLCLK:
+ Downloads_Play( ( ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ) ^ ML_ENQDEF_VAL() ) );
+ break;
+ case LVN_BEGINDRAG:
+ we_are_drag_and_dropping = 1;
+ SetCapture( hwndDlg );
+ break;
+ case LVN_ITEMCHANGED:
+ Downloads_ManageButtons( hwndDlg );
+ break;
+ case LVN_GETDISPINFO:
+ NMLVDISPINFO *lpdi = (NMLVDISPINFO *)l;
+ size_t item = lpdi->item.iItem;
+
+ if ( item < 0 || item >= listContents.size() )
+ return 0;
+
+ //if (FALSE == downloadsSortAscending) item = listContents.size() - item - 1;
+
+ DownloadListItem *l = listContents[ item ];
+
+ if ( lpdi->item.mask & LVIF_TEXT )
+ {
+ lpdi->item.pszText[ 0 ] = 0;
+ switch ( lpdi->item.iSubItem )
+ {
+ case COL_TITLE:
+ if ( !l->token && l->f )
+ {
+ wchar_t *l_title = L"";
+
+ if ( l->f->title != NULL )
+ l_title = l->f->title;
+
+ lstrcpyn( lpdi->item.pszText, l_title, lpdi->item.cchTextMax );
+ }
+ else
+ {
+ if ( l->title ) lstrcpyn( lpdi->item.pszText, l->title, lpdi->item.cchTextMax );
+ }
+ break;
+ case COL_PROGRESS:
+ if ( !l->token && l->f )
+ {
+ switch ( l->f->downloadStatus )
+ {
+ case DownloadedFile::DOWNLOAD_SUCCESS:
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_SUCCESS, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ case DownloadedFile::DOWNLOAD_FAILURE:
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_FAILURE, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ case DownloadedFile::DOWNLOAD_CANCELED:
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_CANCELED, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ }
+ }
+ else lstrcpyn( lpdi->item.pszText, l->status, lpdi->item.cchTextMax );
+ break;
+ case COL_DATE:
+ {
+ if ( !l->token && l->f && l->f->downloadDate )
+ {
+ wchar_t tmp[ 128 ] = { 0 };
+ MakeDateString( l->f->downloadDate, tmp, 128 );
+ lstrcpyn( lpdi->item.pszText, tmp, lpdi->item.cchTextMax );
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_N_A, lpdi->item.pszText, lpdi->item.cchTextMax );
+ }
+ break;
+ }
+ case COL_SOURCE:
+ if ( !l->token && l->f )
+ {
+ wchar_t *l_source = L"";
+
+ if ( l->f->source != NULL )
+ l_source = l->f->source;
+
+ lstrcpyn( lpdi->item.pszText, l_source, lpdi->item.cchTextMax );
+ }
+ else
+ lstrcpyn( lpdi->item.pszText, l->source, lpdi->item.cchTextMax );
+ break;
+ case COL_SIZE:
+ {
+ if ( !l->token && l->f && l->f->totalSize > 0 )
+ WASABI_API_LNG->FormattedSizeString( lpdi->item.pszText, lpdi->item.cchTextMax, l->f->totalSize );
+ else
+ WASABI_API_LNGSTRINGW_BUF( IDS_N_A, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ }
+ case COL_PATH:
+ if ( !l->token && l->f )
+ {
+ wchar_t *l_path = L"";
+
+ if ( l->f->path != NULL )
+ l_path = l->f->path;
+
+ lstrcpyn( lpdi->item.pszText, l_path, lpdi->item.cchTextMax );
+ }
+ else
+ {
+ if ( l->path )
+ lstrcpyn( lpdi->item.pszText, l->path, lpdi->item.cchTextMax );
+ }
+
+ break;
+ }
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+void listbuild( wchar_t **buf, int &buf_size, int &buf_pos, const wchar_t *tbuf )
+{
+ if ( !*buf )
+ {
+ *buf = (wchar_t *)calloc( 4096, sizeof( wchar_t ) );
+ if ( *buf )
+ {
+ buf_size = 4096;
+ buf_pos = 0;
+ }
+ else
+ {
+ buf_size = buf_pos = 0;
+ }
+ }
+ int newsize = buf_pos + lstrlenW( tbuf ) + 1;
+ if ( newsize < buf_size )
+ {
+ size_t old_buf_size = buf_size;
+ buf_size = newsize + 4096;
+ wchar_t *new_buf = (wchar_t *)realloc( *buf, ( buf_size + 1 ) * sizeof( wchar_t ) );
+ if ( new_buf )
+ {
+ *buf = new_buf;
+ }
+ else
+ {
+ new_buf = (wchar_t*)calloc( ( buf_size + 1 ), sizeof( wchar_t ) );
+ if ( new_buf )
+ {
+ memcpy( new_buf, *buf, ( old_buf_size * sizeof( wchar_t ) ) );
+ free( *buf );
+ *buf = new_buf;
+ }
+ else buf_size = (int)old_buf_size;
+ }
+ }
+
+ StringCchCopyW( *buf + buf_pos, buf_size, tbuf );
+ buf_pos = newsize;
+}
+
+wchar_t *getSelectedList()
+{
+ wchar_t *path = NULL;
+ int buf_pos = 0;
+ int buf_size = 0;
+ int download = -1;
+
+ while ( GetDownload( download ) )
+ {
+ if ( listContents[ download ]->f )
+ listbuild( &path, buf_size, buf_pos, listContents[ download ]->f->path );
+ }
+
+ if ( path )
+ path[ buf_pos ] = 0;
+
+ return path;
+}
+
+static void SwapPlayEnqueueInMenu( HMENU listMenu )
+{
+ int playPos = -1, enqueuePos = -1;
+ MENUITEMINFOW playItem = { sizeof( MENUITEMINFOW ), 0, }, enqueueItem = { sizeof( MENUITEMINFOW ), 0, };
+
+ int numItems = GetMenuItemCount( listMenu );
+
+ for ( int i = 0; i < numItems; i++ )
+ {
+ UINT id = GetMenuItemID( listMenu, i );
+ if ( id == IDC_PLAY )
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW( listMenu, i, TRUE, &playItem );
+ }
+ else if ( id == IDC_ENQUEUE )
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos = i;
+ GetMenuItemInfoW( listMenu, i, TRUE, &enqueueItem );
+ }
+ }
+
+ playItem.wID = IDC_ENQUEUE;
+ enqueueItem.wID = IDC_PLAY;
+ SetMenuItemInfoW( listMenu, playPos, TRUE, &playItem );
+ SetMenuItemInfoW( listMenu, enqueuePos, TRUE, &enqueueItem );
+}
+
+static void SyncMenuWithAccelerators( HWND hwndDlg, HMENU menu )
+{
+ HACCEL szAccel[ 24 ] = { 0 };
+ INT c = WASABI_API_APP->app_getAccelerators( hwndDlg, szAccel, sizeof( szAccel ) / sizeof( szAccel[ 0 ] ), FALSE );
+ AppendMenuShortcuts( menu, szAccel, c, MSF_REPLACE );
+}
+
+void UpdateMenuItems( HWND hwndDlg, HMENU menu )
+{
+ bool swapPlayEnqueue = false;
+ if ( ML_ENQDEF_VAL() )
+ {
+ SwapPlayEnqueueInMenu( menu );
+ swapPlayEnqueue = true;
+ }
+
+ SyncMenuWithAccelerators( hwndDlg, menu );
+ if ( swapPlayEnqueue )
+ SwapPlayEnqueueInMenu( menu );
+}
+
+static int IPC_LIBRARY_SENDTOMENU = 0;
+static librarySendToMenuStruct s = { 0 };
+
+static void DownloadList_RightClick(HWND hwndDlg, HWND listHwnd, POINTS pts)
+{
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+
+ RECT controlRect, headerRect;
+ if (FALSE == GetClientRect(listHwnd, &controlRect))
+ SetRectEmpty(&controlRect);
+ else
+ MapWindowPoints(listHwnd, HWND_DESKTOP, (POINT*)&controlRect, 2);
+
+ if ( -1 == pt.x && -1 == pt.y )
+ {
+ RECT itemRect;
+ int selected = downloadList.GetNextSelected();
+ if ( selected != -1 ) // if something is selected we'll drop the menu from there
+ {
+ downloadList.GetItemRect( selected, &itemRect );
+ ClientToScreen( listHwnd, (POINT *)&itemRect );
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect( listHwnd, &itemRect );
+
+ HWND hHeader = (HWND)SNDMSG( listHwnd, LVM_GETHEADER, 0, 0L );
+ RECT headerRect;
+ if ( ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) && GetWindowRect( hHeader, &headerRect ) )
+ {
+ itemRect.top += ( headerRect.bottom - headerRect.top );
+ }
+ }
+
+ pt.x = itemRect.left;
+ pt.y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(listHwnd, LVM_GETHEADER, 0, 0L);
+ if ( 0 == ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) || FALSE == GetWindowRect( hHeader, &headerRect ) )
+ {
+ SetRectEmpty( &headerRect );
+ }
+
+ if ( FALSE != PtInRect( &headerRect, pt ) )
+ {
+ return;
+ }
+
+ LVHITTESTINFO hitTest;
+ hitTest.pt = pt;
+ MapWindowPoints( HWND_DESKTOP, listHwnd, &hitTest.pt, 1 );
+
+ int index = ( downloadList.GetNextSelected() != -1 ? ListView_HitTest( listHwnd, &hitTest ) : -1 );
+
+ HMENU baseMenu = WASABI_API_LOADMENU( IDR_MENU1 );
+
+ if ( baseMenu == NULL )
+ return;
+
+ HMENU menu = GetSubMenu( baseMenu, 0 );
+ if ( menu != NULL )
+ {
+ if ( ( index == -1 ) || ( index != -1 ) && listContents[ index ]->f )
+ DeleteMenu( menu, ID_DOWNLOADS_CANCELDOWNLOAD, MF_BYCOMMAND );
+
+ UINT enableExtras = MF_ENABLED;
+ if ( ( index == -1 ) || ( index != -1 ) && !listContents[ index ]->f
+ || ( index != -1 ) && ( listContents[ index ]->f->downloadStatus != 1 ) )
+ enableExtras = ( MF_GRAYED | MF_DISABLED );
+
+ UINT enableViewExtras = MF_ENABLED;
+ if ( index == -1 )
+ enableViewExtras = ( MF_GRAYED | MF_DISABLED );
+
+ EnableMenuItem( menu, IDC_PLAY, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, IDC_ENQUEUE, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, IDC_REMOVE, MF_BYCOMMAND | enableViewExtras );
+ EnableMenuItem( menu, IDC_DELETE, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, IDC_INFOBOX, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, ID_DOWNLOADS_EXPLORERITEMFOLDER, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, 2, MF_BYPOSITION | enableExtras );
+
+ { // send-to menu shit...
+ ZeroMemory( &s, sizeof( s ) );
+ IPC_LIBRARY_SENDTOMENU = (int)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE );
+ if ( IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU ) == 0xffffffff )
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[ 1 ] = 1;
+ s.build_hMenu = GetSubMenu( menu, 2 );
+ }
+ }
+
+ UpdateMenuItems( hwndDlg, menu );
+
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hwndDlg, NULL );
+ if ( !SendMessage( hwndDlg, WM_COMMAND, r, 0 ) )
+ {
+ s.menu_id = r; // more send to menu shit...
+ if ( s.mode == 2 && SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU ) == 0xffffffff )
+ {
+ s.mode = 3;
+ wchar_t *path = getSelectedList();
+ if ( path )
+ {
+ s.data = path;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU );
+ free( path );
+ }
+ }
+ }
+
+ if ( s.mode )
+ { // yet more send to menu shit...
+ s.mode = 4;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU ); // cleanup
+ }
+ }
+
+ DestroyMenu( baseMenu );
+}
+
+static void Downloads_ContextMenu( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ HWND sourceWindow = (HWND)wParam;
+ if ( sourceWindow == downloadList.getwnd() )
+ DownloadList_RightClick( hwndDlg, sourceWindow, MAKEPOINTS( lParam ) );
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL Downloads_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags = 0 )
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem( hwndDlg, buttonId );
+ GetWindowRect( buttonHWND, &r );
+ UpdateMenuItems( hwndDlg, menu );
+ MLSkinnedButton_SetDropDownState( buttonHWND, TRUE );
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if ( !( flags & BPM_WM_COMMAND ) )
+ tpmFlags |= TPM_RETURNCMD;
+ int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, tpmFlags, r.left, r.top, hwndDlg, NULL );
+ if ( ( flags & BPM_ECHO_WM_COMMAND ) && x )
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( x, 0 ), 0 );
+ MLSkinnedButton_SetDropDownState( buttonHWND, FALSE );
+ return x;
+}
+
+static void Downloads_Play( HWND hwndDlg, HWND from, UINT idFrom )
+{
+ HMENU listMenu = GetSubMenu( g_context_menus2, 0 );
+ int count = GetMenuItemCount( listMenu );
+ if ( count > 2 )
+ {
+ for ( int i = 2; i < count; i++ )
+ {
+ DeleteMenu( listMenu, 2, MF_BYPOSITION );
+ }
+ }
+
+ Downloads_ButtonPopupMenu( hwndDlg, idFrom, listMenu, BPM_WM_COMMAND );
+}
+
+static BOOL WINAPI DownloadDialog_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITMENUPOPUP: // yet yet more send to menu shit...
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ if (SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ }
+ break;
+
+ case WM_USER+543:
+ Navigation_Update();
+ break;
+
+ case WM_CONTEXTMENU:
+ Downloads_ContextMenu(hwndDlg, wParam, lParam);
+ return TRUE;
+
+ case WM_NOTIFYFORMAT:
+ return NFR_UNICODE;
+
+ case WM_INITDIALOG:
+ Downloads_Init(hwndDlg);
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ if (l->idFrom == IDC_DOWNLOADLIST)
+ return (BOOL)DownloadList_Notify(l,hwndDlg);
+ }
+ break;
+
+ case WM_DESTROY:
+ Downloads_Destroy(hwndDlg);
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ Downloads_DisplayChange(hwndDlg);
+ return 0;
+
+ case WM_TIMER:
+ Downloads_Timer(hwndDlg, (UINT)wParam);
+ break;
+
+ case WM_MOUSEMOVE:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m;
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = p;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDRAG);
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ we_are_drag_and_dropping = 0;
+ ReleaseCapture();
+ POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = p;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDRAG);
+ if (m.result > 0)
+ {
+ m.flags = 0;
+ m.result = 0;
+ wchar_t* path = getSelectedList();
+ if(path)
+ {
+ m.data = path;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDROP);
+ free(path);
+ }
+ }
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_DOWNLOADLIST|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_APP + 104:
+ {
+ Downloads_UpdateButtonText(hwndDlg, (int)wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ if ( HIWORD( wParam ) == MLBN_DROPDOWN )
+ {
+ Downloads_Play( hwndDlg, (HWND)lParam, LOWORD( wParam ) );
+ }
+ else
+ {
+ bool action;
+ if ( LOWORD( wParam ) == IDC_PLAY )
+ {
+ action = ( HIWORD( wParam ) == 1 ) ? ML_ENQDEF_VAL() == 1 : 0;
+ }
+ else if ( LOWORD( wParam ) == IDC_ENQUEUE )
+ {
+ action = ( HIWORD( wParam ) == 1 ) ? ML_ENQDEF_VAL() != 1 : 1;
+ }
+ else
+ break;
+
+ Downloads_Play( action );
+ }
+ break;
+ case IDC_REMOVE:
+ Downloads_Remove();
+ break;
+ case IDC_DELETE:
+ Downloads_Delete( hwndDlg );
+ break;
+ case IDC_CLEANUP:
+ Downloads_CleanUp( hwndDlg );
+ break;
+ case IDC_INFOBOX:
+ Downloads_InfoBox( hwndDlg );
+ break;
+ case IDC_SELECTALL:
+ Downloads_SelectAll();
+ break;
+ case ID_DOWNLOADS_EXPLORERITEMFOLDER:
+ exploreItemFolder( hwndDlg );
+ break;
+ case ID_DOWNLOADS_CANCELDOWNLOAD:
+ Downloads_Cancel();
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+HWND CALLBACK DownloadDialog_Create( HWND hParent )
+{
+ return WASABI_API_CREATEDIALOGPARAMW( IDD_DOWNLOADS, hParent, DownloadDialog_DlgProc, 0 );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsDialog.h b/Src/Plugins/Library/ml_downloads/DownloadsDialog.h
new file mode 100644
index 00000000..080f6bb6
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsDialog.h
@@ -0,0 +1,11 @@
+#ifndef NULLSOFT_DOWNLOADSDIALOGH
+#define NULLSOFT_DOWNLOADSDIALOGH
+
+#include "DownloadStatus.h"
+#include "Downloaded.h"
+
+void DownloadsUpdated();
+void DownloadsUpdated( DownloadToken token, const DownloadedFile *f );
+void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token );
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsParse.cpp b/Src/Plugins/Library/ml_downloads/DownloadsParse.cpp
new file mode 100644
index 00000000..627fb52b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsParse.cpp
@@ -0,0 +1,179 @@
+#include "Main.h"
+#include "DownloadsParse.h"
+#include "Downloaded.h"
+#include "Defaults.h"
+#include "ParseUtil.h"
+#include <wchar.h>
+#include <locale.h>
+
+static __time64_t filetime( const wchar_t *file )
+{
+ if ( !file || !*file )
+ return 0;
+
+ WIN32_FIND_DATAW f = { 0 };
+
+ HANDLE h = FindFirstFileW( file, &f );
+ if ( h == INVALID_HANDLE_VALUE )
+ {
+ /*wchar_t tmp[128] = {0};
+ wsprintf(tmp,L"%d",GetLastError());
+ MessageBox(0,file,tmp,0);*/
+ return 0;
+ }
+
+ FindClose( h );
+ SYSTEMTIME s = { 0 };
+ FileTimeToSystemTime( &f.ftCreationTime, &s );
+
+ tm t = { 0 };
+ t.tm_year = s.wYear - 1900;
+ t.tm_mon = s.wMonth - 1;
+ t.tm_mday = s.wDay;
+ t.tm_hour = s.wHour;
+ t.tm_min = s.wMinute;
+ t.tm_sec = s.wMinute;
+ __time64_t ret = _mktime64(&t);
+ /*wchar_t tmp[128] = {0};
+ wsprintf(tmp,L"%d",ret);
+ MessageBox(0,tmp,0,0);*/
+
+ return ret;
+}
+
+size_t GetFileSize(LPCWSTR path)
+{
+ WIN32_FIND_DATAW data = {0};
+ if (path && *path)
+ {
+ HANDLE h = FindFirstFileW(path, &data);
+ if (h == INVALID_HANDLE_VALUE)
+ return -1;
+ FindClose(h);
+ }
+ return data.nFileSizeLow/* | (__int64)data.nFileSizeHigh << 32*/;
+}
+
+static void ReadDownload( const XMLNode *item )
+{
+ DownloadedFile newDownloaded;
+
+ const wchar_t *source = GetContent( item, L"source" );
+ if ( !source ) source = GetContent( item, L"channel" );
+ newDownloaded.SetSource( source );
+
+ const wchar_t *title = GetContent( item, L"title" );
+ if ( !title ) title = GetContent( item, L"item" );;
+ newDownloaded.SetTitle( title );
+
+ const wchar_t *url = GetContent( item, L"url" );
+ newDownloaded.SetURL( url );
+
+ const wchar_t *path = GetContent( item, L"path" );
+ newDownloaded.SetPath( path );
+
+ // TODO ideally should be able to cope with __int64
+ // but the db is setup for int currently...
+ const wchar_t *size = GetContent( item, L"size" );
+ if ( size && size[ 0 ] )
+ {
+ size_t val = _wtoi( size );
+ if ( val > 0 ) newDownloaded.totalSize = val;
+ else if ( !val )
+ {
+ val = GetFileSize( path );
+ if ( val > 0 ) newDownloaded.totalSize = val;
+ else newDownloaded.totalSize = -1;
+ dirty++;
+ }
+ }
+ else
+ {
+ size_t val = GetFileSize( path );
+ if ( val > 0 ) newDownloaded.totalSize = val;
+ else newDownloaded.totalSize = -1;
+ dirty++;
+ }
+
+ const wchar_t *downloadDate = GetContent( item, L"downloadDate" );
+ if ( downloadDate && downloadDate[ 0 ] )
+ {
+ __time64_t val = (__time64_t)_wtoi( downloadDate );
+ if ( time > 0 ) newDownloaded.downloadDate = val;
+ }
+ else
+ {
+ __time64_t val = filetime( newDownloaded.path );
+ if ( time > 0 ) newDownloaded.downloadDate = val;
+ }
+
+ const wchar_t *status = GetContent( item, L"downloadStatus" );
+ if ( status && status[ 0 ] )
+ {
+ newDownloaded.downloadStatus = _wtoi( status );
+ }
+ else
+ {
+ newDownloaded.downloadStatus = DownloadedFile::DOWNLOAD_SUCCESS;
+ }
+
+ downloadedFiles.downloadList.push_back( newDownloaded );
+}
+
+void DownloadsParse::ReadNodes( const wchar_t *url )
+{
+ XMLNode::NodeList::const_iterator itr;
+ const XMLNode *curNode = xmlDOM.GetRoot();
+
+ if ( curNode->Present( L"winamp:preferences" ) )
+ curNode = curNode->Get( L"winamp:preferences" );
+
+ curNode = curNode->Get( L"downloads" );
+ if ( curNode )
+ {
+ const wchar_t *prop = curNode->GetProperty( L"downloadsTitleWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsTitleWidth = _wtoi( prop );
+ if ( downloadsTitleWidth <= 0 )
+ downloadsTitleWidth = DOWNLOADSTITLEWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsProgressWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsProgressWidth = _wtoi( prop );
+ if ( downloadsProgressWidth <= 0 )
+ downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsDateWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsDateWidth = _wtoi( prop );
+ if ( downloadsDateWidth <= 0 )
+ downloadsDateWidth = DOWNLOADSDATEWIDTHDEFAULTS;
+
+ prop = curNode->GetProperty( L"downloadsSourceWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsSourceWidth = _wtoi( prop );
+ if ( downloadsSourceWidth <= 0 )
+ downloadsSourceWidth = DOWNLOADSSOURCEWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsPathWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsPathWidth = _wtoi( prop );
+ if ( downloadsPathWidth <= 0 )
+ downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
+
+ prop = curNode->GetProperty( L"downloadsItemSort" );
+ if ( prop && prop[ 0 ] )
+ downloadsItemSort = _wtoi( prop );
+
+ prop = curNode->GetProperty( L"downloadsSortAscending" );
+ if ( prop && prop[ 0 ] )
+ downloadsSortAscending = !PropertyIsFalse( curNode, L"downloadsSortAscending" );
+
+ const XMLNode::NodeList *downloadsList = curNode->GetList( L"download" );
+ if ( downloadsList )
+ {
+ for ( itr = downloadsList->begin(); itr != downloadsList->end(); itr++ )
+ ReadDownload( *itr );
+ }
+ }
+}
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsParse.h b/Src/Plugins/Library/ml_downloads/DownloadsParse.h
new file mode 100644
index 00000000..8f70ceb9
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsParse.h
@@ -0,0 +1,13 @@
+#ifndef NULLSOFT_DOWNLOADSPARSEH
+#define NULLSOFT_DOWNLOADSPARSEH
+
+#include "DownloadThread.h"
+
+class DownloadsParse : public DownloadThread
+{
+public:
+ virtual void ReadNodes(const wchar_t *url);
+
+};
+
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/Main.cpp b/Src/Plugins/Library/ml_downloads/Main.cpp
new file mode 100644
index 00000000..09a260eb
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Main.cpp
@@ -0,0 +1,387 @@
+#include "main.h"
+#include "DownloadsDialog.h"
+#include "DownloadsParse.h"
+#include "Preferences.h"
+#include "XMLWriter.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "Defaults.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+#include "api__ml_downloads.h"
+#include "Downloaded.h"
+#include "DownloadStatus.h"
+#include <api/service/waServiceFactory.h>
+
+#include <strsafe.h>
+
+wchar_t *ml_cfg = 0,
+ xmlFileName[1024] = {0};
+
+ATOM VIEWPROP = 0;
+
+api_downloadManager *WAC_API_DOWNLOADMANAGER = 0;
+DownloadViewCallback *downloadViewCallback = 0;
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+DWORD threadStorage=TLS_OUT_OF_INDEXES;
+extern "C" winampMediaLibraryPlugin plugin =
+ {
+ MLHDR_VER,
+ "nullsoft(ml_downloads.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+ };
+
+//static prefsDlgRecW preferences;
+//static wchar_t preferencesName[64] = {0};
+int downloads_treeItem = 0,
+ podcast_parent = 0,
+ no_auto_hide = 0;
+HANDLE hMainThread;
+
+HCURSOR hDragNDropCursor;
+int winampVersion = 0, dirty = 0;
+
+api_application *applicationApi = NULL;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+int icons[4] = {-1, -1, -1, -1};
+int activeIcon = 0;
+int totalIcons = 4;
+static Nullsoft::Utility::LockGuard navigationLock;
+
+// used to get the downloads view parented to the podcast view
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+#define NAVITEM_PREFIX L"podcast_svc_"
+#define SERVICE_PODCAST 720
+
+enum
+{
+ DOWNLOADS_TIMER_NAVNODE = 35784,
+};
+
+HNAVITEM Navigation_FindService(UINT serviceId)
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+ INT cchPrefix = ARRAYSIZE(NAVITEM_PREFIX) - 1;
+
+ WCHAR szBuffer[256] = {0};
+ NAVITEM itemInfo = {0};
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.mask = NIMF_TEXTINVARIANT;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.hItem = MLNavCtrl_GetFirst(hLibrary);
+
+ while(NULL != itemInfo.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &itemInfo) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, itemInfo.pszInvariant, cchPrefix,
+ NAVITEM_PREFIX, cchPrefix))
+ {
+ return itemInfo.hItem;
+ }
+ itemInfo.hItem = MLNavItem_GetNext(hLibrary, itemInfo.hItem);
+ }
+ return NULL;
+}
+
+void Navigation_Update_Icon(void)
+{
+ Nullsoft::Utility::AutoLock navlock(navigationLock);
+
+ HNAVITEM hItem = NULL;
+ if(downloads_treeItem)
+ {
+ hItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent,downloads_treeItem);
+ }
+
+ if (hItem)
+ {
+ NAVITEM item;
+ item.cbSize = sizeof(NAVITEM);
+ item.hItem = hItem;
+ item.iSelectedImage = item.iImage = icons[activeIcon];
+ activeIcon = (activeIcon + 1) % totalIcons;
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+}
+
+BOOL Navigation_Update(void)
+{
+ int activeDownloads = 0;
+ int historyDownloads = 0;
+ {
+ Nullsoft::Utility::AutoLock historylock(downloadedFiles.downloadedLock);
+ historyDownloads = (int)downloadedFiles.downloadList.size();
+ }
+
+ {
+ Nullsoft::Utility::AutoLock statuslock(downloadStatus.statusLock);
+ activeDownloads = (int)downloadStatus.downloads.size();
+ }
+
+ Nullsoft::Utility::AutoLock navlock(navigationLock);
+ HNAVITEM hItem = NULL;
+ if (downloads_treeItem)
+ {
+ hItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent,downloads_treeItem);
+ }
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW(IDS_DOWNLOADS);
+ nis.item.iSelectedImage = nis.item.iImage = icons[0];
+
+ WCHAR szName[256] = {0};
+ if (activeDownloads && SUCCEEDED(StringCchPrintf(szName, ARRAYSIZE(szName), L"%s (%u)", WASABI_API_LNGSTRINGW(IDS_DOWNLOADS), activeDownloads)))
+ nis.item.pszText = szName;
+
+ if (activeDownloads > 0 || historyDownloads > 0 || no_auto_hide == 2)
+ {
+ if (!hItem)
+ {
+ nis.item.pszInvariant = NAVITEM_UNIQUESTR;
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ if(podcast_parent) nis.hParent = Navigation_FindService(SERVICE_PODCAST);
+ NAVITEM nvItem = {sizeof(NAVITEM),0,NIMF_ITEMID,};
+
+ nvItem.hItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ downloads_treeItem = nvItem.id;
+ }
+ else
+ {
+ nis.item.hItem = hItem;
+ nis.item.mask = NIMF_TEXT;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &nis.item);
+ }
+ }
+ else if (hItem)
+ {
+ nis.item.hItem = hItem;
+ nis.item.mask = NIMF_TEXT | NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &nis.item);
+ }
+
+ return TRUE;
+}
+
+void CALLBACK Downloads_Nav_Timer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ switch (eventId)
+ {
+ case DOWNLOADS_TIMER_NAVNODE:
+ if (downloadStatus.CurrentlyDownloading())
+ Navigation_Update_Icon();
+ break;
+ }
+}
+
+
+int Init()
+{
+ hMainThread = GetCurrentThread();
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+ threadStorage = TlsAlloc();
+
+ if ( 0 == VIEWPROP )
+ {
+ VIEWPROP = GlobalAddAtom( L"Nullsoft_DownloadsView" );
+ if ( VIEWPROP == 0 )
+ return ML_INIT_FAILURE;
+ }
+
+ winampVersion = (int)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETVERSION );
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( DownloadManagerGUID );
+ if ( !sf ) // no sense in continuing
+ return ML_INIT_FAILURE;
+ else
+ WAC_API_DOWNLOADMANAGER = reinterpret_cast<api_downloadManager *>( sf->getInterface() );
+
+ // loader so that we can get the localisation service api for use
+ sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( ExplorerFindFileApiGUID );
+ if ( sf )
+ WASABI_API_EXPLORERFINDFILE = reinterpret_cast<api_explorerfindfile*>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlDownloadsLangGUID );
+
+ 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;
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ BuildDefaults( plugin.hwndWinampParent );
+
+ mediaLibrary.BuildPath( L"Plugins\\ml\\downloads.xml", xmlFileName, 1024 );
+ if ( PathFileExists( xmlFileName ) )
+ {
+ DownloadsParse downloader;
+ downloader.DownloadFile( xmlFileName );
+ }
+ else
+ {
+ wchar_t xmlRssFileName[ 1024 ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\rss.xml", xmlRssFileName, 1024 );
+ {
+ DownloadsParse rssDownloader;
+ rssDownloader.DownloadFile( xmlRssFileName );
+ }
+ }
+
+ icons[ 0 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS );
+ icons[ 1 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS1 );
+ icons[ 2 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS2 );
+ icons[ 3 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS3 );
+
+ ml_cfg = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW );
+
+ podcast_parent = ( !!GetPrivateProfileInt( L"gen_ml_config", L"podcast_parent", 0, ml_cfg ) );
+ no_auto_hide = GetPrivateProfileInt( L"gen_ml_config", L"no_auto_hide", 0, ml_cfg );
+
+ Navigation_Update();
+
+ downloadViewCallback = new DownloadViewCallback();
+ WAC_API_DOWNLOADMANAGER->RegisterStatusCallback( downloadViewCallback );
+
+ SetTimer( plugin.hwndLibraryParent, DOWNLOADS_TIMER_NAVNODE, 1000, Downloads_Nav_Timer );
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ KillTimer( plugin.hwndLibraryParent, DOWNLOADS_TIMER_NAVNODE );
+
+ // If there are still files downloading, cancel download to remove incomplete downloaded files
+ while ( downloadStatus.CurrentlyDownloading() )
+ {
+ Nullsoft::Utility::AutoLock lock(downloadStatus.statusLock);
+ DownloadToken dltoken = downloadStatus.downloads.begin()->first;
+ WAC_API_DOWNLOADMANAGER->CancelDownload(dltoken);
+ }
+
+ if (dirty)
+ {
+ SaveDownloads( xmlFileName, downloadedFiles );
+ dirty = 0;
+ }
+
+ WAC_API_DOWNLOADMANAGER->UnregisterStatusCallback( downloadViewCallback );
+ downloadViewCallback->Release();
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_APP);
+
+ sf = plugin.service->service_getServiceByGuid(ExplorerFindFileApiGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_EXPLORERFINDFILE);
+
+ sf = plugin.service->service_getServiceByGuid(DownloadManagerGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WAC_API_DOWNLOADMANAGER);
+
+ if ( VIEWPROP != 0 )
+ {
+ GlobalDeleteAtom(VIEWPROP);
+ VIEWPROP = 0;
+ }
+}
+
+HWND CALLBACK DownloadDialog_Create( HWND hParent );
+
+INT_PTR MessageProc( int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3 )
+{
+ switch ( msg )
+ {
+ case ML_MSG_DOWNLOADS_VIEW_LOADED:
+ return TRUE;
+
+ case ML_MSG_DOWNLOADS_VIEW_POSITION:
+ {
+ // toggle the position of the node based on the preferences settings
+ if ( param2 )
+ {
+ podcast_parent = (int)param1;
+ if ( downloads_treeItem )
+ {
+ HNAVITEM hItem = MLNavCtrl_FindItemById( plugin.hwndLibraryParent, downloads_treeItem );
+ if (hItem)
+ {
+ if ( MLNavCtrl_DeleteItem( plugin.hwndLibraryParent, hItem ) )
+ {
+ downloads_treeItem = 0;
+ }
+ }
+ }
+ }
+
+ Navigation_Update();
+ return 0;
+ }
+
+ case ML_MSG_TREE_ONCREATEVIEW:
+ return ( param1 == downloads_treeItem ) ? (INT_PTR)DownloadDialog_Create( (HWND)param2 ) : 0;
+
+ case ML_MSG_NO_CONFIG:
+ return TRUE;
+
+ case ML_MSG_NOTOKTOQUIT:
+ if (downloadStatus.CurrentlyDownloading())
+ {
+ wchar_t titleStr[32] = {0};
+ if ( MessageBox( plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW( IDS_CANCEL_DOWNLOADS_AND_QUIT ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRM_QUIT, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) == IDNO )
+ return TRUE;
+ }
+ return FALSE;
+
+ case ML_MSG_WRITE_CONFIG:
+ if ( dirty )
+ {
+ SaveDownloads( xmlFileName, downloadedFiles );
+ dirty = 0;
+ }
+ break;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = (int)param1;
+ groupBtn = (int)param2;
+ PostMessage( downloads_window, WM_APP + 104, param1, param2 );
+ return 0;
+ }
+
+ return 0;
+}
+
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/Main.h b/Src/Plugins/Library/ml_downloads/Main.h
new file mode 100644
index 00000000..39c4de66
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Main.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include "Downloaded.h"
+
+#define PLUGIN_VERSION_MAJOR 1
+#define PLUGIN_VERSION_MINOR 33
+
+extern int winampVersion, podcast_parent, dirty;
+
+#define NAVITEM_UNIQUESTR L"download_svc"
+BOOL Navigation_Update(void);
+
+bool AddDownloadData(const DownloadedFile &data);
+void CloseDatabase();
+
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/AutoLock.h"
+#include <windows.h>
+#include <shlwapi.h>
+
+extern ATOM VIEWPROP;
+extern winampMediaLibraryPlugin plugin;
+extern int downloads_treeItem;
+
+#include "../Components/wac_downloadManager/wac_downloadManager_api.h"
+
+#define ML_ENQDEF_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"enqueuedef", 0, ml_cfg))
+#define ML_GROUPBTN_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"groupbtn", 1, ml_cfg))
+extern wchar_t* ml_cfg;
+
+#include "DownloadViewCallback.h"
+
+extern DownloadViewCallback *downloadViewCallback;
+
+#endif
+
+extern HWND downloads_window;
+extern int groupBtn, enqueuedef; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/MessageProcessor.cpp b/Src/Plugins/Library/ml_downloads/MessageProcessor.cpp
new file mode 100644
index 00000000..6067965c
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/MessageProcessor.cpp
@@ -0,0 +1,7 @@
+#include "main.h"
+#include "MessageProcessor.h"
+
+#define CBCLASS MessageProcessor
+START_DISPATCH;
+CB(API_MESSAGEPROCESSOR_PROCESS_MESSAGE, ProcessMessage)
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/MessageProcessor.h b/Src/Plugins/Library/ml_downloads/MessageProcessor.h
new file mode 100644
index 00000000..c48e4b8a
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/MessageProcessor.h
@@ -0,0 +1,35 @@
+#ifndef NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
+#define NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
+
+#include <api/application/api_messageprocessor.h>
+#include "main.h"
+#ifndef WM_FORWARDMSG
+#define WM_FORWARDMSG 0x037F
+#endif
+
+class MessageProcessor : public api_messageprocessor
+{
+public:
+ bool ProcessMessage(MSG *msg)
+ {
+
+ if (msg->message < WM_KEYFIRST || msg->message > WM_KEYLAST)
+ return false;
+
+ HWND hWndCtl = ::GetFocus();
+
+ if (IsChild(browserHWND, hWndCtl))
+ {
+ // find a direct child of the dialog from the window that has focus
+ while(::GetParent(hWndCtl) != browserHWND)
+ hWndCtl = ::GetParent(hWndCtl);
+
+ if (activeBrowser->translateKey(*msg))
+ return true;
+ }
+ return false;
+ }
+protected:
+ RECVS_DISPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ParseUtil.cpp b/Src/Plugins/Library/ml_downloads/ParseUtil.cpp
new file mode 100644
index 00000000..0a5c8d57
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ParseUtil.cpp
@@ -0,0 +1,43 @@
+#include "ParseUtil.h"
+
+bool PropertyIsTrue( const XMLNode *item, const wchar_t *property )
+{
+ if ( !item )
+ return false;
+
+ const wchar_t *value = item->GetProperty( property );
+ if ( !value )
+ return false;
+
+ return !_wcsicmp( value, L"true" );
+}
+
+bool PropertyIsFalse( const XMLNode *item, const wchar_t *property )
+{
+ if ( !item )
+ return false;
+
+ const wchar_t *value = item->GetProperty( property );
+ if ( !value )
+ return false;
+
+ return !_wcsicmp( value, L"false" );
+}
+
+const wchar_t *GetContent( const XMLNode *item, const wchar_t *tag )
+{
+ const XMLNode *curNode = item->Get( tag );
+ if ( curNode )
+ return curNode->GetContent();
+ else
+ return 0;
+}
+
+const wchar_t *GetProperty( const XMLNode *item, const wchar_t *tag, const wchar_t *property )
+{
+ const XMLNode *curNode = item->Get( tag );
+ if ( curNode )
+ return curNode->GetProperty( property );
+ else
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ParseUtil.h b/Src/Plugins/Library/ml_downloads/ParseUtil.h
new file mode 100644
index 00000000..a277d729
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ParseUtil.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../xml/XMLNode.h"
+#include <bfc/platform/types.h>
+bool PropertyIsTrue(const XMLNode *item, const wchar_t *property);
+// not necessarily the opposite of PropertyIsTrue (returns false when field is empty
+bool PropertyIsFalse(const XMLNode *item, const wchar_t *property);
+const wchar_t *GetContent(const XMLNode *item, const wchar_t *tag);
+const wchar_t *GetProperty(const XMLNode *item, const wchar_t *tag, const wchar_t *property); \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/Preferences.cpp b/Src/Plugins/Library/ml_downloads/Preferences.cpp
new file mode 100644
index 00000000..fcd3a9cf
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Preferences.cpp
@@ -0,0 +1,77 @@
+#include "main.h"
+#include "api__ml_downloads.h"
+#include "../winamp/wa_ipc.h"
+#include "Defaults.h"
+#include <shlobj.h>
+
+void Preferences_Init(HWND hwndDlg)
+{
+ SetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath);
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)defaultDownloadPath);
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+void Preferences_Browse(HWND hwndDlg)
+{
+ wchar_t folder[MAX_PATH] = {0};
+ BROWSEINFO browse = {0};
+ lstrcpyn(folder, defaultDownloadPath, MAX_PATH);
+ browse.hwndOwner = hwndDlg;
+ browse.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_FOLDER);
+ browse.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ browse.lpfn = BrowseCallbackProc;
+ LPITEMIDLIST itemList = SHBrowseForFolder(&browse);
+ if (itemList)
+ {
+ SHGetPathFromIDList(itemList, folder);
+ lstrcpyn(defaultDownloadPath, folder, MAX_PATH);
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DOWNLOADLOCATION), folder);
+ LPMALLOC malloc;
+ SHGetMalloc(&malloc);
+ malloc->Free(itemList);
+ }
+}
+
+
+BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ Preferences_Init(hwndDlg);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BROWSE:
+ Preferences_Browse(hwndDlg);
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_downloads/Preferences.h b/Src/Plugins/Library/ml_downloads/Preferences.h
new file mode 100644
index 00000000..c0615aab
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Preferences.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_PREFERENCESH
+#define NULLSOFT_PREFERENCESH
+
+BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/RFCDate.cpp b/Src/Plugins/Library/ml_downloads/RFCDate.cpp
new file mode 100644
index 00000000..ab57f9af
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/RFCDate.cpp
@@ -0,0 +1,216 @@
+#include "main.h"
+#include "RFCDate.h"
+#include <strsafe.h>
+
+void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, date_str, (int)len);
+
+}
+
+void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _gmtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ wchar_t rfcDate[64] = {0}, rfcTime[64] = {0};
+ GetDateFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"ddd',' d MMM yyyy ", rfcDate, 64);
+ GetTimeFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"HH':'mm':'ss 'GMT'", rfcTime, 64);
+ StringCchPrintf(data_str,len,L"%s%s",rfcDate,rfcTime);
+}
+
+static int getMonthOfYear(const wchar_t *str);
+static int validateTime(struct tm *tmloc, int gmoffset);
+static const wchar_t *getNextField(const wchar_t *pos);
+static int getGMOffset(const wchar_t *str);
+
+enum
+{
+ DAY_OF_MONTH = 0,
+ MONTH_OF_YEAR,
+ YEAR,
+ TIME,
+ TIMEZONE,
+ DATE_END
+};
+
+__time64_t MakeRFCDate(const wchar_t *date)
+{
+ __time64_t result = 0;
+ const wchar_t *pos = date;
+ tm tmloc = {0};
+ const wchar_t *strmin;
+ const wchar_t *strsec;
+ int gmoffset = 1;
+ int state = DAY_OF_MONTH;
+ tzset();
+ /* skip weekday if present */
+ while (pos && *pos && !iswdigit(*pos)) pos++;
+
+ do
+ {
+ switch (state)
+ {
+ case DAY_OF_MONTH:
+ tmloc.tm_mday = _wtoi(pos);
+ break;
+ case MONTH_OF_YEAR:
+ tmloc.tm_mon = getMonthOfYear(pos);
+ break;
+ case YEAR:
+ {
+ /* TODO: we're only accepting 4-digit dates...*/
+ const wchar_t *test = pos; int numDigits = 0;
+ while (iswdigit(*test) && *test) { test++; numDigits++; }
+ if (numDigits == 2) // let's hope we never have 2 digit years!
+ tmloc.tm_year = _wtoi(pos) + 100; // assume 2 digit years are 20xx
+ else
+ tmloc.tm_year = _wtoi(pos) - 1900;
+ }
+ break;
+ case TIME:
+ strmin = wcschr(pos, L':');
+ strsec = strmin ? wcschr(strmin + 1, L':') : 0;
+
+ tmloc.tm_hour = _wtoi(pos);
+ if (!strmin) return _time64(0);
+ tmloc.tm_min = _wtoi(strmin + 1);
+ if (!strsec) return _time64(0);
+ tmloc.tm_sec = _wtoi(strsec + 1);
+ break;
+ case TIMEZONE:
+ gmoffset = getGMOffset(pos);
+ break;
+ case DATE_END:
+ pos = 0;
+ break;
+ }
+
+ state++;
+ }
+ while ((pos = getNextField(pos)));
+
+ tmloc.tm_isdst = 0; //_daylight;
+
+ if (validateTime(&tmloc, gmoffset))
+ {
+ result = _mktime64(&tmloc) - _timezone;
+ //if (_daylight)
+ }
+
+ return result;
+}
+
+const wchar_t *getNextField(const wchar_t *pos)
+{
+ if (!pos)
+ return 0;
+ while (pos && *pos && !iswspace(*pos)) pos++;
+ while (pos && *pos && iswspace(*pos)) pos++;
+
+ return ((pos && *pos) ? pos : 0);
+}
+
+int validateTime(struct tm *tmloc, int gmoffset)
+{
+ int result = 1;
+
+ if (tmloc->tm_mday < 1 || tmloc->tm_mday > 31 ||
+ tmloc->tm_mon < 0 || tmloc->tm_mon > 11 ||
+ tmloc->tm_year < 0 || tmloc->tm_year > 2000 ||
+ tmloc->tm_hour < 0 || tmloc->tm_hour > 23 ||
+ tmloc->tm_min < 0 || tmloc->tm_min > 59 ||
+ tmloc->tm_sec < 0 || tmloc->tm_sec > 59 ||
+ gmoffset == 1)
+ result = 0;
+
+ return result;
+}
+
+int getMonthOfYear(const wchar_t *str)
+{
+ int mon = -1;
+ /* This is not the most efficient way to determine
+ the month (we could use integer comparisons, for instance)
+ but I don't think this will be a performance bottleneck :)
+ */
+
+ if (!wcsnicmp(str, L"Jan", 3))
+ mon = 0;
+ else if (!wcsnicmp(str, L"Feb", 3))
+ mon = 1;
+ else if (!wcsnicmp(str, L"Mar", 3))
+ mon = 2;
+ else if (!wcsnicmp(str, L"Apr", 3))
+ mon = 3;
+ else if (!wcsnicmp(str, L"May", 3))
+ mon = 4;
+ else if (!wcsnicmp(str, L"Jun", 3))
+ mon = 5;
+ else if (!wcsnicmp(str, L"Jul", 3))
+ mon = 6;
+ else if (!wcsnicmp(str, L"Aug", 3))
+ mon = 7;
+ else if (!wcsnicmp(str, L"Sep", 3))
+ mon = 8;
+ else if (!wcsnicmp(str, L"Oct", 3))
+ mon = 9;
+ else if (!wcsnicmp(str, L"Nov", 3))
+ mon = 10;
+ else if (!wcsnicmp(str, L"Dec", 3))
+ mon = 11;
+
+ return mon;
+}
+
+int getGMOffset(const wchar_t *str)
+{
+ int secs = 0;
+ /* See note in getMonthOfYear() */
+
+ if (!wcsnicmp(str, L"UT", 2) || !wcsnicmp(str, L"GMT", 3))
+ secs = 0;
+ else if (!wcsnicmp(str, L"EDT", 3))
+ secs = -4 * 3600;
+ else if (!wcsnicmp(str, L"PST", 3))
+ secs = -8 * 3600;
+ else if (!wcsnicmp(str, L"EST", 3) || !wcsnicmp(str, L"CDT", 3))
+ secs = -5 * 3600;
+ else if (!wcsnicmp(str, L"MST", 3) || !wcsnicmp(str, L"PDT", 3))
+ secs = -7 * 3600;
+ else if (!wcsnicmp(str, L"CST", 3) || !wcsnicmp(str, L"MDT", 3))
+ secs = -6 * 3600;
+ else if ( (str[0] == L'+' || str[0] == L'-') &&
+ iswdigit(str[1]) && iswdigit(str[2]) &&
+ iswdigit(str[3]) && iswdigit(str[4]))
+ {
+ secs = 3600 * (10 * (str[1] - 48) + str[2] - 48);
+ secs += 60 * (10 * (str[3] - 48) + str[4] - 48);
+
+ if (str[0] == L'-')
+ secs = -secs;
+ }
+ else
+ secs = 1;
+
+ return secs;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/RFCDate.h b/Src/Plugins/Library/ml_downloads/RFCDate.h
new file mode 100644
index 00000000..7606272f
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/RFCDate.h
@@ -0,0 +1,7 @@
+#ifndef NULLSOFT_RFCDATEH
+#define NULLSOFT_RFCDATEH
+
+void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len);
+__time64_t MakeRFCDate(const wchar_t *date);
+void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/TODO.txt b/Src/Plugins/Library/ml_downloads/TODO.txt
new file mode 100644
index 00000000..db9d5965
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/TODO.txt
@@ -0,0 +1,51 @@
+to fix
+----
+crash on shutdown
+
+
+/////------------ 1.1 below --------------
+automatically switch to 'custom' if you click on dropdown in "add" or "edit" url dialog
+
+need icon for listened media
+maybe one for read text?
+
+strip whitespace from beginning of titles
+
+multiple-select
+
+Allow for customizing the download location in add/edit url
+
+deletable items (needs to move to a separate 'deleted items' list so we don't re-add them next rss refresh)
+
+drag-n-drop from webpages
+
+once we get an HTTP 200, we should put the downloaded on the 'downloads' list, and be able to update the download percentage status as necessary
+
+BACKGROUND DOWNLOADER
+<<<
+avoid multiple downloads of the same thing
+avoid downlaoding things that have already been downloaded.
+range / if-range to handle download resuming
+save the last modified dates from "Last-Modified" header
+save unfinished downloads to an XML file and read on load
+>>>
+
+UNIFIED DOWNLOAD MANAGER CONCEPT !!!!!
+
+who needs updates
+downloaded file list
+downloads page (to refresh view)
+item object
+podcast page (to refresh view)
+
+
+new way of listing items
+---
+create a common "items" data structure that select channels add their items to.
+When a channel is select, it adds its items.
+When a channel is deselected, it removes its items.
+When a channel is refreshed, it re-adds its items (assuming the item-adder function protects against dupes)
+only potential issue is if a channel somehow "loses" items (or an item's GUID is changes)
+could be fixed by either 1) keeps track of "parent channel" in the items list
+2) rebuilding the entire items list on every channel refresh
+or 3) preventing GUID changes and item deletions (or forcing an item list rebuild if it does occur)
diff --git a/Src/Plugins/Library/ml_downloads/Util.h b/Src/Plugins/Library/ml_downloads/Util.h
new file mode 100644
index 00000000..12acd266
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Util.h
@@ -0,0 +1,64 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/trace.h"
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#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 MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWL_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+
+LPWSTR Plugin_MallocString(size_t cchLen);
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
+void Plugin_FreeString(LPWSTR pszString);
+LPWSTR Plugin_CopyString(LPCWSTR pszSource);
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen);
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
+void Plugin_FreeAnsiString(LPSTR pszString);
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
+void Plugin_FreeResString(LPWSTR pszResource);
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
+
+void Plugin_SafeRelease(IUnknown *pUnk);
+
+
+HRESULT Plugin_FileExtensionFromUrl(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension);
+void Plugin_ReplaceBadPathChars(LPWSTR pszPath);
+INT Plugin_CleanDirectory(LPWSTR pszPath);
+
+HRESULT Plugin_EnsurePathExist(LPCWSTR pszDirectory);
+
+#endif // NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/XMLWriter.cpp b/Src/Plugins/Library/ml_downloads/XMLWriter.cpp
new file mode 100644
index 00000000..fdc8f2ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/XMLWriter.cpp
@@ -0,0 +1,125 @@
+#include "Main.h"
+#include "RFCDate.h"
+#include "Downloaded.h"
+#include "../nu/AutoLock.h"
+#include "Defaults.h"
+#include "../Agave/Language/api_language.h"
+
+using namespace Nullsoft::Utility;
+
+static void XMLWriteString(FILE *fp, const wchar_t *str)
+{
+ for (const wchar_t *itr = str; *itr; itr=CharNextW(itr))
+ {
+ switch (*itr)
+ {
+ case '<': fputws(L"&lt;", fp); break;
+ case '>': fputws(L"&gt;", fp); break;
+ case '&': fputws(L"&amp;", fp); break;
+ case '\"': fputws(L"&quot;", fp); break;
+ case '\'': fputws(L"&apos;", fp); break;
+ default: fputwc(*itr, fp); break;
+ }
+ }
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, const wchar_t *value)
+{
+ fwprintf(fp, L" %s=\"", property);
+ XMLWriteString(fp, value);
+ fputws(L"\"", fp);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, int value)
+{
+ fwprintf(fp, L" %s=\"%i\"", property, value);
+}
+
+static void InstaPropI64(FILE *fp, const wchar_t *property, int64_t value)
+{
+ fwprintf(fp, L" %s=\"%I64d\"", property, value);
+}
+
+static void InstaPropTime(FILE *fp, const wchar_t *property, __time64_t value)
+{
+ fwprintf(fp, L" %s=\"%I64u\"", property, value);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, float value)
+{
+ _fwprintf_l(fp, L" %s=\"%3.3f\"", WASABI_API_LNG->Get_C_NumericLocale(), property, value);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, bool value)
+{
+ fwprintf(fp, L" %s=\"", property);
+ if (value)
+ fputws(L"true\"", fp);
+ else
+ fputws(L"false\"", fp);
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, const wchar_t *content)
+{
+ if (content && !((INT_PTR)content < 65536) && *content)
+ {
+ fwprintf(fp, L"<%s>", tag);
+ XMLWriteString(fp, content);
+ fwprintf(fp, L"</%s>\r\n", tag);
+ }
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, unsigned int uint)
+{
+ fwprintf(fp, L"<%s>%u</%s>", tag, uint, tag);
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, __time64_t uint)
+{
+ fwprintf(fp, L"<%s>%I64u</%s>", tag, uint, tag);
+}
+
+static void BuildXMLDownloads(FILE *fp, DownloadList &downloads)
+{
+ fputws(L"<downloads", fp);
+ InstaProp(fp, L"downloadsTitleWidth", downloadsTitleWidth);
+ InstaProp(fp, L"downloadsProgressWidth", downloadsProgressWidth);
+ InstaProp(fp, L"downloadsDateWidth", downloadsDateWidth);
+ InstaProp(fp, L"downloadsSourceWidth", downloadsSourceWidth);
+ InstaProp(fp, L"downloadsPathWidth", downloadsPathWidth);
+ InstaProp(fp, L"downloadsItemSort", downloadsItemSort);
+ InstaProp(fp, L"downloadsSortAscending", downloadsSortAscending);
+ fputws(L">\r\n", fp);
+
+ AutoLock lock (downloads);
+ DownloadList::const_iterator itr;
+ for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
+ {
+ fputws(L"<download>", fp);
+ InstaTag(fp, L"source", itr->source);
+ InstaTag(fp, L"title", itr->title);
+ InstaTag(fp, L"url", itr->url);
+ InstaTag(fp, L"path", itr->path);
+ InstaTag(fp, L"size", (unsigned int)itr->totalSize);
+ InstaTag(fp, L"downloadDate", itr->downloadDate);
+ wchar_t status[64] = {0};
+ _itow(itr->downloadStatus, status, 10);
+ InstaTag(fp, L"downloadStatus", status);
+ fputws(L"</download>\r\n", fp);
+ }
+
+ fputws(L"</downloads>", fp);
+}
+
+void SaveDownloads(const wchar_t *fileName, DownloadList &downloads)
+{
+ FILE *fp = _wfopen(fileName, L"wb");
+ if (fp)
+ {
+ wchar_t BOM = 0xFEFF;
+ fwrite(&BOM, sizeof(BOM), 1, fp);
+ fputws(L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n", fp);
+ BuildXMLDownloads(fp, downloads);
+ fclose(fp);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/XMLWriter.h b/Src/Plugins/Library/ml_downloads/XMLWriter.h
new file mode 100644
index 00000000..26389756
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/XMLWriter.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_XMLWRITERH
+#define NULLSOFT_XMLWRITERH
+#include "Downloaded.h"
+
+void SaveDownloads(const wchar_t *fileName, DownloadList &downloads);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/api__ml_downloads.h b/Src/Plugins/Library/ml_downloads/api__ml_downloads.h
new file mode 100644
index 00000000..574b0bf2
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/api__ml_downloads.h
@@ -0,0 +1,11 @@
+#ifndef NULLSOFT_ML_DOWNLOADS_API_H
+#define NULLSOFT_ML_DOWNLOADS_API_H
+
+#include "../Agave/Language/api_language.h"
+
+#include "api/application/api_application.h"
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#endif // !NULLSOFT_ML_DOWNLOADS_API_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/date.c b/Src/Plugins/Library/ml_downloads/date.c
new file mode 100644
index 00000000..bbe876c6
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/date.c
@@ -0,0 +1 @@
+#include "time.h"
diff --git a/Src/Plugins/Library/ml_downloads/date.h b/Src/Plugins/Library/ml_downloads/date.h
new file mode 100644
index 00000000..58204571
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/date.h
@@ -0,0 +1,20 @@
+#ifndef _OD_DATE_
+#define _OD_DATE_
+
+#include <time.h>
+
+
+
+/* These functions all work with "Internet"(RFC 822) format Date/Time strings only */
+/* An example of an RFC 822 format Date/Time string is "Thu, 28 Aug 2003 21:30:47 EDT" */
+
+/* converts the RFC 822 format date string into UTC Calendar time */
+time_t getDateSecs(char *date);
+
+/* returns a string that represents the given UTC Calendar time value as an
+ RFC 822 format string. The buffer is user-supplied and must be at least
+ 30 bytes in size. */
+char *getDateStr(time_t tmval, char *buffer, int gmt);
+
+#endif /*_OD_DATE_*/
+
diff --git a/Src/Plugins/Library/ml_downloads/db.cpp b/Src/Plugins/Library/ml_downloads/db.cpp
new file mode 100644
index 00000000..db0e1b5b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/db.cpp
@@ -0,0 +1,150 @@
+#include <shlwapi.h>
+
+#include "api__ml_downloads.h"
+#include "Downloaded.h"
+#include "../nde/nde_c.h"
+#include "../nu/AutoLock.h"
+
+/* DB Schema
+Source
+Url
+Title
+DownloadDate
+Length
+Filename
+*/
+
+static Nullsoft::Utility::LockGuard dbcs;
+static nde_table_t table = 0;
+static nde_database_t db = 0;
+using namespace Nullsoft::Utility;
+
+enum
+{
+ DB_ID_SOURCE = 0,
+ DB_ID_URL = 1,
+ DB_ID_TITLE = 2,
+ DB_ID_DOWNLOADDATE = 3,
+ DB_ID_LENGTH = 4,
+ DB_ID_FILENAME = 5
+};
+
+static bool OpenDatabase()
+{
+ AutoLock lock(dbcs);
+ if (!db)
+ db = NDE_CreateDatabase();
+
+ return true;
+}
+
+void CloseDatabase()
+{
+ AutoLock lock( dbcs );
+ if ( db )
+ {
+ if ( table )
+ NDE_Database_CloseTable( db, table );
+
+ NDE_DestroyDatabase( db );
+ }
+
+ db = 0;
+}
+
+static void CreateFields( nde_table_t table )
+{
+ // create defaults
+ NDE_Table_NewColumnW( table, DB_ID_SOURCE, L"source", FIELD_STRING );
+ NDE_Table_NewColumnW( table, DB_ID_URL, L"url", FIELD_STRING );
+ NDE_Table_NewColumnW( table, DB_ID_TITLE, L"title", FIELD_STRING );
+ NDE_Table_NewColumnW( table, DB_ID_DOWNLOADDATE, L"downloaddate", FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, DB_ID_LENGTH, L"length", FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, DB_ID_FILENAME, L"filename", FIELD_FILENAME );
+ NDE_Table_PostColumns( table );
+ NDE_Table_AddIndexByIDW( table, DB_ID_URL, L"url" );
+}
+
+static bool OpenTable()
+{
+ AutoLock lock( dbcs );
+ if ( !OpenDatabase() )
+ return false;
+
+ if ( !table )
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t tablePath[ MAX_PATH ] = { 0 }, indexPath[ MAX_PATH ] = { 0 };
+
+ PathCombineW( tablePath, inidir, L"plugins" );
+ PathAppendW( tablePath, L"downloads.dat" );
+
+ PathCombineW( indexPath, inidir, L"plugins" );
+ PathAppendW( indexPath, L"downloads.idx" );
+
+ table = NDE_Database_OpenTable( db, tablePath, indexPath, NDE_OPEN_ALWAYS, NDE_CACHE );
+ if ( table )
+ CreateFields( table );
+ }
+
+ return !!table;
+}
+
+static void db_add( nde_scanner_t s, unsigned char id, wchar_t *data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_StringField_SetString( f, data );
+ }
+}
+
+static void db_add_int( nde_scanner_t s, unsigned char id, int data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_IntegerField_SetValue( f, data );
+ }
+}
+
+static void db_add_time( nde_scanner_t s, unsigned char id, time_t data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_IntegerField_SetValue( f, static_cast<int>( data ) );
+ }
+}
+
+bool AddDownloadData( const DownloadedFile &data )
+{
+ AutoLock lock( dbcs );
+ if ( !OpenTable() )
+ return false;
+
+ nde_scanner_t s = NDE_Table_CreateScanner( table );
+ if ( s )
+ {
+ NDE_Scanner_New( s );
+ db_add( s, DB_ID_SOURCE, data.source );
+ db_add( s, DB_ID_URL, data.url );
+ db_add( s, DB_ID_TITLE, data.title );
+ db_add_time( s, DB_ID_DOWNLOADDATE, data.downloadDate );
+ db_add_int( s, DB_ID_LENGTH, (int)data.totalSize );
+ db_add( s, DB_ID_FILENAME, data.path );
+ NDE_Scanner_Post( s );
+ NDE_Table_DestroyScanner( table, s );
+ NDE_Table_Sync( table );
+
+ return true;
+ }
+ return false;
+}
+
+void CompactDatabase()
+{
+ AutoLock lock( dbcs );
+ if ( OpenTable() )
+ NDE_Table_Compact( table );
+}
diff --git a/Src/Plugins/Library/ml_downloads/errors.h b/Src/Plugins/Library/ml_downloads/errors.h
new file mode 100644
index 00000000..29766060
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/errors.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_ML_DOWNLOADS_ERRORS_H
+#define NULLSOFT_ML_DOWNLOADS_ERRORS_H
+
+enum
+{
+ DOWNLOAD_SUCCESS = 0,
+ DOWNLOAD_404,
+ DOWNLOAD_TIMEOUT,
+ DOWNLOAD_NOHTTP,
+ DOWNLOAD_NOPARSER,
+ DOWNLOAD_CONNECTIONRESET,
+ DOWNLOAD_ERROR_PARSING_XML,
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/layout.cpp b/Src/Plugins/Library/ml_downloads/layout.cpp
new file mode 100644
index 00000000..bfdd9591
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/layout.cpp
@@ -0,0 +1,215 @@
+#include "./layout.h"
+
+#include <windows.h>
+
+BOOL Layout_Initialize( HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout )
+{
+ if ( NULL == itemList || NULL == layout )
+ return FALSE;
+
+ LAYOUTITEM *item;
+
+ for ( INT i = 0; i < itemCount; i++ )
+ {
+ item = &layout[ i ];
+ item->hwnd = GetDlgItem( hwnd, itemList[ i ] );
+ if ( item->hwnd == NULL )
+ continue;
+
+ if ( FALSE == GetWindowRect( item->hwnd, &item->rect ) )
+ SetRectEmpty( &item->rect );
+ else
+ MapWindowPoints( HWND_DESKTOP, hwnd, (POINT *)&item->rect, 2 );
+
+ item->cx = item->rect.right - item->rect.left;
+ item->cy = item->rect.bottom - item->rect.top;
+ item->x = item->rect.left;
+ item->y = item->rect.top;
+ item->flags = 0;
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_Perform( HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw )
+{
+ HDWP hdwp, hdwpTemp;
+ hdwp = BeginDeferWindowPos( layoutCount );
+ if ( hdwp == NULL )
+ return FALSE;
+
+ UINT baseFlags = SWP_NOACTIVATE | SWP_NOZORDER;
+ if ( fRedraw == FALSE )
+ baseFlags |= ( SWP_NOREDRAW | SWP_NOCOPYBITS );
+
+ LAYOUTITEM *item;
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( item->hwnd == NULL )
+ continue;
+
+ UINT flags = baseFlags | ( item->flags & ~( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) );
+ if ( item->x == item->rect.left && item->y == item->rect.top )
+ flags |= SWP_NOMOVE;
+
+ if ( item->cx == ( item->rect.right - item->rect.left ) && item->cy == ( item->rect.bottom - item->rect.top ) )
+ flags |= SWP_NOSIZE;
+
+ if ( ( SWP_HIDEWINDOW & item->flags ) != 0 )
+ {
+ UINT windowStyle = (UINT)GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( ( WS_VISIBLE & windowStyle ) != 0 )
+ {
+ SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE );
+ if ( FALSE != fRedraw )
+ {
+ RedrawWindow( hwnd, &item->rect, NULL, RDW_INVALIDATE | RDW_ERASE );
+ }
+ }
+ }
+
+ if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED ) & flags ) )
+ {
+ hdwpTemp = DeferWindowPos( hdwp, item->hwnd, NULL, item->x, item->y, item->cx, item->cy, flags );
+ if ( hdwpTemp == NULL )
+ break;
+
+ hdwp = hdwpTemp;
+ }
+ }
+
+ BOOL result = ( hdwp != NULL ) ? EndDeferWindowPos( hdwp ) : FALSE;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( NULL != item->hwnd && 0 != ( SWP_SHOWWINDOW & item->flags ) )
+ {
+ UINT windowStyle = (UINT)GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( 0 == ( WS_VISIBLE & windowStyle ) )
+ {
+ SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle | WS_VISIBLE );
+ if ( FALSE != fRedraw )
+ RedrawWindow( item->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN );
+ }
+ }
+ }
+
+ return result;
+}
+
+static void Layout_SetItemVisibility( const RECT *rect, LAYOUTITEM *item )
+{
+ if ( NULL == item || NULL == item->hwnd )
+ return;
+
+ BOOL outsider = ( item->cx <= 0 || item->cy <= 0 ||
+ item->x >= rect->right || item->y >= rect->bottom ||
+ ( item->x + item->cx ) < rect->left || ( item->y + item->cy ) < rect->top );
+
+ UINT windowStyle = (UINT)GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( 0 == ( WS_VISIBLE & windowStyle ) )
+ {
+ if ( !outsider )
+ {
+ item->flags |= SWP_SHOWWINDOW;
+ }
+ }
+ else
+ {
+ if ( outsider )
+ {
+ item->flags |= SWP_HIDEWINDOW;
+ }
+ }
+}
+
+BOOL Layout_SetVisibility( const RECT *rect, LAYOUTITEM *layout, INT layoutCount )
+{
+ if ( NULL == rect || NULL == layout )
+ return FALSE;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ Layout_SetItemVisibility( rect, &layout[ i ] );
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_SetVisibilityEx( const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout )
+{
+ if ( NULL == rect || NULL == indexList || NULL == layout )
+ return FALSE;
+
+ for ( INT i = 0; i < indexCount; i++ )
+ {
+ Layout_SetItemVisibility( rect, &layout[ indexList[ i ] ] );
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_GetValidRgn( HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount )
+{
+ if ( NULL == validRgn )
+ return FALSE;
+
+ SetRectRgn( validRgn, 0, 0, 0, 0 );
+
+ if ( NULL == layout )
+ return FALSE;
+
+ HRGN rgn = CreateRectRgn( 0, 0, 0, 0 );
+ if ( NULL == rgn )
+ return FALSE;
+
+ LAYOUTITEM *item;
+ LONG l, t, r, b;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( NULL != item->hwnd && 0 == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) & item->flags ) )
+ {
+ l = item->x + parrentOffset.x;
+ t = item->y + parrentOffset.y;
+ r = l + item->cx;
+ b = t + item->cy;
+ if ( 0 != ( SWP_NOREDRAW & item->flags ) || ( l == item->rect.left && t == item->rect.top && r == item->rect.right && b == item->rect.bottom ) )
+ {
+ if ( NULL != validRect )
+ {
+ if ( l < validRect->left )
+ l = validRect->left;
+
+ if ( t < validRect->top )
+ t = validRect->top;
+
+ if ( r > validRect->right )
+ r = validRect->right;
+
+ if ( b > validRect->bottom )
+ b = validRect->bottom;
+ }
+
+ if ( l < r && t < b )
+ {
+ SetRectRgn( rgn, l, t, r, b );
+ CombineRgn( validRgn, validRgn, rgn, RGN_OR );
+
+ if ( NULLREGION != GetUpdateRgn( item->hwnd, rgn, FALSE ) )
+ {
+ OffsetRgn( rgn, parrentOffset.x, parrentOffset.y );
+ CombineRgn( validRgn, validRgn, rgn, RGN_DIFF );
+ }
+ }
+ }
+ }
+ }
+
+ DeleteObject( rgn );
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/layout.h b/Src/Plugins/Library/ml_downloads/layout.h
new file mode 100644
index 00000000..9af73396
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/layout.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef struct __LAYOUTITEM
+{
+ HWND hwnd;
+ LONG x;
+ LONG y;
+ LONG cx;
+ LONG cy;
+ UINT flags;
+ RECT rect;
+} LAYOUTITEM;
+
+
+#define LI_GET_R(__li) ((__li).x + (__li).cx)
+#define LI_GET_B(__li) ((__li).y + (__li).cy)
+#define LI_EXPAND_W(__li, __delta) { (__li).cx += (__delta); }
+#define LI_EXPAND_H(__li, __delta) { (__li).cy += (__delta); }
+#define LI_SHIFT_L(__li, __delta) { (__li).x += (__delta); }
+#define LI_SHIFT_T(__li, __delta) { { (__li).y += (__delta); }
+#define LI_SET_L(__li, __val) { (__li).x = (__val); }
+#define LI_SET_T(__li, __val) { (__li).y = (__val); }
+#define LI_SET_W(__li, __val) { (__li).cx = (__val); }
+#define LI_SET_H(__li, __val) { (__li).cy = (__val); }
+#define LI_SET_R(__li, __val) { (__li).cx = ((__val) - (__li).x); }
+#define LI_SET_B(__li, __val) { (__li).cy = ((__val) - (__li).y); }
+
+BOOL Layout_Initialize(HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout);
+BOOL Layout_SetVisibilityEx(const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout);
+BOOL Layout_SetVisibility(const RECT *rect, LAYOUTITEM *layout, INT layoutCount);
+BOOL Layout_Perform(HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw);
+
+BOOL Layout_GetValidRgn(HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount);
+
+
+#endif //NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ml_downloads.rc b/Src/Plugins/Library/ml_downloads/ml_downloads.rc
new file mode 100644
index 00000000..35737775
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.rc
@@ -0,0 +1,249 @@
+// 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_DOWNLOADS DIALOGEX 0, 0, 266, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_DOWNLOADLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,0,264,79
+ CONTROL "Play",IDC_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,81,37,11
+ CONTROL "Enqueue",IDC_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,39,81,37,11
+ CONTROL "Remove",IDC_REMOVE,"Button",BS_OWNERDRAW | WS_TABSTOP,80,81,37,11
+ CONTROL "Clean up",IDC_CLEANUP,"Button",BS_OWNERDRAW | WS_TABSTOP,120,81,37,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,163,81,102,11
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_MENU1 MENU
+BEGIN
+ POPUP "Downloads"
+ BEGIN
+ MENUITEM "Play selection\tEnter", IDC_PLAY
+ MENUITEM "Enqueue selection\tShift+Enter", IDC_ENQUEUE
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_Menu
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", IDC_SELECTALL
+ MENUITEM "View file info...\tAlt+3", IDC_INFOBOX
+ MENUITEM "Explore item folder\tCtrl+F", ID_DOWNLOADS_EXPLORERITEMFOLDER
+ MENUITEM SEPARATOR
+ MENUITEM "Remove from list\tDel", IDC_REMOVE
+ MENUITEM "Delete file\tShift+Del", IDC_DELETE
+ MENUITEM "Cancel download", ID_DOWNLOADS_CANCELDOWNLOAD
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_DOWNLOADS BITMAP "resources\\downloadIcon.bmp"
+IDB_TREEITEM_DOWNLOADS1 BITMAP "resources\\downloadIcon1.bmp"
+IDB_TREEITEM_DOWNLOADS2 BITMAP "resources\\downloadIcon2.bmp"
+IDB_TREEITEM_DOWNLOADS3 BITMAP "resources\\downloadIcon3.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_DOWNLOAD_ACCELERATORS ACCELERATORS
+BEGIN
+ "F", ID_DOWNLOADS_EXPLORERITEMFOLDER, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, IDC_DELETE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ "3", IDC_INFOBOX, VIRTKEY, ALT, NOINVERT
+ VK_RETURN, IDC_PLAY, VIRTKEY, NOINVERT
+ VK_DELETE, IDC_REMOVE, VIRTKEY, NOINVERT
+ "A", IDC_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ VK_RETURN, IDC_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Downloads v%d.%02d"
+ 65535 "{706549D3-D813-45dd-9A0B-E3793A1B63A8}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DOWNLOADS "Downloads"
+ IDS_DOWNLOADING "Downloading..."
+ IDS_CHOOSE_FOLDER "Choose folder to store downloaded media."
+ IDS_FILE_NOT_FOUND "Cannot connect to %s\nFile not Found."
+ IDS_CONNECTION_TIMED_OUT "Cannot connect to %s\nConnection timed out."
+ IDS_NO_JNETLIB "HTTP Downloader library is missing.\nPlease reinstall Winamp."
+ IDS_JNETLIB_MISSING "JNetLib missing"
+ IDS_NO_EXPAT "XML Parsing library is missing.\nPlease reinstall Winamp."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_EXPAT_MISSING "Expat missing"
+ IDS_WDAY_SUN "Sun"
+ IDS_WDAY_MON "Mon"
+ IDS_WDAY_TUE "Tue"
+ IDS_WDAY_WED "Wed"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_WDAY_THU "Thu"
+ IDS_WDAY_FRI "Fri"
+ IDS_WDAY_SAT "Sat"
+ IDS_MONTH_JAN "Jan"
+ IDS_MONTH_FEB "Feb"
+ IDS_MONTH_MAR "Mar"
+ IDS_MONTH_APR "Apr"
+ IDS_MONTH_MAY "May"
+ IDS_MONTH_JUN "Jun"
+ IDS_MONTH_JUL "Jul"
+ IDS_MONTH_AUG "Aug"
+ IDS_MONTH_SEP "Sep"
+ IDS_MONTH_OCT "Oct"
+ IDS_MONTH_NOV "Nov"
+ IDS_MONTH_DEC "Dec"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_INVALID_LINK "Invalid link (404 or timeout)"
+ IDS_CONNECTION_RESET_BY_PEER "Connection reset by peer."
+ IDS_DOWNLOADING_PERCENT "Downloading %d%%"
+ IDS_SOURCE "Source"
+ IDS_TITLE "Title"
+ IDS_PROGRESS "Status"
+ IDS_PATH "Download Location"
+ IDS_PERM_DELETE_ARE_YOU_SURE
+ "This will permanently delete this file, are you sure?"
+ IDS_PERM_DELETE_THESE_ARE_YOU_SURE
+ "This will permanently delete these %d files, are you sure?"
+ IDS_DELETION "Deletion"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLEAR_ALL_FINISHED_DOWNLOADS
+ "This will clear all finished downloads, are you sure?\n\nTip: You can view all your downloaded podcasts in Local Media/Podcasts"
+ IDS_CLEAN_UP_LIST "Clean Up List"
+ IDS_DONE "Done"
+ IDS_SAVE "Save"
+ IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION
+ "The media library feature you are attempting to use requires an internet connection. Please make sure you are connected to the internet and try again."
+ IDS_ADD_TO_DOWNLOADS "Added to downloads."
+ IDS_SURE_YOU_WANT_TO_REMOVE_THIS
+ "Are you sure you want to remove %s\n(%s)"
+ IDS_CONFIRM "Confirm"
+ IDS_CANCEL_DOWNLOADS_AND_QUIT
+ "There are downloads currently in progress.\nAre you sure you want to cancel these downloads and quit?"
+ IDS_CONFIRM_QUIT "Confirm Quit"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DELETEFAILED "Delete Failed"
+ IDS_SORRY "Sorry"
+ IDS_DATE "Download Date"
+ IDS_N_A "n/a"
+ IDS_SIZE "Size"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DOWNLOAD_SUCCESS "Completed"
+ IDS_DOWNLOAD_FAILURE "Failed"
+ IDS_DOWNLOAD_CANCELED "Cancelled"
+ IDS_DOWNLOAD_PENDING "Pending..."
+ IDS_DOWNLOADING_PROGRESS
+ "Downloading %u file(s), %s of %s complete (%d%%)"
+ IDS_DOWNLOADING_COMPLETE "Downloading %u files, %s complete"
+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_downloads/ml_downloads.sln b/Src/Plugins/Library/ml_downloads/ml_downloads.sln
new file mode 100644
index 00000000..dd12dca8
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.sln
@@ -0,0 +1,93 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_downloads", "ml_downloads.vcxproj", "{23E1FE5D-8833-4095-97D5-A5972CFA477C}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}"
+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
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|Win32.Build.0 = Debug|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|x64.ActiveCfg = Debug|x64
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|x64.Build.0 = Debug|x64
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|Win32.ActiveCfg = Release|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|Win32.Build.0 = Release|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|x64.ActiveCfg = Release|x64
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj
new file mode 100644
index 00000000..3d27d8bf
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj
@@ -0,0 +1,370 @@
+<?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>{23E1FE5D-8833-4095-97D5-A5972CFA477C}</ProjectGuid>
+ <RootNamespace>ml_downloads</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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>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>true</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>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>
+ </Link>
+ <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>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\Alias.h" />
+ <ClInclude Include="..\..\..\nu\ChildSizer.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\xml\XMLDOM.h" />
+ <ClInclude Include="..\..\..\xml\XMLNode.h" />
+ <ClInclude Include="api__ml_downloads.h" />
+ <ClInclude Include="Defaults.h" />
+ <ClInclude Include="Downloaded.h" />
+ <ClInclude Include="DownloadsDialog.h" />
+ <ClInclude Include="DownloadsParse.h" />
+ <ClInclude Include="DownloadStatus.h" />
+ <ClInclude Include="DownloadThread.h" />
+ <ClInclude Include="DownloadViewCallback.h" />
+ <ClInclude Include="errors.h" />
+ <ClInclude Include="layout.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="ParseUtil.h" />
+ <ClInclude Include="Preferences.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="RFCDate.h" />
+ <ClInclude Include="Util.h" />
+ <ClInclude Include="XMLWriter.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp" />
+ <ClCompile Include="..\..\..\xml\XMLNode.cpp" />
+ <ClCompile Include="db.cpp" />
+ <ClCompile Include="Defaults.cpp" />
+ <ClCompile Include="Downloaded.cpp" />
+ <ClCompile Include="DownloadsDialog.cpp" />
+ <ClCompile Include="DownloadsParse.cpp" />
+ <ClCompile Include="DownloadStatus.cpp" />
+ <ClCompile Include="DownloadThread.cpp" />
+ <ClCompile Include="DownloadViewCallback.cpp" />
+ <ClCompile Include="layout.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="ParseUtil.cpp" />
+ <ClCompile Include="Preferences.cpp" />
+ <ClCompile Include="RFCDate.cpp" />
+ <ClCompile Include="util.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="XMLWriter.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\bitmap1.bmp" />
+ <Image Include="resources\downloadIcon.bmp" />
+ <Image Include="resources\downloadIcon1.bmp" />
+ <Image Include="resources\downloadIcon2.bmp" />
+ <Image Include="resources\downloadIcon3.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_downloads.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="DESIGN.txt" />
+ <Text Include="TODO.txt" />
+ </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_downloads/ml_downloads.vcxproj.filters b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters
new file mode 100644
index 00000000..9883c853
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="db.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Defaults.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Downloaded.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadsDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadsParse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadStatus.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadThread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadViewCallback.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="layout.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ParseUtil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Preferences.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RFCDate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="XMLWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLNode.cpp">
+ <Filter>Source Files\xml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp">
+ <Filter>Source Files\xml</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_downloads.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Defaults.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Downloaded.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadsDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadsParse.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadStatus.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadThread.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadViewCallback.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="errors.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="layout.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="XMLWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RFCDate.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Preferences.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ParseUtil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\Alias.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ChildSizer.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\xml\XMLDOM.h">
+ <Filter>Header Files\xml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\xml\XMLNode.h">
+ <Filter>Header Files\xml</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\downloadIcon.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\downloadIcon1.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\downloadIcon2.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\downloadIcon3.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\bitmap1.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="DESIGN.txt" />
+ <Text Include="TODO.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{fbb3be6a-b7ea-4313-b14c-3e90ed67b4ec}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{52a7174b-e9ac-428f-8f7c-921bcb4e1637}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{dcbaa963-b32c-488a-a8d0-cd04a987d873}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{3d2c389d-dacb-4c28-985e-6404ec435898}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{87d018de-72e8-4fe5-94e6-ca03aeb0ede9}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{4ec61852-ecfb-400c-ae96-803f47338909}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\xml">
+ <UniqueIdentifier>{87422940-a801-4fae-9a04-bac04b096146}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{7d1c9f6a-447c-4b7e-bb0a-bd7aef6f1919}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{b5409075-f53b-4665-991a-3996953e0b03}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\xml">
+ <UniqueIdentifier>{c8ff3a21-0fca-40ea-bce7-8b7492a07ad0}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_downloads.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/png.rc b/Src/Plugins/Library/ml_downloads/png.rc
new file mode 100644
index 00000000..5f5d3de1
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/png.rc
@@ -0,0 +1,63 @@
+// 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
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RCDATA
+//
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.\0"
+END
+
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#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_downloads/resource.h b/Src/Plugins/Library/ml_downloads/resource.h
new file mode 100644
index 00000000..c3c33609
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resource.h
@@ -0,0 +1,120 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_downloads.rc
+//
+#define IDS_NULLSOFT_PODCAST 0
+#define IDS_PODCASTS 1
+#define IDS_DOWNLOADING_KB_COMPLETE 2
+#define IDS_DOWNLOADING_KB_PROGRESS 3
+#define IDS_DOWNLOADS 5
+#define IDS_DOWNLOADING 6
+#define IDS_CHOOSE_FOLDER 7
+#define IDS_FILE_NOT_FOUND 9
+#define IDS_CONNECTION_TIMED_OUT 10
+#define IDS_ERROR_PARSING_XML 11
+#define IDS_NO_JNETLIB 13
+#define IDS_JNETLIB_MISSING 14
+#define IDS_NO_EXPAT 15
+#define IDS_EXPAT_MISSING 16
+#define IDS_CONNECTION_RESET 17
+#define IDS_WDAY_SUN 28
+#define IDS_WDAY_MON 29
+#define IDS_WDAY_TUE 30
+#define IDS_WDAY_WED 31
+#define IDS_WDAY_THU 32
+#define IDS_WDAY_FRI 33
+#define IDS_WDAY_SAT 34
+#define IDS_MONTH_JAN 35
+#define IDS_MONTH_FEB 36
+#define IDS_MONTH_MAR 37
+#define IDS_MONTH_APR 38
+#define IDS_MONTH_MAY 39
+#define IDS_MONTH_JUN 40
+#define IDS_MONTH_JUL 41
+#define IDS_MONTH_AUG 42
+#define IDS_MONTH_SEP 43
+#define IDS_MONTH_OCT 44
+#define IDS_MONTH_NOV 45
+#define IDS_MONTH_DEC 46
+#define IDS_INVALID_LINK 54
+#define IDS_CONNECTION_RESET_BY_PEER 55
+#define IDS_DOWNLOADING_PERCENT 56
+#define IDS_SOURCE 57
+#define IDS_ITEM 58
+#define IDS_TITLE 58
+#define IDS_PROGRESS 59
+#define IDS_PATH 60
+#define IDS_PERM_DELETE_ARE_YOU_SURE 61
+#define IDS_PERM_DELETE_THESE_ARE_YOU_SURE 62
+#define IDS_DELETION 63
+#define IDS_CLEAR_ALL_FINISHED_DOWNLOADS 64
+#define IDS_CLEAN_UP_LIST 65
+#define IDS_DONE 66
+#define IDS_SAVE 67
+#define IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION 68
+#define IDS_ADD_TO_DOWNLOADS 69
+#define IDS_SURE_YOU_WANT_TO_REMOVE_THIS 74
+#define IDS_CONFIRM 75
+#define IDS_CANCEL_DOWNLOADS_AND_QUIT 76
+#define IDS_CONFIRM_QUIT 77
+#define IDS_DELETEFAILED 80
+#define IDS_SORRY 81
+#define IDS_DATE 82
+#define IDS_N_A 83
+#define IDS_SIZE 84
+#define IDD_DOWNLOADS 107
+#define IDR_MENU1 112
+#define IDR_VIEW_DOWNLOAD_ACCELERATORS 122
+#define IDB_TREEITEM_DOWNLOADS 126
+#define IDB_TREEITEM_DOWNLOADS1 131
+#define IDS_DOWNLOAD_SUCCESS 134
+#define IDS_DOWNLOAD_FAILURE 135
+#define IDS_DOWNLOAD_CANCELED 136
+#define IDB_TREEITEM_DOWNLOADS2 136
+#define IDB_BITMAP2 137
+#define IDB_TREEITEM_DOWNLOADS3 137
+#define IDS_DOWNLOAD_PENDING 138
+#define IDS_DOWNLOADING_PROGRESS 139
+#define IDS_DOWNLOADING_COMPLETE 140
+#define IDC_CUSTOM 1000
+#define IDC_DELETE 1011
+#define IDC_DOWNLOADLIST 1029
+#define IDC_STATICDOWNLOADLOCATION 1034
+#define IDC_STATICAUTODOWNLOAD 1035
+#define IDC_ADD 1044
+#define IDC_EDIT 1045
+#define IDC_PLAY 1047
+#define IDC_ENQUEUE 1048
+#define IDC_DOWNLOAD 1050
+#define IDC_CANCEL 1051
+#define IDC_BROWSE 1051
+#define IDC_ENQUEUE2 1051
+#define IDC_BROWSER 1057
+#define IDC_STATUS 1058
+#define IDC_DOWNLOADLOCATION 1059
+#define IDC_SETTINGSBOX 1060
+#define IDC_CLEANUP 1060
+#define IDC_PLAYACTION 1065
+#define IDC_ENQUEUEACTION 1066
+#define ID_PLAYMEDIA_IDC 40018
+#define IDC_REMOVE 40024
+#define ID_Menu 40026
+#define IDC_INFOBOX 40028
+#define IDC_SELECTALL 40030
+#define ID_NAVIGATION_PREFERENCES 40032
+#define ID_NAVIGATION_HELP 40033
+#define ID_DOWNLOADS_EXPLORERITEMFOLDER 40034
+#define ID_ENQUEUE 40036
+#define ID_DOWNLOADS_CANCELDOWNLOAD 40043
+#define IDS_PLUGIN_NAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 141
+#define _APS_NEXT_COMMAND_VALUE 40044
+#define _APS_NEXT_CONTROL_VALUE 1068
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmp
new file mode 100644
index 00000000..ab762e4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon.png b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.png
new file mode 100644
index 00000000..ff92ea88
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmp
new file mode 100644
index 00000000..4fef75f4
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmp
new file mode 100644
index 00000000..4b59943a
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmp
new file mode 100644
index 00000000..4e18a399
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/util.cpp b/Src/Plugins/Library/ml_downloads/util.cpp
new file mode 100644
index 00000000..9a8a7927
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/util.cpp
@@ -0,0 +1,255 @@
+#include "main.h"
+#include "./util.h"
+#include "api__ml_downloads.h"
+
+#include <strsafe.h>
+
+LPWSTR Plugin_MallocString(size_t cchLen)
+{
+ return (LPWSTR)calloc(cchLen, sizeof(WCHAR));
+}
+
+void Plugin_FreeString(LPWSTR pszString)
+{
+ if (NULL != pszString)
+ {
+ free(pszString);
+ }
+}
+
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen)
+{
+ return (LPWSTR)realloc(pszString, sizeof(WCHAR) * cchLen);
+}
+
+LPWSTR Plugin_CopyString(LPCWSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenW(pszSource) + 1;
+
+ LPWSTR copy = Plugin_MallocString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(WCHAR) * cchSource);
+ }
+ return copy;
+}
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen)
+{
+ return (LPSTR)calloc(cchLen, sizeof(CHAR));
+}
+
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenA(pszSource) + 1;
+
+ LPSTR copy = Plugin_MallocAnsiString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(CHAR) * cchSource);
+ }
+ return copy;
+
+}
+void Plugin_FreeAnsiString(LPSTR pszString)
+{
+ Plugin_FreeString((LPWSTR)pszString);
+}
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
+{
+ INT cchBuffer = WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, lpDefaultChar, lpUsedDefaultChar);
+ if (0 == cchBuffer) return NULL;
+
+ LPSTR buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, buffer, cchBuffer, lpDefaultChar, lpUsedDefaultChar))
+ {
+ Plugin_FreeAnsiString(buffer);
+ return NULL;
+ }
+ return buffer;
+}
+
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte)
+{
+ if (NULL == lpMultiByteStr) return NULL;
+ INT cchBuffer = MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0);
+ if (NULL == cchBuffer) return NULL;
+
+ if (cbMultiByte > 0) cchBuffer++;
+
+ LPWSTR buffer = Plugin_MallocString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, buffer, cchBuffer))
+ {
+ Plugin_FreeString(buffer);
+ return NULL;
+ }
+
+ if (cbMultiByte > 0)
+ {
+ buffer[cchBuffer - 1] = L'\0';
+ }
+ return buffer;
+}
+
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource)
+{
+ return (IS_INTRESOURCE(pszResource)) ?
+ (LPWSTR)pszResource :
+ Plugin_CopyString(pszResource);
+}
+
+void Plugin_FreeResString(LPWSTR pszResource)
+{
+ if (!IS_INTRESOURCE(pszResource))
+ Plugin_FreeString(pszResource);
+}
+
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString)
+{
+ if (NULL == pszBuffer)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ if (NULL == pszString)
+ {
+ pszBuffer[0] = L'\0';
+ }
+ else if (IS_INTRESOURCE(pszString))
+ {
+ if (NULL == WASABI_API_LNG)
+ hr = E_FAIL;
+ else
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszString, pszBuffer, cchBufferMax);
+ }
+ else
+ {
+ hr = StringCchCopy(pszBuffer, cchBufferMax, pszString);
+ }
+ return hr;
+}
+
+void Plugin_SafeRelease(IUnknown *pUnk)
+{
+ if (NULL != pUnk)
+ pUnk->Release();
+}
+
+HRESULT Plugin_FileExtensionFromUrl(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension)
+{
+ LPCWSTR cursor = pszUrl;
+ while (L'\0' != *cursor && L'?' != *cursor)
+ cursor = CharNext(cursor);
+
+ LPCWSTR end = cursor;
+
+ while (cursor != pszUrl && L'/' != *cursor && L'\\' != *cursor && L'.' != *cursor)
+ cursor = CharPrev(pszUrl, cursor);
+
+ if (L'.' == *cursor && cursor != pszUrl)
+ {
+ if (CharNext(cursor) < end)
+ {
+ INT cchExtension = (INT)(INT_PTR)(end - cursor);
+ return StringCchCopyN(pszBuffer, cchBufferMax, cursor, cchExtension);
+ }
+ }
+
+ return StringCchCopy(pszBuffer, cchBufferMax, defaultExtension);
+}
+
+void Plugin_ReplaceBadPathChars(LPWSTR pszPath)
+{
+ if (NULL == pszPath)
+ return;
+
+ while (L'\0' != *pszPath)
+ {
+ switch(*pszPath)
+ {
+ case L'?':
+ case L'/':
+ case L'\\':
+ case L':':
+ case L'*':
+ case L'\"':
+ case L'<':
+ case L'>':
+ case L'|':
+ *pszPath = L'_';
+ break;
+ default:
+ if (*pszPath < 32) *pszPath = L'_';
+ break;
+ }
+ pszPath = CharNextW(pszPath);
+ }
+}
+
+INT Plugin_CleanDirectory(LPWSTR pszPath)
+{
+ if (NULL == pszPath)
+ return 0;
+
+ INT cchPath = lstrlen(pszPath);
+ LPWSTR cursor = pszPath + cchPath;
+ while (cursor-- != pszPath && (L' ' == *cursor || L'.' == *cursor))
+ *cursor = L'\0';
+
+ return (cchPath - (INT)(INT_PTR)(cursor - pszPath) - 1);
+}
+
+HRESULT Plugin_EnsurePathExist(LPCWSTR pszDirectory)
+{
+ DWORD ec = ERROR_SUCCESS;
+
+ UINT errorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+
+ if (0 == CreateDirectory(pszDirectory, NULL))
+ {
+ ec = GetLastError();
+ if (ERROR_PATH_NOT_FOUND == ec)
+ {
+ LPCWSTR pszBlock = pszDirectory;
+ WCHAR szBuffer[MAX_PATH] = {0};
+
+ LPCTSTR pszCursor = PathFindNextComponent(pszBlock);
+ ec = (pszCursor == pszBlock || S_OK != StringCchCopyN(szBuffer, ARRAYSIZE(szBuffer), pszBlock, (pszCursor - pszBlock))) ?
+ ERROR_INVALID_NAME : ERROR_SUCCESS;
+
+ pszBlock = pszCursor;
+
+ while (ERROR_SUCCESS == ec && NULL != (pszCursor = PathFindNextComponent(pszBlock)))
+ {
+ if (pszCursor == pszBlock || S_OK != StringCchCatN(szBuffer, ARRAYSIZE(szBuffer), pszBlock, (pszCursor - pszBlock)))
+ ec = ERROR_INVALID_NAME;
+
+ if (ERROR_SUCCESS == ec && !CreateDirectory(szBuffer, NULL))
+ {
+ ec = GetLastError();
+ if (ERROR_ALREADY_EXISTS == ec) ec = ERROR_SUCCESS;
+ }
+ pszBlock = pszCursor;
+ }
+ }
+
+ if (ERROR_ALREADY_EXISTS == ec)
+ ec = ERROR_SUCCESS;
+ }
+
+ SetErrorMode(errorMode);
+ SetLastError(ec);
+ return HRESULT_FROM_WIN32(ec);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/version.rc2 b/Src/Plugins/Library/ml_downloads/version.rc2
new file mode 100644
index 00000000..f6e65323
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,33,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,33,0,0"
+ VALUE "InternalName", "Nullsoft Downloads"
+ VALUE "LegalCopyright", "Copyright © 2010-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_downloads.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_fanzone.7z b/Src/Plugins/Library/ml_fanzone.7z
new file mode 100644
index 00000000..982e8592
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone.7z
Binary files differ
diff --git a/Src/Plugins/Library/ml_fanzone/CMakeLists.txt b/Src/Plugins/Library/ml_fanzone/CMakeLists.txt
new file mode 100644
index 00000000..2fe08d33
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/CMakeLists.txt
@@ -0,0 +1,77 @@
+# Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+#
+# Source files.
+#
+
+# ml_fanzone sources.
+set(CEFSIMPLE_SRCS
+ cef_app.cc
+ cef_app.h
+ cef_handler.cc
+ cef_handler.h
+ )
+set(CEFSIMPLE_SRCS_WINDOWS
+ ml_fanzone.rc
+ main.cpp
+ resource.h
+ cef_handler_win.cc
+ )
+APPEND_PLATFORM_SOURCES(CEFSIMPLE_SRCS)
+source_group(ml_fanzone FILES ${CEFSIMPLE_SRCS})
+
+set(CEFSIMPLE_SRCS
+ ${CEFSIMPLE_SRCS}
+ )
+
+# ml_fanzone helper sources.
+APPEND_PLATFORM_SOURCES(CEFSIMPLE_HELPER_SRCS)
+source_group(ml_fanzone FILES ${CEFSIMPLE_HELPER_SRCS})
+
+# ml_fanzone resources.
+
+
+#
+# Shared configuration.
+#
+
+# Target executable names.
+set(CEF_TARGET "ml_fanzone")
+if(OS_MAC)
+ set(CEF_HELPER_TARGET "ml_fanzone_Helper")
+ set(CEF_HELPER_OUTPUT_NAME "ml_fanzone Helper")
+else()
+ # Logical target used to link the libcef library.
+ ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}")
+endif()
+
+# Determine the target output directory.
+SET_CEF_TARGET_OUT_DIR()
+
+
+#
+# Windows configuration.
+#
+
+if(OS_WINDOWS)
+ # Executable target.
+ add_executable(${CEF_TARGET} WIN32 ${CEFSIMPLE_SRCS})
+ add_dependencies(${CEF_TARGET} libcef_dll_wrapper)
+ SET_EXECUTABLE_TARGET_PROPERTIES(${CEF_TARGET})
+ target_link_libraries(${CEF_TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS})
+
+ if(USE_SANDBOX)
+ # Logical target used to link the cef_sandbox library.
+ ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}")
+ target_link_libraries(${CEF_TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS})
+ endif()
+
+ # Add the custom manifest files to the executable.
+ ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}" "${CEF_TARGET}" "dll")
+
+ # Copy binary and resource files to the target output directory.
+ COPY_FILES("${CEF_TARGET}" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${CEF_TARGET_OUT_DIR}")
+ COPY_FILES("${CEF_TARGET}" "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}")
+endif()
diff --git a/Src/Plugins/Library/ml_fanzone/api__ml_fanzone.h b/Src/Plugins/Library/ml_fanzone/api__ml_fanzone.h
new file mode 100644
index 00000000..038ed0c8
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/api__ml_fanzone.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_API_ML_FANZONE_H
+#define NULLSOFT_API_ML_FANZONE_H
+
+#include <api/service/waServiceFactory.h>
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#endif // !NULLSOFT_API_ML_FANZONE_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_fanzone/compatibility.manifest b/Src/Plugins/Library/ml_fanzone/compatibility.manifest
new file mode 100644
index 00000000..755c272c
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/compatibility.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <!--The ID below indicates application support for Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!--The ID below indicates application support for Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!--The ID below indicates application support for Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <!-- This tag is required for XAML islands usage in the process for media scenarios. -->
+ <!-- This version corresponds to the Windows 10 May 2019 Update. -->
+ <maxversiontested Id="10.0.18362.0"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/Src/Plugins/Library/ml_fanzone/listview.cpp b/Src/Plugins/Library/ml_fanzone/listview.cpp
new file mode 100644
index 00000000..a3ceb22c
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/listview.cpp
@@ -0,0 +1,128 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+
+#include <windows.h>
+#include <commctrl.h>
+#include "listview.h"
+
+#ifdef GEN_ML_EXPORTS
+#include "main.h" // for getting the font
+#include "config.h"
+#endif
+// bp Comment: all the calls beginning "ListView_" are
+// MACROs defined in commctrl.h
+
+void W_ListView :: AddCol (char *text, int w)
+{
+ LVCOLUMN lvc={0,};
+ lvc.mask = LVCF_TEXT|LVCF_WIDTH;
+ lvc.pszText = text;
+ if (w) lvc.cx=w;
+ ListView_InsertColumn (m_hwnd, m_col, &lvc);
+ m_col++;
+}
+
+int W_ListView::GetColumnWidth (int col)
+{
+ if (col < 0 || col >= m_col) return 0;
+ return ListView_GetColumnWidth (m_hwnd, col);
+}
+
+
+int W_ListView::GetParam (int p)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = p;
+ ListView_GetItem (m_hwnd, &lvi);
+ return lvi.lParam;
+}
+
+int W_ListView::InsertItem (int p, char *text, int param)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = p;
+ lvi.pszText = text;
+ lvi.cchTextMax=strlen (text);
+ lvi.lParam = param;
+ return ListView_InsertItem (m_hwnd, &lvi);
+}
+
+
+void W_ListView::SetItemText (int p, int si, char *text)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.iSubItem = si;
+ lvi.mask = LVIF_TEXT;
+ lvi.pszText = text;
+ lvi.cchTextMax = strlen (text);
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::SetItemParam (int p, int param)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.mask=LVIF_PARAM;
+ lvi.lParam=param;
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::refreshFont ()
+{
+ if (m_font)
+ {
+ DeleteFont (m_font);
+ SetWindowFont (m_hwnd, NULL, FALSE);
+ }
+ m_font = NULL;
+
+ HWND h;
+#ifdef GEN_ML_EXPORTS
+ h=g_hwnd;
+#else
+ h=m_libraryparent;
+#endif
+ if (h && m_allowfonts)
+ {
+ int a=SendMessage (h, WM_USER+0x1000 /*WM_ML_IPC*/,66, 0x0600 /*ML_IPC_SKIN_WADLG_GETFUNC*/);
+ if (a)
+ {
+ m_font= (HFONT)a;
+ SetWindowFont (m_hwnd, m_font, FALSE);
+ }
+ }
+ InvalidateRect (m_hwnd, NULL, TRUE);
+}
+
+void W_ListView::setallowfonts (int allow)
+{
+ m_allowfonts=allow;
+}
+
+void W_ListView::setwnd (HWND hwnd)
+{
+ m_hwnd = hwnd;
+ if (hwnd)
+ {
+ ListView_SetExtendedListViewStyle (hwnd, LVS_EX_FULLROWSELECT|LVS_EX_UNDERLINEHOT );
+ refreshFont ();
+ }
+}
diff --git a/Src/Plugins/Library/ml_fanzone/listview.h b/Src/Plugins/Library/ml_fanzone/listview.h
new file mode 100644
index 00000000..5938a4dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/listview.h
@@ -0,0 +1,144 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+#if 0
+#ifndef _LISTVIEW_H_
+#define _LISTVIEW_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+class W_ListView
+{
+public:
+ W_ListView()
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ }
+ W_ListView(HWND hwnd)
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ setwnd(hwnd);
+ }
+ ~W_ListView()
+ {
+ if (m_font) DeleteFont(m_font);
+ m_font=0;
+ }
+
+ void refreshFont();
+
+#ifndef GEN_ML_EXPORTS
+ void setLibraryParentWnd(HWND hwndParent)
+ {
+ m_libraryparent=hwndParent;
+ }// for Winamp Font getting stuff
+#endif
+ void setallowfonts(int allow=1);
+ void setwnd(HWND hwnd);
+ void AddCol(char *text, int w);
+ int GetCount(void)
+ {
+ return ListView_GetItemCount(m_hwnd);
+ }
+ int GetParam(int p);
+ void DeleteItem(int n)
+ {
+ ListView_DeleteItem(m_hwnd,n);
+ }
+ void Clear(void)
+ {
+ ListView_DeleteAllItems(m_hwnd);
+ }
+ int GetSelected(int x)
+ {
+ return(ListView_GetItemState(m_hwnd, x, LVIS_SELECTED) & LVIS_SELECTED)?1:0;
+ }
+
+ int GetSelectedCount()
+ {
+ return ListView_GetSelectedCount(m_hwnd);
+ }
+
+ int GetSelectionMark()
+ {
+ return ListView_GetSelectionMark(m_hwnd);
+ }
+ void SetSelected(int x)
+ {
+ ListView_SetItemState(m_hwnd,x,LVIS_SELECTED,LVIS_SELECTED);
+ }
+ int InsertItem(int p, char *text, int param);
+ void GetItemRect(int i, RECT *r)
+ {
+ ListView_GetItemRect(m_hwnd, i, r, LVIR_BOUNDS);
+ }
+ void SetItemText(int p, int si, char *text);
+ void SetItemParam(int p, int param);
+
+ void GetText(int p, int si, char *text, int maxlen)
+ {
+ ListView_GetItemText(m_hwnd, p, si, text, maxlen);
+ }
+ int FindItemByParam(int param)
+ {
+ LVFINDINFO fi={LVFI_PARAM,0,param};
+ return ListView_FindItem(m_hwnd,-1,&fi);
+ }
+ int FindItemByPoint(int x, int y)
+ {
+ int l=GetCount();
+ for (int i=0;i<l;i++)
+ {
+ RECT r;
+ GetItemRect(i, &r);
+ if (r.left<=x && r.right>=x && r.top<=y && r.bottom>=y) return i;
+ }
+ return -1;
+ }
+ int GetColumnWidth(int col);
+ HWND getwnd(void)
+ {
+ return m_hwnd;
+ }
+
+protected:
+ HWND m_hwnd;
+ HFONT m_font;
+ int m_col;
+ int m_allowfonts;
+#ifndef GEN_ML_EXPORTS
+ HWND m_libraryparent;
+#endif
+};
+
+#endif//_LISTVIEW_H_
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_fanzone/main.cpp b/Src/Plugins/Library/ml_fanzone/main.cpp
new file mode 100644
index 00000000..f94e0e08
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/main.cpp
@@ -0,0 +1,110 @@
+#define PLUGIN_VERSION L"0.1"
+
+#include "main.h"
+
+#include "../nu/AutoWide.h"
+
+#include "../../General/gen_ml/menu.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+#include "../WAT/WAT.h"
+
+static int Init();
+static void Quit();
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_fanzone.dll)",
+ Init,
+ Quit,
+ fanzone_pluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+int fanzone_treeItem = 0;
+
+HCURSOR hDragNDropCursor;
+C_Config *g_config = 0;
+WNDPROC waProc = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_application *WASABI_API_APP = 0;
+
+static DWORD WINAPI wa_newWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
+{
+ if ( waProc )
+ return (DWORD)CallWindowProcW( waProc, hwnd, msg, wParam, lParam );
+ else
+ return (DWORD)DefWindowProc( hwnd, msg, wParam, lParam );
+}
+
+int Init()
+{
+ waProc = (WNDPROC)SetWindowLongPtrW( plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc );
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( AnonymousStatsGUID );
+ if ( sf )
+ AGAVE_API_STATS = reinterpret_cast<api_stats *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application *>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlFanZoneLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_FANZONE ), PLUGIN_VERSION );
+ plugin.description = (char *)szDescription;
+
+ wchar_t inifile[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins", inifile, MAX_PATH );
+ CreateDirectoryW( inifile, NULL );
+
+ mediaLibrary.BuildPath( L"Plugins\\gen_ml.ini", inifile, MAX_PATH );
+ g_config = new C_Config( inifile );
+
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.pszText = WASABI_API_LNGSTRINGW( IDS_FANZONE );
+ nis.item.pszInvariant = L"Fanzone Library";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.iSelectedImage = nis.item.iImage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_FANZONE );
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = { sizeof( NAVITEM ),0,NIMF_ITEMID, };
+ nvItem.hItem = MLNavCtrl_InsertItem( plugin.hwndLibraryParent, &nis );
+ MLNavItem_GetInfo( plugin.hwndLibraryParent, &nvItem );
+ fanzone_treeItem = nvItem.id;
+
+
+ return 0;
+}
+
+void Quit()
+{
+ if ( g_config != 0 )
+ delete g_config;
+}
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+}
diff --git a/Src/Plugins/Library/ml_fanzone/main.h b/Src/Plugins/Library/ml_fanzone/main.h
new file mode 100644
index 00000000..23dcf870
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/main.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_FANZONE_MAIN_H
+#define NULLSOFT_FANZONE_MAIN_H
+
+#include <windows.h>
+#include <strsafe.h>
+#include <iostream>
+
+#include "api__ml_fanzone.h"
+#include "resource.h"
+#include <windowsx.h>
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../nu/MediaLibraryInterface.h"
+
+#include "../winamp/wa_ipc.h"
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../Plugins/General/gen_ml/config.h"
+
+static HWND m_hwnd;
+
+extern winampMediaLibraryPlugin plugin;
+INT_PTR fanzone_pluginMessageProc( int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3 );
+extern int fanzone_treeItem;
+
+#endif // !NULLSOFT_FANZONE_MAIN_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_fanzone/ml_fanzone.dll.manifest b/Src/Plugins/Library/ml_fanzone/ml_fanzone.dll.manifest
new file mode 100644
index 00000000..d36f084b
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/ml_fanzone.dll.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+
+ <!--The compatibility section will be merged from build/win/compatibility.manifest -->
+
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
+ </dependentAssembly>
+ </dependency>
+
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+</assembly>
diff --git a/Src/Plugins/Library/ml_fanzone/ml_fanzone.rc b/Src/Plugins/Library/ml_fanzone/ml_fanzone.rc
new file mode 100644
index 00000000..a537e8c9
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/ml_fanzone.rc
@@ -0,0 +1,142 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#define APSTUDIO_HIDDEN_SYMBOLS
+#include "windows.h"
+#undef APSTUDIO_HIDDEN_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_CEFSIMPLE ICON "resources\\cefsimple.ico"
+
+IDI_SMALL ICON "resources\\small.ico"
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
+ "#include ""windows.h""\r\n"
+ "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_VIEW_FANZONE DIALOGEX 0, 0, 259, 168
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_FANZONE,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_TABSTOP,7,11,184,79
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_FANZONE BITMAP "resources\\fanzone_16x16.bmp"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_VIEW_FANZONE, DIALOG
+ BEGIN
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_VIEW_FANZONE AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_FANZONE "Nullsoft Fanzone Library v%s"
+ 65535 "{DBF92DD3-766E-42E8-A4D2-AE1AC88CF2D1}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_FANZONE "Fanzone"
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj b/Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj
new file mode 100644
index 00000000..182aeca0
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj
@@ -0,0 +1,354 @@
+<?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>{DBF92DD3-766E-42E8-A4D2-AE1AC88CF2D1}</ProjectGuid>
+ <RootNamespace>ml_fanzone</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ <PreferredToolArchitecture>x86</PreferredToolArchitecture>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <GenerateManifest>false</GenerateManifest>
+ </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>
+ <GenerateManifest>true</GenerateManifest>
+ </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>
+ <VcpkgEnabled>true</VcpkgEnabled>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);WIN32;_WINDOWS;_DEBUG;_USRDLL;ML_FANZONE_LIB;__STDC_CONSTANT_MACROS;__STDC_FORMAT_MACROS;_WIN32;UNICODE;_UNICODE;WINVER=0x0601;_WIN32_WINNT=0x601;NOMINMAX;WIN32_LEAN_AND_MEAN;_HAS_EXCEPTIONS=0;CEF_USE_ATL;CMAKE_INTDIR="Debug"</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <DisableSpecificWarnings>4100;4127;4244;4324;4481;4512;4701;4702;4996</DisableSpecificWarnings>
+ <LanguageStandard>stdcpp14</LanguageStandard>
+ <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;gdi32.lib;rpcrt4.lib;shlwapi.lib;ws2_32.lib;kernel32.lib;user32.lib;winspool.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;comdlg32.lib;advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>true</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>true</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <StackReserveSize>0x8000</StackReserveSize>
+ <LargeAddressAware>true</LargeAddressAware>
+ <AdditionalManifestDependencies>
+ </AdditionalManifestDependencies>
+ <DelayLoadDLLs>
+ </DelayLoadDLLs>
+ <UACUIAccess>true</UACUIAccess>
+ </Link>
+ <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>%(PreprocessorDefinitions);WIN32;_DEBUG;_WINDOWS;__STDC_CONSTANT_MACROS;__STDC_FORMAT_MACROS;_WIN32;UNICODE;_UNICODE;WINVER=0x0601;_WIN32_WINNT=0x601;NOMINMAX;WIN32_LEAN_AND_MEAN;_HAS_EXCEPTIONS=0;CEF_USE_ATL;CMAKE_INTDIR=\"Debug\"</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\external_dependencies\CEF\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>
+ </OutputManifestFile>
+ <AdditionalManifestFiles>
+ </AdditionalManifestFiles>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_FANZONE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <StackReserveSize>0x8000</StackReserveSize>
+ <LargeAddressAware>true</LargeAddressAware>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;_USRDLL;ML_FANZONE_LIB;__STDC_CONSTANT_MACROS;__STDC_FORMAT_MACROS;_WIN32;UNICODE;_UNICODE;WINVER=0x0601;_WIN32_WINNT=0x601;NOMINMAX;WIN32_LEAN_AND_MEAN;_HAS_EXCEPTIONS=0;PSAPI_VERSION=1;CEF_USE_ATL;_NDEBUG;CMAKE_INTDIR="Release";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <DisableSpecificWarnings>4100;4127;4244;4324;4481;4512;4701;4702;4996</DisableSpecificWarnings>
+ <LanguageStandard>stdcpp14</LanguageStandard>
+ </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>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <StackReserveSize>0x8000</StackReserveSize>
+ <LargeAddressAware>true</LargeAddressAware>
+ <AdditionalManifestDependencies>%(AdditionalManifestDependencies)</AdditionalManifestDependencies>
+ <UACUIAccess>true</UACUIAccess>
+ </Link>
+ <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>
+ <AdditionalManifestFiles>ml_fanzone.dll.manifest;compatibility.manifest</AdditionalManifestFiles>
+ </Manifest>
+ <ManifestResourceCompile>
+ <ResourceOutputFileName>
+ </ResourceOutputFileName>
+ </ManifestResourceCompile>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_FANZONE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <StackReserveSize>0x8000</StackReserveSize>
+ <LargeAddressAware>true</LargeAddressAware>
+ </Link>
+ <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="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="view.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="api__ml_fanzone.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_fanzone.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\cefsimple.ico" />
+ <Image Include="resources\fanzone_16x16.bmp" />
+ <Image Include="resources\small.ico" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\WAT\WAT.vcxproj">
+ <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="CMakeLists.txt" />
+ </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_fanzone/ml_fanzone.vcxproj.filters b/Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj.filters
new file mode 100644
index 00000000..0134715e
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/ml_fanzone.vcxproj.filters
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="view.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_fanzone.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{b09f9553-f445-44f8-9a72-cb758d5286cf}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{be47cb42-3d48-4f47-83c8-6b797a58b3b5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{e2ca50af-70fe-46f4-86cf-1ca4d35f7af3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{ba64025b-123f-434f-b5cd-9f578be68b35}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{a842431b-0ab9-48d1-afdd-2492128c0577}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{88f1ad0f-1bde-4677-b4ef-149786474b02}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{fc3b229a-9ed1-4937-bc2d-726130e69bbf}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{46374174-4599-4ca0-b088-999738b14566}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_fanzone.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\fanzone_16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\small.ico">
+ <Filter>Ressource Files</Filter>
+ </Image>
+ <Image Include="resources\cefsimple.ico">
+ <Filter>Ressource Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="CMakeLists.txt" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_fanzone/myCefApp.cpp b/Src/Plugins/Library/ml_fanzone/myCefApp.cpp
new file mode 100644
index 00000000..a93eb43d
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/myCefApp.cpp
@@ -0,0 +1,49 @@
+//#include "main.h"
+//
+//BOOL MyDialogExProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
+//{
+// static CefRefPtr<MyCefClient> cef_client;
+//
+// switch ( msg )
+// {
+// case WM_INITDIALOG:
+// {
+// CefMainArgs main_args( GetModuleHandle( nullptr ) );
+// CefSettings settings;
+// CefRefPtr<CefApp> app;
+// CefInitialize( main_args, settings, app, nullptr );
+//
+// HWND cef_container_handle = GetDlgItem( hwnd, IDC_CEF_CONTAINER );
+//
+// RECT rect;
+// GetClientRect( cef_container_handle, &rect );
+// CefRect cef_rect( rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top );
+//
+// CefWindowInfo window_info;
+// window_info.SetAsChild( cef_container_handle, cef_rect );
+//
+// cef_client = new MyCefClient( cef_container_handle );
+//
+// // Create the browser window in the container control.
+// CefBrowserSettings browser_settings;
+// CefRefPtr<CefRequestContext> request_context;
+// CefRefPtr<CefDictionaryValue> extra_info;
+//
+// CefRefPtr<CefBrowser> browser = CefBrowserHost::CreateBrowserSync( window_info, cef_client.get(), FANZONE_BASE_URL, browser_settings, extra_info, request_context );
+//
+//
+// return TRUE;
+// }
+//
+// case WM_DESTROY:
+// {
+// CefShutdown();
+// break;
+// }
+//
+// default:
+// break;
+// }
+//
+// return FALSE;
+//}
diff --git a/Src/Plugins/Library/ml_fanzone/resource.h b/Src/Plugins/Library/ml_fanzone/resource.h
new file mode 100644
index 00000000..d9b2da8d
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/resource.h
@@ -0,0 +1,23 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_fanzone.rc
+//
+#define IDS_FANZONE 1
+#define IDI_CEFSIMPLE 100
+#define IDD_VIEW_FANZONE 102
+#define IDI_SMALL 103
+#define IDC_LIST_NFT 1001
+#define IDC_LIST_FANZONE 1001
+#define IDB_TREEITEM_FANZONE 1003
+#define IDS_NULLSOFT_FANZONE 65534
+
+// 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 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_fanzone/resources/FANZONE_16x16.bmp b/Src/Plugins/Library/ml_fanzone/resources/FANZONE_16x16.bmp
new file mode 100644
index 00000000..04905290
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/resources/FANZONE_16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_fanzone/resources/cefsimple.ico b/Src/Plugins/Library/ml_fanzone/resources/cefsimple.ico
new file mode 100644
index 00000000..d551aa3a
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/resources/cefsimple.ico
Binary files differ
diff --git a/Src/Plugins/Library/ml_fanzone/resources/small.ico b/Src/Plugins/Library/ml_fanzone/resources/small.ico
new file mode 100644
index 00000000..d551aa3a
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/resources/small.ico
Binary files differ
diff --git a/Src/Plugins/Library/ml_fanzone/version.rc2 b/Src/Plugins/Library/ml_fanzone/version.rc2
new file mode 100644
index 00000000..b0271335
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 0,1,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", "0,1,0,0"
+ VALUE "InternalName", "Nullsoft Fanzone Library"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_fanzone.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_fanzone/view.cpp b/Src/Plugins/Library/ml_fanzone/view.cpp
new file mode 100644
index 00000000..b223c2df
--- /dev/null
+++ b/Src/Plugins/Library/ml_fanzone/view.cpp
@@ -0,0 +1,444 @@
+#include <strsafe.h>
+#include "main.h"
+#include <shellapi.h>
+
+#include "../nu/DialogSkinner.h"
+#include "../nu/ListView.h"
+
+#include "../../General/gen_ml/ml_ipc.h"
+#include "../../General/gen_ml/menu.h"
+
+#include "../WAT/WAT.h"
+
+
+#ifdef _DEBUG
+constexpr auto FANZONE_BASE_URL = L"https://player-stg.winamp.com/fanzone/music?mtm_campaign=legendary_player";
+#else
+constexpr auto FANZONE_BASE_URL = L"https://player.winamp.com/fanzone/music?mtm_campaign=legendary_player";
+#endif // _DEBUG
+
+INT_PTR CALLBACK view_FANZONEDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static W_ListView m_fanzone_node;
+static HWND m_headerhwnd;
+extern C_Config *g_config;
+extern char *g_ext_list;
+static int customAllowed;
+static viewButtons view;
+
+C_Config *g_wa_config = 0;
+char *g_ext_list = 0;
+
+// used for the send-to menu bits
+static INT_PTR IPC_LIBRARY_SENDTOMENU;
+static librarySendToMenuStruct s;
+BOOL myMenu = FALSE;
+
+extern HMENU g_context_menus, g_context_menus2;
+extern HCURSOR hDragNDropCursor;
+
+static int FANZONE_contextMenu( INT_PTR param1, HWND hHost, POINTS pts )
+{
+ return TRUE;
+}
+
+static int pluginHandleIpcMessage(int msg, int param)
+{
+ return (int)SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, param, msg );
+}
+
+INT_PTR fanzone_pluginMessageProc( int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3 )
+{
+ if ( message_type == ML_MSG_NO_CONFIG )
+ {
+ return TRUE;
+ }
+ else if ( message_type == ML_MSG_TREE_ONCREATEVIEW && param1 == fanzone_treeItem )
+ {
+ return (INT_PTR)WASABI_API_CREATEDIALOGW( IDD_VIEW_FANZONE, (HWND)param2, view_FANZONEDialogProc );
+ }
+ else if ( message_type == ML_MSG_NAVIGATION_CONTEXTMENU )
+ {
+ return FANZONE_contextMenu( param1, (HWND)param2, MAKEPOINTS( param3 ) );
+ }
+ else if ( message_type == ML_MSG_ONSENDTOSELECT || message_type == ML_MSG_TREE_ONDROPTARGET )
+ {
+ // set with droptarget defaults =)
+ UINT_PTR type = 0, data = 0;
+
+ if ( message_type == ML_MSG_ONSENDTOSELECT )
+ {
+ if ( param3 != (INT_PTR)fanzone_pluginMessageProc ) return 0;
+
+ type = (int)param1;
+ data = (int)param2;
+ }
+ else
+ {
+ if ( param1 != fanzone_treeItem ) return 0;
+
+ type = (int)param2;
+ data = (int)param3;
+
+ if ( !data )
+ {
+ return ( type == ML_TYPE_ITEMRECORDLISTW || type == ML_TYPE_ITEMRECORDLIST ||
+ type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES ||
+ type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW ||
+ type == ML_TYPE_CDTRACKS ||
+ type == ML_TYPE_PLAYLIST || type == ML_TYPE_PLAYLISTS ) ? 1 : -1;
+ }
+ }
+ }
+ else if ( message_type == WM_WA_IPC )
+ {
+
+ int l_toto = 0;
+ }
+ else if ( message_type == IPC_SETVOLUME )
+ {
+ int curvol = IPC_GETVOLUME( plugin.hwndWinampParent );
+
+
+ int l_toto = 0;
+ }
+ else if ( message_type == WINAMP_VOLUMEDOWN )
+ {
+
+
+ int l_toto = 0;
+ }
+ else if ( message_type == WINAMP_VOLUMEUP )
+ {
+
+
+ int l_toto = 0;
+ }
+
+
+ return 0;
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, GROUP_MAIN, IDC_LIST_FANZONE
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[ sizeof( controls ) / sizeof( controls[ 0 ] ) ], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if ( rc.right == rc.left || rc.bottom == rc.top )
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for ( index = 0; index < sizeof( controls ) / sizeof( *controls ); index++ )
+ {
+ if ( controls[ index ] >= GROUP_MIN && controls[ index ] <= GROUP_MAX ) // group id
+ {
+ skipgroup = FALSE;
+ switch ( controls[ index ] )
+ {
+ case GROUP_MAIN:
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ), rc.top, rc.right, rc.bottom );
+ break;
+ }
+ continue;
+ }
+
+ if (skipgroup)
+ continue;
+
+ pl->id = controls[ index ];
+ pl->hwnd = GetDlgItem( hwnd, pl->id );
+ if ( !pl->hwnd )
+ continue;
+
+ GetWindowRect( pl->hwnd, &ri );
+ MapWindowPoints( HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2 );
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_LIST_FANZONE:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1),
+ rg.right - rg.left + WASABI_API_APP->getScaleY(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+
+ SETLAYOUTFLAGS( pl, ri );
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos( (INT)( pl - layout ) );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ hdwp = DeferWindowPos( hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags );
+ }
+
+ if ( hdwp )
+ EndDeferWindowPos( hdwp );
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ if ( fRedraw )
+ {
+ GetUpdateRgn( hwnd, rgn, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow( hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN );
+ }
+
+ if ( g_rgnUpdate )
+ {
+ GetUpdateRgn( hwnd, g_rgnUpdate, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR );
+ }
+ }
+ }
+
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ if ( pc->rgn )
+ DeleteObject( pc->rgn );
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn( hwnd, NULL );
+}
+
+static BOOL FANZONE_OnDisplayChange()
+{
+ ListView_SetTextColor( m_fanzone_node.getwnd(), dialogSkinner.Color( WADLG_ITEMFG ) );
+ ListView_SetBkColor( m_fanzone_node.getwnd(), dialogSkinner.Color( WADLG_ITEMBG ) );
+ ListView_SetTextBkColor( m_fanzone_node.getwnd(), dialogSkinner.Color( WADLG_ITEMBG ) );
+
+ m_fanzone_node.SetFont( dialogSkinner.GetFont() );
+
+ LayoutWindows( m_hwnd, TRUE );
+
+ // Display the modal windows
+ ShowWindow( m_fanzone_node.getwnd(), SW_SHOW );
+ UpdateWindow( m_fanzone_node.getwnd() );
+
+ return 0;
+}
+
+
+enum
+{
+ BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+static BOOL FANZONE_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify )
+{
+ return FALSE;
+}
+
+static BOOL FANZONE_OnDestroy(HWND hwnd)
+{
+ m_hwnd = 0;
+
+ return FALSE;
+}
+
+static BOOL FANZONE_OnNotify(HWND hwnd, NMHDR *notification)
+{
+ if (notification->idFrom == IDC_LIST_FANZONE)
+ {
+ if (notification->code == LVN_BEGINDRAG)
+ {
+ SetCapture(hwnd);
+ }
+ }
+ return FALSE;
+}
+
+static BOOL FANZONE_OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
+{
+ m_hwnd = hwndDlg;
+
+ m_fanzone_node.setwnd(GetDlgItem(hwndDlg, IDC_LIST_FANZONE));
+
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+
+ FANZONE_OnDisplayChange();
+
+ m_headerhwnd = ListView_GetHeader( m_fanzone_node.getwnd() );
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ customAllowed = FALSE;
+
+ MLSKINWINDOW l_ml_skin_window = {0};
+ l_ml_skin_window.skinType = SKINNEDWND_TYPE_DIALOG;
+ l_ml_skin_window.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ l_ml_skin_window.hwndToSkin = m_hwnd;
+ MLSkinWindow( plugin.hwndLibraryParent, &l_ml_skin_window );
+
+ //l_ml_skin_window.skinType = SKINNEDWND_TYPE_STATIC;
+ //l_ml_skin_window.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ //l_ml_skin_window.hwndToSkin = m_hwnd;
+ //MLSkinWindow( mediaLibrary.library, &l_ml_skin_window );
+
+ ShellExecuteW(NULL, L"open", FANZONE_BASE_URL, NULL, NULL, SW_SHOWNORMAL);
+
+ delete g_wa_config;
+
+ SetTimer( m_hwnd, 100, 15, NULL );
+
+
+ return TRUE;
+}
+
+
+
+INT_PTR CALLBACK view_FANZONEDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam);
+ if (a) return a;
+
+ switch(uMsg)
+ {
+ HANDLE_MSG(hwndDlg, WM_INITDIALOG, FANZONE_OnInitDialog);
+ HANDLE_MSG(hwndDlg, WM_COMMAND, FANZONE_OnCommand);
+ HANDLE_MSG(hwndDlg, WM_DESTROY, FANZONE_OnDestroy);
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST_FANZONE | DCW_SUNKENBORDER };
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ myMenu = TRUE;
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ myMenu = FALSE;
+ }
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ return FANZONE_OnDisplayChange();
+
+ case WM_NOTIFY:
+ return FANZONE_OnNotify(hwndDlg, (LPNMHDR)lParam);
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/HistoryAPI.cpp b/Src/Plugins/Library/ml_history/HistoryAPI.cpp
new file mode 100644
index 00000000..58301aef
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPI.cpp
@@ -0,0 +1,93 @@
+#include "HistoryAPI.h"
+#include "ml_history.h"
+
+static void saveQueryToList(nde_scanner_t s, historyRecordList *obj)
+{
+ emptyRecentRecordList(obj);
+
+ NDE_Scanner_First(s);
+
+ int r;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_FILENAME);
+ if (f)
+ {
+ allocRecentRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ wchar_t *strval = NDE_StringField_GetString(f);
+ ndestring_retain(strval);
+ obj->Items[obj->Size].filename = strval;
+ recentScannerRefToObjCacheNFN(s, obj);
+ }
+
+ r = NDE_Scanner_Next(s);
+ }
+ while (r && !NDE_Scanner_EOF(s));
+
+ if (obj->Size && obj->Size < obj->Alloc - 1024)
+ {
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc = obj->Size;
+ historyRecord *data = (historyRecord*)realloc(obj->Items, sizeof(historyRecord) * obj->Alloc);
+ if (data)
+ {
+ obj->Items=data;
+ }
+ else
+ {
+ data=(historyRecord*)malloc(sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(historyRecord)*old_Alloc);
+ free(obj->Items);
+ obj->Items=data;
+ }
+ else obj->Alloc = (int)old_Alloc;
+ }
+ }
+}
+
+historyRecordList *HistoryAPI::Query(const wchar_t *query)
+{
+ if (!openDb())
+ return 0;
+
+ // run query
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query);
+
+ historyRecordList obj;
+
+ obj.Alloc = 0;
+ obj.Items = NULL;
+ obj.Size = 0;
+ saveQueryToList(s, &obj);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ if (obj.Size)
+ {
+ historyRecordList *result = (historyRecordList *)malloc(sizeof(historyRecordList));
+ memcpy(result, &obj, sizeof(historyRecordList));
+ return result;
+ }
+ else
+ {
+ freeRecentRecordList(&obj);
+ return 0;
+ }
+}
+
+void HistoryAPI::FreeHistoryList(historyRecordList *historyList)
+{
+ freeRecentRecordList(historyList);
+}
+
+#define CBCLASS HistoryAPI
+START_DISPATCH;
+CB(API_HISTORY_QUERY, Query)
+VCB(API_HISTORY_FREEHISTORYLIST, FreeHistoryList)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_history/HistoryAPI.h b/Src/Plugins/Library/ml_history/HistoryAPI.h
new file mode 100644
index 00000000..cd5dfc8d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPI.h
@@ -0,0 +1,12 @@
+#pragma once
+#include "api_history.h"
+
+class HistoryAPI : public api_history
+{
+public:
+ historyRecordList *Query(const wchar_t *query);
+ void FreeHistoryList(historyRecordList *historyList);
+
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp b/Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp
new file mode 100644
index 00000000..a938f6c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp
@@ -0,0 +1,62 @@
+#include "HistoryAPIFactory.h"
+#include "api__ml_history.h"
+#include "HistoryAPI.h"
+
+HistoryAPI historyAPI;
+static const char serviceName[] = "History API";
+
+FOURCC HistoryAPIFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *HistoryAPIFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID HistoryAPIFactory::GetGUID()
+{
+ return HistoryApiGuid;
+}
+
+void *HistoryAPIFactory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &historyAPI;
+}
+
+int HistoryAPIFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int HistoryAPIFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *HistoryAPIFactory::GetTestString()
+{
+ return 0;
+}
+
+int HistoryAPIFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS HistoryAPIFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/HistoryAPIFactory.h b/Src/Plugins/Library/ml_history/HistoryAPIFactory.h
new file mode 100644
index 00000000..a07e063f
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPIFactory.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "api__ml_history.h"
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class HistoryAPIFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp b/Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp
new file mode 100644
index 00000000..d3619745
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp
@@ -0,0 +1,95 @@
+#include "api__ml_history.h"
+#include "JSAPI2_Creator.h"
+#include "JSAPI2_HistoryAPI.h"
+#include "resource.h"
+
+
+IDispatch *JSAPI2_Creator::CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info)
+{
+ if (!_wcsicmp(name, L"History"))
+ return new JSAPI2::HistoryAPI(key, info);
+ else
+ return 0;
+}
+
+int JSAPI2_Creator::PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data)
+{
+ if (group && !_wcsicmp(group, L"history"))
+ {
+ const wchar_t *title_str = AGAVE_API_JSAPI2_SECURITY->GetAssociatedName(authorization_key);
+ return AGAVE_API_JSAPI2_SECURITY->SecurityPrompt(parent, title_str, L"This service is requesting access to your playback history.", JSAPI2::svc_apicreator::AUTHORIZATION_FLAG_GROUP_ONLY);
+ }
+ else
+ return JSAPI2::svc_apicreator::AUTHORIZATION_UNDEFINED;
+}
+
+#define CBCLASS JSAPI2_Creator
+START_DISPATCH;
+CB(JSAPI2_SVC_APICREATOR_CREATEAPI, CreateAPI);
+CB(JSAPI2_SVC_APICREATOR_PROMPTFORAUTHORIZATION, PromptForAuthorization);
+END_DISPATCH;
+#undef CBCLASS
+
+static JSAPI2_Creator jsapi2_svc;
+static const char serviceName[] = "History Javascript Objects";
+
+// {D9F59F89-82F3-446d-8CD8-6D4445094D50}
+static const GUID jsapi2_factory_guid =
+{ 0xd9f59f89, 0x82f3, 0x446d, { 0x8c, 0xd8, 0x6d, 0x44, 0x45, 0x9, 0x4d, 0x50 } };
+
+
+FOURCC JSAPI2Factory::GetServiceType()
+{
+ return jsapi2_svc.getServiceType();
+}
+
+const char *JSAPI2Factory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID JSAPI2Factory::GetGUID()
+{
+ return jsapi2_factory_guid;
+}
+
+void *JSAPI2Factory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &jsapi2_svc;
+}
+
+int JSAPI2Factory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int JSAPI2Factory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *JSAPI2Factory::GetTestString()
+{
+ return 0;
+}
+
+int JSAPI2Factory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS JSAPI2Factory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_Creator.h b/Src/Plugins/Library/ml_history/JSAPI2_Creator.h
new file mode 100644
index 00000000..ddcc9aee
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_Creator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class JSAPI2Factory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+class JSAPI2_Creator : public JSAPI2::svc_apicreator
+{
+ IDispatch *CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info);
+ int PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data);
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp
new file mode 100644
index 00000000..32bea983
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp
@@ -0,0 +1,123 @@
+#include "api__ml_history.h"
+#include "JSAPI2_HistoryAPI.h"
+#include "../Winamp/JSAPI.h"
+#include "JSAPI2_HistoryRecordList.h"
+#include "HistoryAPI.h"
+
+extern HistoryAPI historyAPI;
+
+JSAPI2::HistoryAPI::HistoryAPI(const wchar_t *_key, JSAPI::ifc_info *_info)
+{
+ info = _info;
+ key = _key;
+ refCount = 1;
+}
+
+#define DISP_TABLE \
+ CHECK_ID(Query)\
+
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+};
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = JSAPI_DISP_ENUMIFY(str); continue; }
+HRESULT JSAPI2::HistoryAPI::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ DISP_TABLE
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryAPI::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryAPI::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryAPI::Query(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_BSTR, puArgErr);
+ JSAPI_INIT_RESULT(pvarResult, VT_DISPATCH);
+
+ if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"history", L"query", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ if (pvarResult) // no sense in running the query if they don't care about the return value!
+ {
+ historyRecordList *query_results = historyAPI.Query(JSAPI_PARAM(pdispparams, 1).bstrVal);
+ if (query_results)
+ V_DISPATCH(pvarResult) = new JSAPI2::HistoryRecordList(query_results);
+ else
+ V_DISPATCH(pvarResult) = 0;
+ }
+ return S_OK;
+ }
+ else
+ {
+ V_DISPATCH(pvarResult) = 0;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdispparams, pvarResult, puArgErr);
+HRESULT JSAPI2::HistoryAPI::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ DISP_TABLE
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JSAPI2::HistoryAPI::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::HistoryAPI::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+
+ULONG JSAPI2::HistoryAPI::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h
new file mode 100644
index 00000000..a880ef4d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <ocidl.h>
+#include "../Winamp/JSAPI_Info.h"
+
+namespace JSAPI2
+{
+ class HistoryAPI : public IDispatch
+ {
+ public:
+ HistoryAPI(const wchar_t *_key, JSAPI::ifc_info *info);
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+ private:
+ const wchar_t *key;
+ volatile LONG refCount;
+ JSAPI::ifc_info *info;
+
+ STDMETHOD (Query)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ };
+}
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp
new file mode 100644
index 00000000..6b9a9ac6
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp
@@ -0,0 +1,260 @@
+#include "JSAPI2_HistoryRecord.h"
+#include "../Winamp/JSAPI.h"
+
+#define DISP_TABLE \
+ CHECK_ID(filename)\
+ CHECK_ID(title)\
+ CHECK_ID(length)\
+ CHECK_ID(playcount)\
+ CHECK_ID(lastplay)\
+ CHECK_ID(offset)\
+
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+ DISP_TABLE_NUM_ENTRIES,
+};
+
+JSAPI2::HistoryRecord::HistoryRecord(IUnknown *_parent, const historyRecord *_record)
+{
+ parent = _parent;
+ parent->AddRef();
+ record = _record;
+ refCount = 1;
+}
+
+JSAPI2::HistoryRecord::~HistoryRecord()
+{
+ if (parent)
+ parent->Release();
+}
+
+HRESULT JSAPI2::HistoryRecord::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ if (GetDispID(rgszNames[i], fdexNameCaseSensitive, &rgdispid[i]) == DISPID_UNKNOWN)
+ unknowns=true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryRecord::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ return InvokeEx(dispid, lcid, wFlags, pdispparams, pvarResult, pexecinfo, 0);
+}
+
+HRESULT JSAPI2::HistoryRecord::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP JSAPI2::HistoryRecord::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_IDispatchEx))
+ *ppvObject = (IDispatchEx *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::HistoryRecord::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+ULONG JSAPI2::HistoryRecord::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(bstrName, L## #str) == 0) { *pid = JSAPI_DISP_ENUMIFY(str); return S_OK; }
+HRESULT JSAPI2::HistoryRecord::GetDispID(BSTR bstrName, DWORD grfdex, DISPID *pid)
+{
+ DISP_TABLE
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdp, pvarRes);
+HRESULT JSAPI2::HistoryRecord::InvokeEx(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ switch(id)
+ {
+ DISP_TABLE
+ }
+ }
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::DeleteMemberByName(BSTR bstrName, DWORD grfdex)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::DeleteMemberByDispID(DISPID id)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::GetMemberProperties(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
+{
+ return E_NOTIMPL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): *pbstrName = SysAllocString(L ## #str); return S_OK;
+HRESULT JSAPI2::HistoryRecord::GetMemberName(DISPID id, BSTR *pbstrName)
+{
+ switch(id)
+ {
+ DISP_TABLE
+ }
+
+ return E_NOTIMPL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): *pid = JSAPI_DISP_ENUMIFY(str) + 1; break;
+HRESULT JSAPI2::HistoryRecord::GetNextDispID(DWORD grfdex, DISPID id, DISPID *pid)
+{
+ if (grfdex == fdexEnumDefault)
+ {
+ switch(id)
+ {
+ case DISPID_UNKNOWN:
+ *pid = 0;
+ break;
+ DISP_TABLE
+
+ }
+ if (*pid == DISP_TABLE_NUM_ENTRIES)
+ return S_FALSE;
+ else
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::GetNameSpaceParent(IUnknown **ppunk)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::filename(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(record->filename));
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+
+HRESULT JSAPI2::HistoryRecord::title(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(record->title));
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::length(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, record->length);
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::offset(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, record->offset);
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::playcount(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, record->playcnt);
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::lastplay(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_DATE);
+ if (pvarResult)
+ {
+ __time64_t convertTime = record->lastplayed;
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ SystemTimeToVariantTime(&sysTime, &pvarResult->date);
+ }
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h
new file mode 100644
index 00000000..065b4edd
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h
@@ -0,0 +1,46 @@
+#pragma once
+#include <dispex.h>
+#include "history.h"
+namespace JSAPI2
+{
+ class HistoryRecord : public IDispatchEx
+ {
+ public:
+ HistoryRecord(IUnknown *_parent, const historyRecord *_record);
+ ~HistoryRecord();
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ void AddObject(IDispatch *obj);
+ private:
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ // *** IDispatchEx Methods ***
+ STDMETHOD (GetDispID)(BSTR bstrName, DWORD grfdex, DISPID *pid);
+ STDMETHOD (InvokeEx)(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller);
+ STDMETHOD (DeleteMemberByName)(BSTR bstrName, DWORD grfdex) ;
+ STDMETHOD (DeleteMemberByDispID)(DISPID id);
+ STDMETHOD (GetMemberProperties)(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex);
+ STDMETHOD (GetMemberName)(DISPID id, BSTR *pbstrName);
+ STDMETHOD (GetNextDispID)(DWORD grfdex, DISPID id, DISPID *pid);
+ STDMETHOD (GetNameSpaceParent)(IUnknown **ppunk);
+ private:
+ const historyRecord *record;
+ IUnknown *parent;
+ volatile LONG refCount;
+
+ STDMETHOD (filename)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (title)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (length)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (playcount)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (lastplay)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (offset)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+
+ };
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp
new file mode 100644
index 00000000..ee949c48
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp
@@ -0,0 +1,198 @@
+#include "JSAPI2_HistoryRecordList.h"
+#include "JSAPI2_HistoryRecord.h"
+#include "ml_history.h"
+#include "../Winamp/JSAPI.h"
+#include <strsafe.h>
+
+JSAPI2::HistoryRecordList::HistoryRecordList(historyRecordList *_record)
+{
+ recordList = _record;
+ refCount = 1;
+}
+
+JSAPI2::HistoryRecordList::~HistoryRecordList()
+{
+ freeRecentRecordList(recordList);
+}
+
+enum
+{
+ OBJ_ARRAY_DISP_LENGTH,
+ OBJ_ARRAY_NUM_DISP,
+};
+
+HRESULT JSAPI2::HistoryRecordList::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ if (GetDispID(rgszNames[i], fdexNameCaseSensitive, &rgdispid[i]) == DISPID_UNKNOWN)
+ unknowns=true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryRecordList::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ return InvokeEx(dispid, lcid, wFlags, pdispparams, pvarResult, pexecinfo, 0);
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP JSAPI2::HistoryRecordList::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_IDispatchEx))
+ *ppvObject = (IDispatchEx *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::HistoryRecordList::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+
+ULONG JSAPI2::HistoryRecordList::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetDispID(BSTR bstrName, DWORD grfdex, DISPID *pid)
+{
+ if (!_wcsicmp(bstrName, L"length"))
+ *pid=OBJ_ARRAY_DISP_LENGTH;
+ else
+ {
+ if (bstrName[0] >= L'0' && bstrName[0] <= L'9')
+ *pid=_wtoi(bstrName) + OBJ_ARRAY_NUM_DISP;
+ else
+ return DISP_E_MEMBERNOTFOUND;
+
+ }
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryRecordList::InvokeEx(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
+{
+ JSAPI_VERIFY_PARAMCOUNT(pdp, 0);
+
+ switch(id)
+ {
+ case OBJ_ARRAY_DISP_LENGTH:
+ {
+ JSAPI_INIT_RESULT(pvarRes, VT_I4);
+ JSAPI_SET_RESULT(pvarRes, lVal, recordList->Size);
+ return S_OK;
+ }
+ default:
+ {
+ if (id < OBJ_ARRAY_NUM_DISP)
+ return DISP_E_MEMBERNOTFOUND;
+
+ int index = id - OBJ_ARRAY_NUM_DISP;
+ if (index>=recordList->Size)
+ return DISP_E_MEMBERNOTFOUND;
+
+ JSAPI_INIT_RESULT(pvarRes, VT_DISPATCH);
+ JSAPI_SET_RESULT(pvarRes, pdispVal, new JSAPI2::HistoryRecord(this, &recordList->Items[index]));
+ return S_OK;
+ }
+ }
+
+}
+
+HRESULT JSAPI2::HistoryRecordList::DeleteMemberByName(BSTR bstrName, DWORD grfdex)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::DeleteMemberByDispID(DISPID id)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetMemberProperties(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetMemberName(DISPID id, BSTR *pbstrName)
+{
+ if (id >= OBJ_ARRAY_NUM_DISP)
+ {
+ wchar_t temp[64];
+ StringCbPrintfW(temp, sizeof(temp), L"%d", id-OBJ_ARRAY_NUM_DISP);
+ *pbstrName = SysAllocString(temp);
+ return S_OK;
+ }
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetNextDispID(DWORD grfdex, DISPID id, DISPID *pid)
+{
+ if (grfdex == fdexEnumDefault)
+ {
+ if (id == DISPID_UNKNOWN)
+ {
+ if (recordList->Size == 0)
+ return S_FALSE;
+ else
+ {
+ *pid = OBJ_ARRAY_NUM_DISP;
+ return S_OK;
+ }
+ }
+ else if (id < OBJ_ARRAY_NUM_DISP)
+ {
+ return S_FALSE;
+ }
+ else
+ {
+ int index = (id - OBJ_ARRAY_NUM_DISP) + 1;
+ if (index >= recordList->Size)
+ {
+ return S_FALSE;
+ }
+ else
+ {
+ *pid = OBJ_ARRAY_NUM_DISP + index;
+ return S_OK;
+ }
+
+ }
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetNameSpaceParent(IUnknown **ppunk)
+{
+ return E_NOTIMPL;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h
new file mode 100644
index 00000000..961614ab
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <dispex.h>
+#include "history.h"
+namespace JSAPI2
+{
+ class HistoryRecordList : public IDispatchEx
+ {
+ public:
+ HistoryRecordList(historyRecordList *_record);
+ ~HistoryRecordList();
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ void AddObject(IDispatch *obj);
+ private:
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ // *** IDispatchEx Methods ***
+ STDMETHOD (GetDispID)(BSTR bstrName, DWORD grfdex, DISPID *pid);
+ STDMETHOD (InvokeEx)(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller);
+ STDMETHOD (DeleteMemberByName)(BSTR bstrName, DWORD grfdex) ;
+ STDMETHOD (DeleteMemberByDispID)(DISPID id);
+ STDMETHOD (GetMemberProperties)(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex);
+ STDMETHOD (GetMemberName)(DISPID id, BSTR *pbstrName);
+ STDMETHOD (GetNextDispID)(DWORD grfdex, DISPID id, DISPID *pid);
+ STDMETHOD (GetNameSpaceParent)(IUnknown **ppunk);
+ private:
+ historyRecordList *recordList;
+ volatile LONG refCount;
+ };
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/Main.cpp b/Src/Plugins/Library/ml_history/Main.cpp
new file mode 100644
index 00000000..7f8391d4
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/Main.cpp
@@ -0,0 +1,349 @@
+#include "api__ml_history.h"
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "HistoryAPIFactory.h"
+#include "JSAPI2_Creator.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../nu/AutoCharFn.h"
+#include <strsafe.h>
+
+#define LOCAL_WRITE_VER L"2.03"
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = 0;
+api_application *WASABI_API_APP=0;
+
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+static HistoryAPIFactory historyAPIFactory;
+static JSAPI2Factory jsapi2Factory;
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+prefsDlgRecW preferences = {0};
+wchar_t g_tableDir[MAX_PATH] = {0};
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_history.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+// Delay load library control << begin >>
+#include <delayimp.h>
+#pragma comment(lib, "delayimp")
+
+bool nde_error = false;
+
+FARPROC WINAPI FailHook(unsigned dliNotify, PDelayLoadInfo pdli)
+{
+ nde_error = true;
+ return 0;
+}
+/*
+extern "C"
+{
+ PfnDliHook __pfnDliFailureHook2 = FailHook;
+}
+*/
+// Delay load library control << end >>
+
+int Init()
+{
+ InitializeCriticalSection(&g_db_cs);
+
+ g_db = NULL;
+ g_table = NULL;
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ wchar_t configName[ MAX_PATH ] = { 0 };
+ const wchar_t *dir = mediaLibrary.GetIniDirectoryW();
+
+ if ( (INT_PTR)( dir ) < 65536 )
+ return ML_INIT_FAILURE;
+
+
+ PathCombineW( g_tableDir, dir, L"Plugins" );
+ PathCombineW( configName, g_tableDir, L"gen_ml.ini" );
+
+ g_config = new C_Config( configName );
+
+ CreateDirectoryW( g_tableDir, NULL );
+ PathCombineW( g_tableDir, g_tableDir, L"ml" );
+ CreateDirectoryW( g_tableDir, NULL );
+
+ // loader so that we can get the localisation service api for use
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( JSAPI2::api_securityGUID );
+ if ( sf )
+ AGAVE_API_JSAPI2_SECURITY = reinterpret_cast<JSAPI2::api_security *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( ExplorerFindFileApiGUID );
+ if ( sf )
+ WASABI_API_EXPLORERFINDFILE = reinterpret_cast<api_explorerfindfile *>( sf->getInterface() );
+
+ plugin.service->service_register( &historyAPIFactory );
+ plugin.service->service_register( &jsapi2Factory );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlHistoryLangGUID );
+
+ g_context_menus2 = WASABI_API_LOADMENUW(IDR_CONTEXTMENUS);
+
+ static wchar_t szDescription[256];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_HISTORY ), LOCAL_WRITE_VER );
+
+
+ plugin.description = (char*)szDescription;
+
+ static wchar_t preferencesName[64] = {0};
+ preferences.hInst = WASABI_API_LNG_HINST;
+ preferences.dlgID = IDD_PREFS;
+ preferences.proc = (void *)PrefsProc;
+ preferences.name = WASABI_API_LNGSTRINGW_BUF( IDS_HISTORY, preferencesName, 64 );
+ preferences.where = 6; // media library
+
+ SENDWAIPC( plugin.hwndWinampParent, IPC_ADD_PREFS_DLGW, &preferences );
+
+ if ( !history_init() )
+ return ML_INIT_FAILURE;
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ plugin.service->service_deregister(&historyAPIFactory);
+ plugin.service->service_deregister(&jsapi2Factory);
+ history_quit();
+ delete g_config;
+ DeleteCriticalSection(&g_db_cs);
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(languageApiGUID);
+ if (sf) sf->releaseInterface(WASABI_API_LNG);
+
+ sf = plugin.service->service_getServiceByGuid(JSAPI2::api_securityGUID);
+ if (sf) sf->releaseInterface(AGAVE_API_JSAPI2_SECURITY);
+}
+
+static INT_PTR History_OnContextMenu(INT_PTR param1, HWND hHost, POINTS pts)
+{
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, ml_history_tree);
+ if (hItem != myItem)
+ return FALSE;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+
+ HMENU hMenu = WASABI_API_LOADMENUW(IDR_CONTEXTMENUS);
+ HMENU subMenu = (NULL != hMenu) ? GetSubMenu(hMenu, 1) : NULL;
+ if (NULL != subMenu)
+ {
+
+ INT r = Menu_TrackPopup(plugin.hwndLibraryParent, subMenu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY |
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON,
+ pt.x, pt.y, hHost, NULL);
+
+ switch(r)
+ {
+ case ID_NAVIGATION_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &preferences);
+ break;
+ case ID_NAVIGATION_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8105304048660-The-Winamp-Media-Library");
+ break;
+ }
+ }
+
+ if (NULL != hMenu)
+ DestroyMenu(hMenu);
+
+ return TRUE;
+}
+
+void History_StartTracking(const wchar_t *filename, bool resume)
+{
+ KillTimer(plugin.hwndWinampParent, 8082);
+ if (!resume)
+ {
+ free(history_fn);
+ history_fn = 0;
+ history_fn_mode = 0;
+ if (wcsstr(filename, L"://") && _wcsnicmp(filename, L"cda://", 6) && _wcsnicmp(filename, L"file://", 7))
+ {
+ history_fn_mode = 1;
+ }
+ history_fn = _wcsdup(filename);
+ }
+
+ int timer1 = -1, timer2 = -1, timer3 = -1;
+
+ // wait for x seconds
+ if(g_config->ReadInt(L"recent_wait_secs",0))
+ {
+ timer1 = g_config->ReadInt(L"recent_wait_secs_lim",5)*1000;
+ }
+
+ // wait for x percent of the song (approx to a second)
+ if(g_config->ReadInt(L"recent_wait_percent",0))
+ {
+ basicFileInfoStructW bfiW = {0};
+ bfiW.filename = history_fn;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfiW, IPC_GET_BASIC_FILE_INFOW);
+ if(bfiW.length > 0)
+ {
+ bfiW.length=bfiW.length*1000;
+ timer2 = (bfiW.length*g_config->ReadInt(L"recent_wait_percent_lim",50))/100;
+ }
+ }
+
+ // wait for the end of the item (within the last second of the track hopefully)
+ if(g_config->ReadInt(L"recent_wait_end",0))
+ {
+ basicFileInfoStructW bfiW = {0};
+ bfiW.filename = history_fn;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfiW, IPC_GET_BASIC_FILE_INFOW);
+ if(bfiW.length > 0)
+ {
+ timer3=(bfiW.length-1)*1000;
+ }
+ }
+
+ // decide on which playback option will be the prefered duration (smallest wins)
+ if(timer1 != -1 && timer2 != -1)
+ {
+ if(timer1 > timer2)
+ {
+ timer = timer2;
+ }
+ if(timer2 > timer1)
+ {
+ timer = timer1;
+ }
+ }
+ else if(timer1 == -1 && timer2 != -1)
+ {
+ timer = timer2;
+ }
+ else if(timer2 == -1 && timer1 != -1)
+ {
+ timer = timer1;
+ }
+
+ // only track on end of file as very last method
+ if((timer <= 0) && (timer3 > 0)){ timer = timer3; }
+
+ // if no match or something went wrong then try to ensure the default timer value is used
+ SetTimer(plugin.hwndWinampParent, 8082, ((timer > 0)? timer : 350), NULL);
+}
+
+INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ switch (message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW: // param1 = param of tree item, param2 is HWND of parent. return HWND if it is us
+ return (param1 == ml_history_tree) ? (INT_PTR)onTreeViewSelectChange((HWND)param2) : 0;
+
+ case ML_MSG_CONFIG:
+ mediaLibrary.GoToPreferences((int)preferences._id);
+ return TRUE;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = (int)param1;
+ groupBtn = (int)param2;
+ PostMessage(m_curview_hwnd, WM_APP + 104, param1, param2);
+ return 0;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ return History_OnContextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+
+ case ML_MSG_PLAYING_FILE:
+ if (param1)
+ {
+ int resume = g_config->ReadInt(L"resumeplayback",0);
+ if(resume)
+ {
+ int is_playing = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_ISPLAYING);
+ //int play_pos = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETOUTPUTTIME);
+ if(is_playing == 1/* && !(play_pos/1000 > 0)*/) //playing, look up last play offset and send seek message
+ {
+ wchar_t genre[256]={0};
+ extendedFileInfoStructW efis={
+ (wchar_t*)param1,
+ L"genre",
+ genre,
+ ARRAYSIZE(genre),
+ };
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFOW);
+
+ wchar_t ispodcast[8]={0};
+ extendedFileInfoStructW efis1={
+ (wchar_t*)param1,
+ L"ispodcast",
+ ispodcast,
+ ARRAYSIZE(ispodcast),
+ };
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis1,IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE);
+
+ if (resume == 2 || (ispodcast[0] && _wtoi(ispodcast) > 0) || (genre[0] && !_wcsicmp(genre, L"podcast")))
+ {
+ int offset = retrieve_offset((wchar_t*)param1);
+ if (offset > 0 && (offset/1000 > 0)) PostMessage(plugin.hwndWinampParent,WM_WA_IPC,offset,IPC_JUMPTOTIME);
+ }
+ }
+ }
+
+ History_StartTracking((const wchar_t *)param1, false);
+ }
+ break;
+
+ case ML_MSG_WRITE_CONFIG:
+ if(param1)
+ {
+ closeDb();
+ openDb();
+ }
+ break;
+ }
+ return 0;
+}
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/Main.h b/Src/Plugins/Library/ml_history/Main.h
new file mode 100644
index 00000000..685b8ad1
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/Main.h
@@ -0,0 +1,34 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include <windows.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "resource.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "..\..\General\gen_ml/menu.h"
+#include <commctrl.h>
+#include <shlwapi.h>
+#include "ml_history.h"
+#include <windowsx.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "../nde/nde_c.h"
+#include "api__ml_history.h"
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+extern winampMediaLibraryPlugin plugin;
+extern bool nde_error;
+extern int history_fn_mode;
+extern wchar_t *history_fn;
+extern int timer;
+extern HWND m_curview_hwnd;
+extern int groupBtn, enqueuedef;
+extern HMENU g_context_menus2;
+
+void History_StartTracking(const wchar_t *filename, bool resume);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/api__ml_history.h b/Src/Plugins/Library/ml_history/api__ml_history.h
new file mode 100644
index 00000000..ae410bf1
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/api__ml_history.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_ML_HISTORY_API_H
+#define NULLSOFT_ML_HISTORY_API_H
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_security;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_security
+
+#include <api/service/waServiceFactory.h>
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#endif // !NULLSOFT_ML_HISTORY_API_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/api_history.cpp b/Src/Plugins/Library/ml_history/api_history.cpp
new file mode 100644
index 00000000..b13f171d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/api_history.cpp
@@ -0,0 +1 @@
+#include "api_history.h"
diff --git a/Src/Plugins/Library/ml_history/api_history.h b/Src/Plugins/Library/ml_history/api_history.h
new file mode 100644
index 00000000..3f987eb3
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/api_history.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include "history.h"
+
+class api_history : public Dispatchable
+{
+protected:
+ api_history() {}
+ ~api_history() {}
+public:
+ historyRecordList *Query(const wchar_t *query);
+ void FreeHistoryList(historyRecordList *historyList);
+
+ enum
+ {
+ API_HISTORY_QUERY = 0,
+ API_HISTORY_FREEHISTORYLIST = 1,
+ };
+};
+
+inline historyRecordList *api_history::Query(const wchar_t *query)
+{
+ return _call(API_HISTORY_QUERY, (historyRecordList *)0, query);
+}
+
+inline void api_history::FreeHistoryList(historyRecordList *historyList)
+{
+ _voidcall(API_HISTORY_FREEHISTORYLIST, historyList);
+}
+
+// {F9BF9119-D163-4118-BEA7-5980869DBB2E}
+static const GUID HistoryApiGuid =
+{ 0xf9bf9119, 0xd163, 0x4118, { 0xbe, 0xa7, 0x59, 0x80, 0x86, 0x9d, 0xbb, 0x2e } };
diff --git a/Src/Plugins/Library/ml_history/db_error.txt b/Src/Plugins/Library/ml_history/db_error.txt
new file mode 100644
index 00000000..0705b100
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/db_error.txt
@@ -0,0 +1,6 @@
+
+There was an error reading the history database. This could be due to the database becoming corrupted or having been removed.
+
+If the database files (recent.dat and recent.idx) have become corrupted then unless you have a back up, you will need to reset the database.
+
+By resetting the database, any playing history will be lost which also will have happened if the database was corrupted. \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/history.h b/Src/Plugins/Library/ml_history/history.h
new file mode 100644
index 00000000..6e1e8486
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/history.h
@@ -0,0 +1,35 @@
+#pragma once
+#ifndef NULLSOFT_ML_HISTORY_HISTORY_H
+#define NULLSOFT_ML_HISTORY_HISTORY_H
+
+#include <time.h>
+typedef struct
+{
+ wchar_t *filename;
+ wchar_t *title;
+ wchar_t *ext;
+ int length;
+ unsigned int playcnt;
+ __time64_t lastplayed;
+ int offset;
+} historyRecord;
+
+typedef struct
+{
+ historyRecord *Items;
+ int Size;
+ int Alloc;
+} historyRecordList;
+
+enum
+{
+ HISTORY_SORT_LASTPLAYED = 0,
+ HISTORY_SORT_PLAYCOUNT = 1,
+ HISTORY_SORT_TITLE = 2,
+ HISTORY_SORT_LENGTH = 3,
+ HISTORY_SORT_FILENAME = 4,
+ HISTORY_SORT_OFFSET = 5,
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/ml_history.cpp b/Src/Plugins/Library/ml_history/ml_history.cpp
new file mode 100644
index 00000000..24cb7aa1
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.cpp
@@ -0,0 +1,289 @@
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "..\..\General\gen_ml/MediaLibraryCOM.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <malloc.h>
+
+int ml_history_tree = -1;
+HWND m_curview_hwnd = NULL;
+static WNDPROC wa_oldWndProc=0;
+wchar_t *history_fn = 0;
+int timer = -1;
+static wchar_t *last_history_fn;
+static int last_timer=-1;
+static int last_play_pos=0;
+int history_fn_mode = 0;
+
+nde_database_t g_db = NULL;
+nde_table_t g_table = NULL;
+int g_table_dirty = 0;
+C_Config *g_config;
+CRITICAL_SECTION g_db_cs;
+
+HWND onTreeViewSelectChange(HWND hwnd)
+{
+ openDb();
+ if (m_curview_hwnd) DestroyWindow(m_curview_hwnd);
+
+ if (!g_table || nde_error)
+ {
+ m_curview_hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_DB_ERROR, hwnd, view_errorinfoDialogProc, 0);
+ }
+ else
+ {
+ m_curview_hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_RECENTITEMS, hwnd, view_historyDialogProc, 0);
+ }
+ return m_curview_hwnd;
+}
+
+static void history_cleanupifnecessary();
+static LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+int history_init()
+{
+ if ( !g_config->ReadInt( L"showrecentitems", 1 ) )
+ return 1;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW(IDS_HISTORY);
+ nis.item.pszInvariant = L"History";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.iSelectedImage = nis.item.iImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_RECENT);
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = {sizeof(NAVITEM),0,NIMF_ITEMID,};
+ nvItem.hItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ ml_history_tree = nvItem.id;
+
+ if (!wa_oldWndProc) // don't double dip (we call history_init() dynamically if the user fiddles with prefs
+ {
+ wa_oldWndProc = (WNDPROC)SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc);
+ }
+
+
+ return 1;
+}
+
+void history_quit()
+{
+ if (last_history_fn)
+ {
+ if ((last_timer <= 0) || (last_play_pos > last_timer))
+ {
+ if (last_play_pos) history_onFile(last_history_fn, last_play_pos);
+ }
+ }
+ free(last_history_fn);
+ last_history_fn = 0;
+ free(history_fn);
+ history_fn = 0;
+ closeDb();
+ mediaLibrary.RemoveTreeItem(ml_history_tree);
+ ml_history_tree=0;
+}
+
+static void RetypeFilename(nde_table_t table)
+{
+ // TODO: UI
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+ nde_scanner_t pruneScanner = NDE_Table_CreateScanner(table);
+ if (pruneScanner)
+ {
+ NDE_Scanner_First(pruneScanner);
+ while (!NDE_Scanner_EOF(pruneScanner))
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, HISTORYVIEW_COL_FILENAME);
+ if (f && NDE_Field_GetType(f) == FIELD_STRING)
+ {
+ wchar_t *s = NDE_StringField_GetString(f);
+ ndestring_retain(s);
+
+ NDE_Scanner_DeleteField(pruneScanner, f);
+
+ nde_field_t new_f = NDE_Scanner_NewFieldByID(pruneScanner, HISTORYVIEW_COL_FILENAME);
+ NDE_StringField_SetNDEString(new_f, s);
+
+ ndestring_release(s);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ else if (f)
+ break;
+
+ NDE_Scanner_Next(pruneScanner);
+ }
+
+ NDE_Table_DestroyScanner(table, pruneScanner);
+ NDE_Table_Sync(table);
+ }
+}
+
+static void CreateFields(nde_table_t table)
+{
+ // create defaults
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_LASTPLAYED, L"lastplay", FIELD_DATETIME);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_PLAYCOUNT, L"playcount", FIELD_INTEGER);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_TITLE, L"title", FIELD_STRING);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_LENGTH, L"length", FIELD_INTEGER);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_FILENAME, L"filename", FIELD_FILENAME);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_OFFSET, L"offset", FIELD_INTEGER);
+ NDE_Table_PostColumns(g_table);
+ NDE_Table_AddIndexByIDW(g_table, 0, L"filename");}
+
+int openDb()
+{
+ if (g_table) return 1; // need to close first
+
+ EnterCriticalSection(&g_db_cs);
+
+ // benski> i know this looks redundant, but we might have sat and blocked at the above Critical Section for a while
+ if (g_table)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+
+ if (!g_db)
+ {
+ __try
+ {
+ g_db = NDE_CreateDatabase(plugin.hDllInstance);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ g_db = NULL;
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ }
+
+ wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(tableName, g_tableDir, L"recent.dat");
+ PathCombineW(indexName, g_tableDir, L"recent.idx");
+
+ if (!g_db)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ g_table = NDE_Database_OpenTable(g_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
+ if (g_table)
+ {
+ CreateFields(g_table);
+ RetypeFilename(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ return (g_table != NULL);
+}
+
+void closeDb(bool clear_dirty)
+{
+ if (g_table_dirty && g_table)
+ {
+ history_bgQuery_Stop();
+ NDE_Table_Sync(g_table);
+ if (clear_dirty) g_table_dirty=0;
+ }
+ if (g_db)
+ {
+ __try
+ {
+ if (g_table)
+ NDE_Database_CloseTable(g_db, g_table);
+
+ NDE_DestroyDatabase(g_db);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ }
+ }
+ g_db = NULL;
+ g_table = NULL;
+}
+
+INT_PTR pluginHandleIpcMessage(int msg, INT_PTR param)
+{
+ return (INT_PTR) SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
+}
+
+static LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if ((uMsg == WM_SYSKEYDOWN || uMsg == WM_KEYDOWN) &&
+ wParam == 'H' &&
+ !(GetAsyncKeyState(VK_MENU)&0x8000) &&
+ !(GetAsyncKeyState(VK_SHIFT)&0x8000) &&
+ (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ && ml_history_tree > 0)
+ {
+ mediaLibrary.ShowMediaLibrary();
+ mediaLibrary.SelectTreeItem(ml_history_tree);
+ }
+ else if (history_fn && uMsg == WM_TIMER && wParam == 8082)
+ {
+ if (!history_fn_mode || SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_GETOUTPUTTIME) > 350)
+ {
+ KillTimer(hwndDlg, 8082);
+ if (SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_ISPLAYING) == 1)
+ {
+ history_onFile(history_fn, -1);
+ }
+ free(history_fn);
+ history_fn = 0;
+ }
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+ }
+ else if (last_history_fn && uMsg == WM_TIMER && wParam == 8083)
+ {
+ KillTimer(hwndDlg, 8083);
+ history_onFile(last_history_fn, last_play_pos);
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+ }
+ else if (uMsg == WM_WA_IPC)
+ {
+ if(lParam == IPC_STOPPLAYING && g_config->ReadInt(L"resumeplayback",0))
+ {
+ KillTimer(hwndDlg, 8082);
+ if (last_history_fn)
+ {
+ free(last_history_fn);
+ last_history_fn = 0;
+ }
+ if (history_fn) last_history_fn = _wcsdup(history_fn);
+ // copes with stopping after playback was tracked otherwise this aspect will fail!
+ else last_history_fn = _wcsdup((wchar_t*)SendMessage(hwndDlg,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME));
+ last_timer = timer;
+
+ stopPlayingInfoStruct *stopPlayingInfo = (stopPlayingInfoStruct *)wParam;
+ last_play_pos = stopPlayingInfo->last_time;
+
+ if (!stopPlayingInfo->g_fullstop)
+ {
+ if ((last_timer <= 0) || (last_play_pos > last_timer))
+ {
+ if (last_play_pos) SetTimer(hwndDlg, 8083, 150, NULL);
+ }
+ }
+ else // clean up play offset
+ {
+ if (last_history_fn) history_onFile(last_history_fn, 0);
+ }
+ }
+ else if(lParam == IPC_CB_MISC)
+ {
+ if (wParam == IPC_CB_MISC_PAUSE)
+ {
+ KillTimer(hwndDlg, 8082);
+ }
+ else if (wParam == IPC_CB_MISC_UNPAUSE)
+ {
+ if (history_fn) History_StartTracking(history_fn, true);
+ }
+ }
+ } // wm_wa_ipc
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/ml_history.h b/Src/Plugins/Library/ml_history/ml_history.h
new file mode 100644
index 00000000..4e8e60f5
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.h
@@ -0,0 +1,64 @@
+#ifndef ML_HISTORY_MAIN_H
+#define ML_HISTORY_MAIN_H
+
+#include "main.h"
+#include <windows.h>
+#include <commctrl.h>
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/config.h"
+#include "../nde/nde_c.h"
+
+#define HISTORYVIEW_COL_LASTPLAYED 0
+#define HISTORYVIEW_COL_PLAYCOUNT 1
+#define HISTORYVIEW_COL_TITLE 2
+#define HISTORYVIEW_COL_LENGTH 3
+#define HISTORYVIEW_COL_FILENAME 4
+#define HISTORYVIEW_COL_OFFSET 5
+
+#define UPDATE_QUERY_TIMER_ID 505
+
+extern int ml_history_tree;
+HWND onTreeViewSelectChange(HWND hwnd);
+
+int history_init();
+void history_quit();
+
+int openDb();
+void closeDb(bool clear_dirty=true);
+extern wchar_t g_tableDir[];
+extern C_Config *g_config;
+
+extern CRITICAL_SECTION g_db_cs;
+extern nde_database_t g_db;
+extern nde_table_t g_table;
+extern int g_table_dirty;
+
+inline BOOL WINAPI IsCharSpaceW(wchar_t c) { return (c == L' ' || c == L'\t'); }
+inline bool IsTheW(const wchar_t *str) { if (str && (str[0] == L't' || str[0] == L'T') && (str[1] == L'h' || str[1] == L'H') && (str[2] == L'e' || str[2] == L'E') && (str[3] == L' ')) return true; else return false; }
+#define SKIP_THE_AND_WHITESPACE(x) { wchar_t *save##x=(wchar_t*)x; while (IsCharSpaceW(*x) && *x) x++; if (IsTheW(x)) x+=4; while (IsCharSpaceW(*x)) x++; if (!*x) x=save##x; }
+
+void history_bgQuery_Stop();
+void history_onFile(const wchar_t *fn, int offset);
+int retrieve_offset(const wchar_t *fn);
+BOOL CALLBACK view_historyDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+BOOL CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+void db_setFieldInt(nde_scanner_t s, unsigned char id, int data);
+void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data);
+void makeFilename2(const wchar_t *filename, wchar_t *filename2, int filename2_len);
+void queryStrEscape(const char *p, GayString &str);
+INT_PTR pluginHandleIpcMessage(int msg, INT_PTR param);
+
+//prefs.cpp
+BOOL CALLBACK PrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+#include "history.h"
+
+void allocRecentRecordList(historyRecordList *obj, int newsize, int granularity=512);
+void emptyRecentRecordList(historyRecordList *obj);
+void recentScannerRefToObjCacheNFN(nde_scanner_t s, historyRecordList *obj);
+void sortResults(historyRecordList *obj, int column, int dir);
+void freeRecentRecordList(historyRecordList *obj);
+void saveQueryToList(nde_scanner_t s, historyRecordList *obj);
+
+#endif ML_HISTORY_MAIN_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/ml_history.rc b/Src/Plugins/Library/ml_history/ml_history.rc
new file mode 100644
index 00000000..6627b89a
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.rc
@@ -0,0 +1,264 @@
+// 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_VIEW_RECENTITEMS DIALOGEX 0, 0, 291, 248
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Search:",IDC_SEARCHCAPTION,1,2,25,8,SS_CENTERIMAGE
+ EDITTEXT IDC_QUICKSEARCH,28,1,209,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_CLEAR,"Button",BS_OWNERDRAW | WS_TABSTOP,240,0,49,11
+ CONTROL "List4",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,14,289,220
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,237,35,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,38,237,35,11
+ CONTROL "Remove",IDC_REMOVEBOOK,"Button",BS_OWNERDRAW | WS_TABSTOP,76,237,36,11
+ CONTROL "",IDC_MEDIASTATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,113,238,178,10
+END
+
+IDD_PREFS DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "History",IDC_STATIC,0,0,272,246
+ LTEXT "When 'History' is enabled, Winamp will keep track of when and how many times any items are played through Winamp.",IDC_STATIC2,6,12,259,16
+ CONTROL "Enable 'History' view in Media Library",IDC_CHECK1,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,34,132,10
+ CONTROL "Track when and how many times all files are played",IDC_CHECK2,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,49,177,10
+ CONTROL "Track when and how many times all streams are played",IDC_CHECK3,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,62,189,10
+ CONTROL "Limit tracking to items played in the last",IDC_CHECK4,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,75,138,10
+ EDITTEXT IDC_EDIT1,157,74,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "days",IDC_STATIC1,183,76,16,8
+ GROUPBOX "Tracking Control",IDC_STATIC,16,89,249,89
+ CONTROL "Wait",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,103,30,8
+ EDITTEXT IDC_EDIT2,53,101,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "seconds before tracking items",IDC_STATIC4,81,103,174,8
+ CONTROL "Wait",IDC_CHECK6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,119,30,8
+ EDITTEXT IDC_EDIT3,53,117,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "percent of playback before tracking items",IDC_STATIC5,81,119,174,8
+ CONTROL "Wait until the end of complete playback\n(May count before the displayed end due to output buffer sizes)",IDC_CHECK7,
+ "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,23,133,232,16
+ LTEXT "Note: When the first match against any of the above selected option(s) is met then the currently playing item will be tracked.",IDC_STATIC6,23,156,235,16
+ GROUPBOX "Playback Resume",IDC_STATIC,16,184,249,52
+ COMBOBOX IDC_COMBO1,22,196,236,39,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Note: This will make Winamp resume playback of tracked files from the last played position (once the first Tracking Control target has been reached).",IDC_STATIC7,22,212,249,16
+END
+
+IDD_VIEW_DB_ERROR DIALOGEX 0, 0, 194, 166
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CTEXT "",IDC_DB_ERROR,10,10,174,129,0x2000
+ CONTROL "Reset Database",IDC_RESET_DB_ON_ERROR,"Button",BS_OWNERDRAW | WS_TABSTOP,61,144,70,12
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "RecentWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_MEDIAWND_PLAYSELECTEDFILES
+ MENUITEM "Enqueue selection\tShift+Enter", ID_MEDIAWND_ENQUEUESELECTEDFILES
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", 40003
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", ID_MEDIAWND_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "View f&ile info...\tAlt+3", ID_PE_ID3
+ MENUITEM "Remove from recent list\tDel", ID_MEDIAWND_REMOVEFROMLIBRARY
+ MENUITEM SEPARATOR
+ MENUITEM "&Clear playback offset\tShift+Del", ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY
+ MENUITEM SEPARATOR
+ MENUITEM "Explore item folder\tCtrl+F", 40007
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_PREFS, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 246
+ END
+
+ IDD_VIEW_DB_ERROR, DIALOG
+ BEGIN
+ LEFTMARGIN, 10
+ RIGHTMARGIN, 184
+ TOPMARGIN, 10
+ BOTTOMMARGIN, 156
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_RECENT BITMAP "resources\\ti_history_items_16x16x16.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXT
+//
+
+IDR_DB_ERROR TEXT ".\\db_error.txt"
+IDR_NDE_ERROR TEXT "..\\ml_local\\nde_error.txt"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_ACCELERATORS ACCELERATORS
+BEGIN
+ "3", ID_PE_ID3, VIRTKEY, ALT, NOINVERT
+ "A", ID_MEDIAWND_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "F", ID_MEDIAWND_EXPLOREFOLDER, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_MEDIAWND_REMOVEFROMLIBRARY, VIRTKEY, NOINVERT
+ VK_DELETE, ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_MEDIAWND_PLAYSELECTEDFILES, VIRTKEY, NOINVERT
+ VK_RETURN, ID_MEDIAWND_ENQUEUESELECTEDFILES, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_HISTORY "Nullsoft History v%s"
+ 65535 "{F8756C00-11D2-4857-8C50-163AE4A57783}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_HISTORY "History"
+ IDS_DELAYLOAD_FAILURE "History was unable to load library ('%s') which is required. \nHistory cannot continue succesfully and will be turned off."
+ IDS_ERROR "Error"
+ IDS_SCANNING_ELLIPSE "Scanning..."
+ IDS_COL_LAST_PLAYED "Last Played"
+ IDS_COL_PLAY_COUNT "Play Count"
+ IDS_COL_TITLE "Title"
+ IDS_COL_LENGTH "Length"
+ IDS_COL_FILENAME "Filename"
+ IDS_EST_TIME_NO_SECS "%d %s, %u %s [%u:%02u estimated playtime]"
+ IDS_EST_TIME_HAS_SECS "%d %s, %u %s [%u:%02u:%02u estimated playtime]"
+ IDS_SCANNING "Scanning"
+ IDS_ITEM "item"
+ IDS_ITEMS "items"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PLAY "play"
+ IDS_PLAYS "plays"
+ IDS_DAY "day"
+ IDS_DAYS "days"
+ IDS_REMOVE_ALL_HISTORY "Are you sure you want to reset your playing history?"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_DISABLED "Disabled"
+ IDS_PODCAST_ONLY "Enabled for Podcast files only"
+ IDS_ANY_APPLICABLE "Enabled for any applicable files"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COL_OFFSET "Play Offset"
+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_history/ml_history.sln b/Src/Plugins/Library/ml_history/ml_history.sln
new file mode 100644
index 00000000..e8f35db5
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.sln
@@ -0,0 +1,94 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_history", "ml_history.vcxproj", "{840EEC3D-03D7-4186-8FC7-763392D13309}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A} = {4D25C321-7F8B-424E-9899-D80A364BAF1A}
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
+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
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|Win32.ActiveCfg = Debug|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|Win32.Build.0 = Debug|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|x64.ActiveCfg = Debug|x64
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|x64.Build.0 = Debug|x64
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|Win32.ActiveCfg = Release|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|Win32.Build.0 = Release|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|x64.ActiveCfg = Release|x64
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_history/ml_history.vcxproj b/Src/Plugins/Library/ml_history/ml_history.vcxproj
new file mode 100644
index 00000000..0d6cf7cd
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.vcxproj
@@ -0,0 +1,341 @@
+<?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>{840EEC3D-03D7-4186-8FC7-763392D13309}</ProjectGuid>
+ <RootNamespace>ml_history</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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="api_history.cpp" />
+ <ClCompile Include="HistoryAPI.cpp" />
+ <ClCompile Include="HistoryAPIFactory.cpp" />
+ <ClCompile Include="JSAPI2_Creator.cpp" />
+ <ClCompile Include="JSAPI2_HistoryAPI.cpp" />
+ <ClCompile Include="JSAPI2_HistoryRecord.cpp" />
+ <ClCompile Include="JSAPI2_HistoryRecordList.cpp" />
+ <ClCompile Include="Main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">main.h</PrecompiledHeaderFile>
+ </ClCompile>
+ <ClCompile Include="ml_history.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="view_history.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="api__ml_history.h" />
+ <ClInclude Include="api_history.h" />
+ <ClInclude Include="history.h" />
+ <ClInclude Include="HistoryAPI.h" />
+ <ClInclude Include="HistoryAPIFactory.h" />
+ <ClInclude Include="JSAPI2_Creator.h" />
+ <ClInclude Include="JSAPI2_HistoryAPI.h" />
+ <ClInclude Include="JSAPI2_HistoryRecord.h" />
+ <ClInclude Include="JSAPI2_HistoryRecordList.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="ml_history.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="..\ml_local\nde_error.txt" />
+ <Text Include="db_error.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_history.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_history_items_16x16x16.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ </ProjectReference>
+ <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_history/ml_history.vcxproj.filters b/Src/Plugins/Library/ml_history/ml_history.vcxproj.filters
new file mode 100644
index 00000000..4912c232
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.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="api_history.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HistoryAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HistoryAPIFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Creator.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_HistoryAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_HistoryRecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_HistoryRecordList.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_history.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_history.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="HistoryAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="HistoryAPIFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Creator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_HistoryAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_HistoryRecord.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_HistoryRecordList.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ml_history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="..\ml_local\nde_error.txt" />
+ <Text Include="db_error.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{c2e8f027-5cef-4e02-911f-4719fc3ae234}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{a71fca4c-7b5a-4857-9d5d-ac9a8635df44}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{bd330ba0-d0e2-4ac8-b30d-b936c4549d0c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{b97ef514-3134-4462-b767-54f001121b05}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{73085ff5-df6e-41f0-a474-a193e6007e23}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{6091f753-b15a-4842-b822-d511183811d7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{02146aa6-27b8-4603-89ac-f219ea316867}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{91facc1d-0b70-4a53-a029-b731c6a5fc00}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{a9cf0eec-25c4-45ad-a116-77cc32baf3fa}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{b4d6ce84-5fe5-48af-a5b1-f0e01e83d9c5}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_history.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_history_items_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/prefs.cpp b/Src/Plugins/Library/ml_history/prefs.cpp
new file mode 100644
index 00000000..8fa3832d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/prefs.cpp
@@ -0,0 +1,178 @@
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+
+void hideShowRecentItemsCheckboxes(HWND hwndDlg)
+{
+ int enabled = IsDlgButtonChecked(hwndDlg, IDC_CHECK1);
+ int wait_secs = !!g_config->ReadInt(L"recent_wait_secs",0);
+ int wait_percent = !!g_config->ReadInt(L"recent_wait_percent",0);
+ int wait_end = !!g_config->ReadInt(L"recent_wait_end",0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK2), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK3), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK4), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK5), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK6), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK7), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT1), enabled && !!g_config->ReadInt(L"recent_limitd",1));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC1), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC3), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC4), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC5), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC6), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT2), enabled && wait_secs);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT3), enabled && wait_percent);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC7), enabled && !(wait_end && !wait_secs && !wait_percent));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO1), enabled && !(wait_end && !wait_secs && !wait_percent));
+}
+
+BOOL CALLBACK PrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static int need_ref;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ CheckDlgButton(hwndDlg,IDC_CHECK2,(g_config->ReadInt(L"recent_track",1)&1)?BST_CHECKED:0);
+ CheckDlgButton(hwndDlg,IDC_CHECK3,(g_config->ReadInt(L"recent_track",1)&2)?0:BST_CHECKED);
+ CheckDlgButton(hwndDlg,IDC_CHECK1,!!g_config->ReadInt(L"showrecentitems",1));
+ CheckDlgButton(hwndDlg,IDC_CHECK4,!!g_config->ReadInt(L"recent_limitd",1));
+ CheckDlgButton(hwndDlg,IDC_CHECK5,!!g_config->ReadInt(L"recent_wait_secs",0));
+ CheckDlgButton(hwndDlg,IDC_CHECK6,!!g_config->ReadInt(L"recent_wait_percent",0));
+ CheckDlgButton(hwndDlg,IDC_CHECK7,!!g_config->ReadInt(L"recent_wait_end",0));
+
+ int item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_DISABLED));
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETITEMDATA,item,0);
+ item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_PODCAST_ONLY));
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETITEMDATA,item,1);
+ item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_ANY_APPLICABLE));
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETITEMDATA,item,2);
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETCURSEL,g_config->ReadInt(L"resumeplayback",0),0);
+
+ SetDlgItemInt(hwndDlg,IDC_EDIT1,g_config->ReadInt(L"recent_limitnd",30),FALSE);
+ SetDlgItemInt(hwndDlg,IDC_EDIT2,g_config->ReadInt(L"recent_wait_secs_lim",5),FALSE);
+ SetDlgItemInt(hwndDlg,IDC_EDIT3,g_config->ReadInt(L"recent_wait_percent_lim",50),FALSE);
+ need_ref=0;
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+ }
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_CHECK1:
+ {
+ int a=!!IsDlgButtonChecked(hwndDlg,IDC_CHECK1);
+ g_config->WriteInt(L"showrecentitems",a);
+ if (a) history_init();
+ else history_quit();
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ }
+ break;
+
+ case IDC_EDIT1:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ BOOL t;
+ int v=GetDlgItemInt(hwndDlg,IDC_EDIT1,&t,FALSE);
+ if (t) g_config->WriteInt(L"recent_limitnd",v);
+ need_ref++;
+ }
+ break;
+
+ case IDC_CHECK4:
+ g_config->WriteInt(L"recent_limitd",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK4));
+ EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT1),!!g_config->ReadInt(L"recent_limitd",1));
+ need_ref++;
+ break;
+
+ case IDC_CHECK2:
+ case IDC_CHECK3:
+ g_config->WriteInt(L"recent_track",(IsDlgButtonChecked(hwndDlg,IDC_CHECK2)?1:0) | (IsDlgButtonChecked(hwndDlg,IDC_CHECK3)?0:2));
+ break;
+
+ case IDC_CHECK5:
+ g_config->WriteInt(L"recent_wait_secs",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK5));
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+
+ case IDC_CHECK6:
+ g_config->WriteInt(L"recent_wait_percent",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK6));
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+
+ case IDC_CHECK7:
+ g_config->WriteInt(L"recent_wait_end",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK7));
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+
+ case IDC_COMBO1:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ int item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_GETCURSEL,0,0);
+ if (item != CB_ERR)
+ {
+ g_config->WriteInt(L"resumeplayback", (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_GETITEMDATA,item,0));
+ }
+ }
+ break;
+
+ case IDC_EDIT2:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ BOOL t;
+ int v=GetDlgItemInt(hwndDlg,IDC_EDIT2,&t,FALSE);
+ if (t)
+ {
+ if(v < 0)
+ {
+ v = 1;
+ SetDlgItemInt(hwndDlg, IDC_EDIT2, v, 0);
+ }
+ g_config->WriteInt(L"recent_wait_secs_lim",v);
+ }
+ need_ref++;
+ }
+ break;
+
+ case IDC_EDIT3:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ BOOL t;
+ int v=GetDlgItemInt(hwndDlg,IDC_EDIT3,&t,FALSE);
+ if(t)
+ {
+ int tweaked = 0;
+ if(v > 99){
+ v = 99;
+ tweaked = 1;
+ }
+ else if(v < 1)
+ {
+ v = 1;
+ tweaked = 1;
+ }
+ if(tweaked)
+ {
+ SetDlgItemInt(hwndDlg, IDC_EDIT3, v, 0);
+ }
+
+ g_config->WriteInt(L"recent_wait_percent_lim",v);
+ }
+ need_ref++;
+ }
+ break;
+ };
+ break;
+
+ case WM_DESTROY:
+ if (need_ref)
+ {
+ g_config->WriteInt(L"recent_limitlt",0); // make sure it gets refreshed
+ // TODO: only do this if the history view is open
+ PostMessage(plugin.hwndLibraryParent,WM_USER+30,0,0);
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/resource.h b/Src/Plugins/Library/ml_history/resource.h
new file mode 100644
index 00000000..83a2c048
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/resource.h
@@ -0,0 +1,92 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_history.rc
+//
+#define IDS_HISTORY 1
+#define IDS_DELAYLOAD_FAILURE 2
+#define IDS_ERROR 3
+#define IDS_SCANNING_ELLIPSE 4
+#define IDS_COL_LAST_PLAYED 5
+#define IDS_COL_PLAY_COUNT 6
+#define IDS_COL_TITLE 7
+#define IDS_COL_LENGTH 8
+#define IDS_COL_FILENAME 9
+#define IDS_EST_TIME_NO_SECS 10
+#define IDS_EST_TIME_HAS_SECS 11
+#define IDS_SCANNING 13
+#define IDS_ITEM 14
+#define IDS_ITEMS 15
+#define IDS_PLAY 16
+#define IDS_PLAYS 17
+#define IDS_DAY 18
+#define IDS_DAYS 19
+#define IDS_REMOVE_ALL_HISTORY 20
+#define IDS_CONFIRMATION 21
+#define IDS_DISABLED 22
+#define IDS_PODCAST_ONLY 23
+#define IDS_ANY_APPLICABLE 24
+#define IDR_CONTEXTMENUS 101
+#define IDD_DIALOG1 102
+#define IDB_BITMAP1 103
+#define IDB_TREEITEM_RECENT 103
+#define IDD_PREFS 104
+#define IDS_COL_OFFSET 107
+#define IDR_ACCELERATOR1 109
+#define IDR_VIEW_ACCELERATORS 109
+#define IDR_DB_ERROR 110
+#define IDD_VIEW_DB_ERROR 111
+#define IDR_NDE_ERROR 111
+#define IDD_VIEW_RECENTITEMS 246
+#define IDC_BUTTON_CUSTOM 1000
+#define IDC_LIST2 1001
+#define IDC_STATIC6 1001
+#define IDC_CLEAR 1005
+#define IDC_QUICKSEARCH 1006
+#define IDC_COMBO1 1006
+#define IDC_MEDIASTATUS 1015
+#define IDC_BUTTON_PLAY 1016
+#define IDC_BUTTON_ENQUEUE 1017
+#define IDC_SEARCHCAPTION 1020
+#define IDC_CHECK1 1050
+#define IDC_CHECK2 1051
+#define IDC_CHECK3 1052
+#define IDC_CHECK4 1053
+#define IDC_EDIT1 1054
+#define IDC_CHECK5 1055
+#define IDC_EDIT2 1056
+#define IDC_STATIC1 1057
+#define IDC_STATIC2 1058
+#define IDC_STATIC3 1059
+#define IDC_STATIC4 1060
+#define IDC_CHECK6 1061
+#define IDC_EDIT3 1062
+#define IDC_STATIC5 1063
+#define IDC_CHECK7 1064
+#define IDC_CHECK8 1065
+#define IDC_STATIC7 1066
+#define IDC_REMOVEBOOK 1084
+#define IDC_DB_ERROR 1085
+#define IDC_RESET_DB_ON_ERROR 1086
+#define ID_MEDIAWND_PLAYSELECTEDFILES 40001
+#define ID_MEDIAWND_ENQUEUESELECTEDFILES 40002
+#define ID_MEDIAWND_ADDTOPLAYLIST 40003
+#define ID_MEDIAWND_SELECTALL 40004
+#define ID_PE_ID3 40005
+#define ID_MEDIAWND_REMOVEFROMLIBRARY 40006
+#define ID_MEDIAWND_EXPLOREFOLDER 40007
+#define ID_NAVIGATION_PREFERENCES 40008
+#define ID_NAVIGATION_HELP 40009
+#define ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY 40010
+#define ID_RECENTWND_CLEARPLAYBACKOFFSET 40011
+#define IDS_NULLSOFT_HISTORY 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 113
+#define _APS_NEXT_COMMAND_VALUE 40012
+#define _APS_NEXT_CONTROL_VALUE 1007
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmp b/Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmp
new file mode 100644
index 00000000..06714c39
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_history/version.rc2 b/Src/Plugins/Library/ml_history/version.rc2
new file mode 100644
index 00000000..c0abd21a
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,0,3,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", "2,0,3,0"
+ VALUE "InternalName", "Nullsoft History"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_history.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_history/view_history.cpp b/Src/Plugins/Library/ml_history/view_history.cpp
new file mode 100644
index 00000000..010cf4e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/view_history.cpp
@@ -0,0 +1,2349 @@
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/listview.h"
+#include <time.h>
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include <malloc.h>
+#include <string>
+#include "../nu/AutoWide.h"
+#include "../nu/AutoCharFn.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../nu/sort.h"
+#include "../nu/menushortcuts.h"
+#include "../Winamp/strutil.h"
+#include "api__ml_history.h"
+#include <strsafe.h>
+
+static INT_PTR IPC_LIBRARY_SENDTOMENU;
+static void history_cleanupifnecessary();
+static GayStringW g_q;
+static W_ListView resultlist;
+static int resultSkin, customAllowed;
+static HWND m_hwnd, m_headerhwnd;
+static historyRecordList itemCache;
+static int history_bgThread_Kill;
+static HANDLE history_bgThread_Handle;
+static void fileInfoDialogs(HWND hwndParent);
+static int m_lv_last_topidx;
+int groupBtn = 1, enqueuedef = 0;
+HMENU g_context_menus2 = NULL;
+static viewButtons view;
+
+static void MakeDateStringW(__time64_t convertTime, wchar_t *dest, size_t destlen)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, (int)destlen);
+
+ size_t dateSize = lstrlenW(dest);
+ dest += dateSize;
+ destlen -= dateSize;
+ if (destlen)
+ {
+ *dest++ = ' ';
+ destlen--;
+ }
+
+ GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, (int)destlen);
+}
+
+void makeFilename2(const wchar_t *filename, wchar_t *filename2, int filename2_len)
+{
+ filename2[0]=0;
+ if (wcsstr(filename,L"~"))
+ {
+ WIN32_FIND_DATAW d = {0};
+ HANDLE h = FindFirstFileW(filename,&d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+ lstrcpynW(filename2,filename,filename2_len);
+ wchar_t *p=scanstr_backW(filename2,L"\\",filename2-1)+1;
+ int offs=(int)(p-filename2);
+ lstrcpynW(filename2+offs,d.cFileName,filename2_len - offs);
+ if (!_wcsicmp(filename,filename2)) filename2[0]=0;
+ }
+ }
+}
+
+void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (!f) f = NDE_Scanner_NewFieldByID(s, id);
+ NDE_StringField_SetString(f, data);
+}
+
+void db_setFieldInt(nde_scanner_t s, unsigned char id, int data)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (!f) f = NDE_Scanner_NewFieldByID(s, id);
+ NDE_IntegerField_SetValue(f, data);
+}
+
+void queryStrEscape(const wchar_t *p, GayStringW &str)
+{
+ if (!p || !*p) return;
+ size_t l = wcslen(p);
+ wchar_t *escaped = (wchar_t *)calloc((l*3+1), sizeof(wchar_t));
+ if (escaped)
+ {
+ wchar_t *d = escaped;
+ while (p && *p)
+ {
+ if (*p == L'%') { *d++ = L'%'; *d++ = L'%'; }
+ else if (*p == L'\"') { *d++ = L'%'; *d++ = L'2'; *d++ = L'2'; }
+ else if (*p == L'\'') { *d++ = L'%'; *d++ = L'2'; *d++ = L'7'; }
+ else if (*p == L'[') { *d++ = L'%'; *d++ = L'5'; *d++ = L'B'; }
+ else if (*p == L']') { *d++ = L'%'; *d++ = L'5'; *d++ = L'D'; }
+ else if (*p == L'(') { *d++ = L'%'; *d++ = L'2'; *d++ = L'8'; }
+ else if (*p == L')') { *d++ = L'%'; *d++ = L'2'; *d++ = L'9'; }
+ else if (*p == L'#') { *d++ = L'%'; *d++ = L'2'; *d++ = L'3'; }
+ else *d++ = *p;
+ p++;
+ }
+ *d = 0;
+ str.Set(escaped);
+ free(escaped);
+ }
+}
+
+int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb)
+{
+ if (!pa) pa = L"";
+ else SKIP_THE_AND_WHITESPACE(pa)
+
+ if (!pb) pb = L"";
+ else SKIP_THE_AND_WHITESPACE(pb)
+
+ return _wcsicmp(pa, pb);
+}
+
+struct SortRules
+{
+ int by;
+ int dir;
+};
+static int __fastcall sortFunc(const void *elem1, const void *elem2, const void *context)
+{
+ historyRecord *a=(historyRecord*)elem1;
+ historyRecord *b=(historyRecord*)elem2;
+
+ const SortRules *rules = (SortRules *)context;
+ int use_by = rules->by;
+ int use_dir = !!rules->dir;
+
+ #define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+
+ // this might be too slow, but it'd be nice
+ int x;
+ for (x = 0; x < 4; x ++)
+ {
+ if (use_by == HISTORY_SORT_FILENAME)
+ {
+ int v=STRCMP_NULLOK(a->filename,b->filename);
+ RETIFNZ(v)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_TITLE)
+ {
+ int v=STRCMP_NULLOK(a->title,b->title);
+ RETIFNZ(v)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_LASTPLAYED)
+ {
+ __time64_t v1=a->lastplayed;
+ __time64_t v2=b->lastplayed;
+ RETIFNZ(v2-v1)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_PLAYCOUNT)
+ {
+ int v1=a->playcnt;
+ int v2=b->playcnt;
+ RETIFNZ(v2-v1)
+ use_by=HISTORYVIEW_COL_LASTPLAYED;
+ }
+ else if (use_by == HISTORY_SORT_LENGTH) // length -> artist -> album -> track
+ {
+ int v1=a->length;
+ int v2=b->length;
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v2-v1)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_OFFSET)
+ {
+ int v1=a->offset;
+ int v2=b->offset;
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v2-v1)
+ return 0;
+ }
+ else break; // no sort order?
+ }
+ #undef RETIFNZ
+ return 0;
+}
+
+void sortResults(historyRecordList *obj, int column, int dir) // sorts the results based on the current sort mode
+{
+ if (obj->Size > 1)
+ {
+ SortRules rules = {column, dir};
+ nu::qsort(obj->Items,obj->Size,sizeof(historyRecord),&rules, sortFunc);
+ }
+}
+
+// does not copy filename
+void recentScannerRefToObjCacheNFN(nde_scanner_t s, historyRecordList *obj)
+{
+ nde_field_t f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_TITLE);
+ if (f)
+ {
+ wchar_t *strval = NDE_StringField_GetString(f);
+ ndestring_retain(strval);
+ obj->Items[obj->Size].title = strval;
+ }
+ else
+ obj->Items[obj->Size].title = 0;
+
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_LENGTH);
+ obj->Items[obj->Size].length = f?NDE_IntegerField_GetValue(f):-1;
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_OFFSET);
+ obj->Items[obj->Size].offset = f?NDE_IntegerField_GetValue(f):-1;
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_PLAYCOUNT);
+ obj->Items[obj->Size].playcnt = f?NDE_IntegerField_GetValue(f):0;
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_LASTPLAYED);
+ obj->Items[obj->Size].lastplayed = f?NDE_IntegerField_GetValue(f):0;
+ obj->Size++;
+}
+
+static void freeRecentRecord(historyRecord *p)
+{
+ ndestring_release(p->title);
+ ndestring_release(p->filename);
+}
+
+void emptyRecentRecordList(historyRecordList *obj)
+{
+ historyRecord *p=obj->Items;
+ while (obj->Size-->0)
+ {
+ freeRecentRecord(p);
+ p++;
+ }
+ obj->Size=0;
+}
+
+void freeRecentRecordList(historyRecordList *obj)
+{
+ emptyRecentRecordList(obj);
+ free(obj->Items);
+ obj->Items=0;
+ obj->Alloc=obj->Size=0;
+}
+
+void allocRecentRecordList(historyRecordList *obj, int newsize, int granularity)
+{
+ if (newsize < obj->Alloc || newsize < obj->Size) return;
+
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc=newsize+granularity;
+ historyRecord *data = (historyRecord*)realloc(obj->Items,sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ obj->Items=data;
+ }
+ else
+ {
+ data=(historyRecord*)malloc(sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(historyRecord)*old_Alloc);
+ free(obj->Items);
+ obj->Items=data;
+ }
+ else obj->Alloc = (int)old_Alloc;
+ }
+ if (!obj->Items) obj->Alloc=0;
+}
+
+static void playFiles(int enqueue, int all)
+{
+ if (history_bgThread_Handle) return;
+
+ int cnt=0;
+ int l=itemCache.Size;
+
+ for(int i=0;i<l;i++)
+ {
+ if ( all || resultlist.GetSelected( i ) )
+ {
+ if ( !cnt )
+ {
+ if ( !enqueue ) SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE );
+ cnt++;
+ }
+ enqueueFileWithMetaStructW s = { 0 };
+ s.filename = itemCache.Items[ i ].filename;
+ s.title = itemCache.Items[ i ].title;
+ s.ext = NULL;
+ s.length = itemCache.Items[ i ].length;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW );
+ }
+ }
+ if (cnt)
+ {
+ if(!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC,0,IPC_STARTPLAY);
+ }
+}
+
+static int history_saveQueryToList(nde_scanner_t s, historyRecordList *obj, int user32, int *killswitch) {
+
+ emptyRecentRecordList(obj);
+
+ NDE_Scanner_First(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ return 0;
+ }
+
+ int r;
+ unsigned int total_length_s=0;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_FILENAME);
+ if (!f) break;
+
+ allocRecentRecordList(obj,obj->Size+1);
+ if (!obj->Alloc) break;
+
+ wchar_t *strval = NDE_StringField_GetString(f);
+ ndestring_retain(strval);
+ obj->Items[obj->Size].filename = strval;
+ recentScannerRefToObjCacheNFN(s,obj);
+
+ int thisl=obj->Items[obj->Size-1].length;
+
+ if (thisl > 0) total_length_s+=thisl * obj->Items[obj->Size-1].playcnt;
+ else total_length_s|=(1<<31);
+
+ r=NDE_Scanner_Next(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ return 0;
+ }
+ } while(r);
+
+ if (obj->Size && obj->Size < obj->Alloc - 1024)
+ {
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc = obj->Size;
+ historyRecord *data=(historyRecord*)realloc(obj->Items,sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ obj->Items=data;
+ }
+ else
+ {
+ data=(historyRecord*)malloc(sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(historyRecord)*old_Alloc);
+ free(obj->Items);
+ obj->Items=data;
+ }
+ else obj->Alloc = (int)old_Alloc;
+ }
+ }
+
+ if (killswitch && *killswitch) return 0;
+
+ sortResults(obj,
+ g_config->ReadInt(L"recent_sort_by",HISTORY_SORT_LASTPLAYED),
+ g_config->ReadInt(L"recent_sort_dir",0));
+
+ if (killswitch && *killswitch) return 0;
+
+ return total_length_s;
+}
+
+typedef struct
+{
+ int user32;
+} history_bgThreadParms;
+
+// out can never be bigger than in+1
+static void parsequicksearch(wchar_t *out, const wchar_t *in) // parses a list into a list of terms that we are searching for
+{
+ int inquotes = 0, neednull = 0;
+ while (in && *in)
+ {
+ wchar_t c = *in++;
+ if (c != ' ' && c != '\t' && c != '\"')
+ {
+ neednull = 1;
+ *out++ = c;
+ }
+ else if (c == '\"')
+ {
+ inquotes = !inquotes;
+ if (!inquotes)
+ {
+ *out++ = 0;
+ neednull = 0;
+ }
+ }
+ else
+ {
+ if (inquotes) *out++ = c;
+ else if (neednull)
+ {
+ *out++ = 0;
+ neednull = 0;
+ }
+ }
+ }
+ *out++ = 0;
+ *out++ = 0;
+}
+
+void makeQueryStringFromText(GayStringW *query, const wchar_t *text, int nf)
+{
+ int ispar = 0;
+ if (query->Get()[0])
+ {
+ ispar = 1;
+ query->Append(L"&(");
+ }
+ if (!_wcsnicmp(text, L"query:", 6)) query->Append(text + 6); // copy the query as is
+ else if (text[0] == L'?') query->Append(text + 1);
+ else // this is ubergay. no wait it isn't anymore. it rocks now due to the GayString
+ {
+ int isAny = 0;
+ if (text && (*text == L'*' && text[1] == L' '))
+ {
+ isAny = 1;
+ text += 2;
+ }
+ int cchText = lstrlenW(text);
+ wchar_t *tmpbuf = (wchar_t*)calloc((cchText + 2), sizeof(wchar_t));
+ parsequicksearch(tmpbuf, text);
+
+ int x;
+ const wchar_t *fields[5] =
+ {
+ L"filename",
+ L"title",
+ L"artist",
+ L"album",
+ L"genre",
+ };
+ wchar_t *p = tmpbuf;
+ while (p && *p)
+ {
+ size_t lenp = wcslen(p);
+
+ if (p == tmpbuf) query->Append(L"(");
+ else if (isAny) query->Append(L")|(");
+ else query->Append(L")&(");
+ if (p[0] == L'<' && p[wcslen(p) - 1] == L'>' && wcslen(p) > 2)
+ {
+ wchar_t *op = p;
+ while (op && *op)
+ {
+ if (op && *op == L'\'') *op = L'\"';
+ if (op) op++;
+ }
+ p[lenp - 1] = 0; // remove >
+ query->Append(p + 1);
+ }
+ else
+ {
+ for (x = 0; x < (int)min(sizeof(fields) / sizeof(fields[0]), nf); x ++)
+ {
+ const wchar_t *field = fields[x];
+ if (x) query->Append(L"|");
+ query->Append(field);
+ query->Append(L" HAS \"");
+ GayStringW escaped;
+ queryStrEscape(p, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\"");
+ }
+ }
+ p += lenp + 1;
+ }
+ query->Append(L")");
+ free(tmpbuf);
+ }
+ if (ispar) query->Append(L")");
+}
+
+static DWORD WINAPI history_bgThreadQueryProc(void *tmp)
+{
+ history_bgThreadParms *p=(history_bgThreadParms*)tmp;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, g_q.Get());
+ int total_length_s=history_saveQueryToList(s,&itemCache, p->user32, &history_bgThread_Kill);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (!history_bgThread_Kill) PostMessage(m_hwnd,WM_APP+3,0x69,total_length_s);
+ return 0;
+}
+
+void history_bgQuery_Stop() // exported for other people to call since it is useful (eventually
+{ // we should have bgQuery pass the new query info along but I'll do that soon)
+ if (history_bgThread_Handle)
+ {
+ history_bgThread_Kill=1;
+ WaitForSingleObject(history_bgThread_Handle,INFINITE);
+ CloseHandle(history_bgThread_Handle);
+ history_bgThread_Handle=0;
+ }
+ KillTimer(m_hwnd,123);
+}
+
+static void history_bgQuery(int user32=0) // only internal used
+{
+ history_bgQuery_Stop();
+
+ SetDlgItemTextW(m_hwnd,IDC_MEDIASTATUS,WASABI_API_LNGSTRINGW(IDS_SCANNING_ELLIPSE));
+ SetTimer(m_hwnd,123,200,NULL);
+
+ DWORD id;
+ static history_bgThreadParms parms;
+ parms.user32=user32;
+ history_bgThread_Kill=0;
+ history_bgThread_Handle=CreateThread(NULL,0,history_bgThreadQueryProc,(LPVOID)&parms,0,&id);
+}
+
+static void doQuery(HWND hwndDlg, wchar_t *text, int dobg=1) {
+ history_bgQuery_Stop();
+
+ GayStringW query;
+ if (text[0]) makeQueryStringFromText(&query,text,2);
+
+ wchar_t *parent_query=NULL;
+ SendMessage(GetParent(hwndDlg),WM_APP+2,0,(LONG_PTR)&parent_query);
+
+ g_q.Set(L"");
+
+ if(parent_query && parent_query[0])
+ {
+ g_q.Set(L"(");
+ g_q.Append(parent_query);
+ g_q.Append(L")");
+ }
+
+ if (query.Get() && query.Get()[0])
+ {
+ if(g_q.Get()[0])
+ {
+ g_q.Append(L" & (");
+ g_q.Append(query.Get());
+ g_q.Append(L")");
+ }
+ else g_q.Set(query.Get());
+ }
+
+ if (dobg) history_bgQuery();
+}
+
+static WNDPROC search_oldWndProc;
+static DWORD WINAPI search_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if(uMsg == WM_KEYDOWN && wParam == VK_DOWN)
+ {
+ HWND hwndList = resultlist.getwnd();
+ if (hwndList)
+ {
+ PostMessage(GetParent(hwndDlg), WM_NEXTDLGCTL, (WPARAM)hwndList, TRUE);
+ ListView_SetItemState(hwndList,0,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
+ }
+ }
+ return (DWORD)CallWindowProcW(search_oldWndProc,hwndDlg,uMsg,wParam,lParam);
+}
+
+static void exploreItemFolder(HWND hwndDlg)
+{
+ if (resultlist.GetSelectionMark() >= 0)
+ {
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ WASABI_API_EXPLORERFINDFILE->AddFile(itemCache.Items[i].filename);
+ }
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+static void removeSelectedItems(int isAll=0)
+{
+ int hasdel=0;
+ history_bgQuery_Stop();
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ for(int i=0;i<itemCache.Size;i++)
+ {
+ if(resultlist.GetSelected(i) || isAll)
+ {
+ if(NDE_Scanner_LocateNDEFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,itemCache.Items[i].filename))
+ {
+ hasdel=1;
+ NDE_Scanner_Edit(s);
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ }
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (!hasdel)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return;
+ }
+
+ NDE_Table_Sync(g_table);
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+
+ resultlist.Clear();
+ emptyRecentRecordList(&itemCache);
+
+ SendMessage(m_hwnd,WM_APP+1,0,0); //refresh current view
+}
+
+static void removeSelectedItemOffsets(int isAll=0)
+{
+ int hasdel=0;
+ history_bgQuery_Stop();
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ for(int i=0;i<itemCache.Size;i++)
+ {
+ if(resultlist.GetSelected(i) || isAll)
+ {
+ if(NDE_Scanner_LocateNDEFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,itemCache.Items[i].filename))
+ {
+ db_setFieldInt(s, HISTORYVIEW_COL_OFFSET, -1);
+ NDE_Scanner_Post(s);
+ hasdel=1;
+ itemCache.Items[i].offset = -1;
+ }
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (!hasdel)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return;
+ }
+
+ NDE_Table_Sync(g_table);
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+
+ resultlist.RefreshAll();
+}
+
+static void History_SaveLastQuery(HWND hwnd)
+{
+ LPSTR pszQuery = NULL;
+ HWND hEditbox = GetDlgItem(hwnd, IDC_QUICKSEARCH);
+ if (NULL != hEditbox)
+ {
+ UINT cchTextMax = GetWindowTextLength(hEditbox);
+ if (0 != cchTextMax)
+ {
+ cchTextMax++;
+ LPWSTR pszText = (LPWSTR)calloc(cchTextMax, sizeof(WCHAR));
+ if (NULL != pszText)
+ {
+ UINT cchText = GetWindowTextW(hEditbox, pszText, cchTextMax);
+ if (0 != cchText)
+ {
+ UINT cchQuery = WideCharToMultiByte(CP_UTF8, 0, pszText, cchText, NULL, 0, NULL, NULL);
+ if (0 != cchQuery)
+ {
+ cchQuery++;
+ pszQuery = (LPSTR)calloc(cchQuery, sizeof(CHAR));
+ if (NULL != pszQuery)
+ {
+ cchQuery = WideCharToMultiByte(CP_UTF8, 0, pszText, cchText, pszQuery, cchQuery, NULL, NULL);
+ pszQuery[cchQuery] = '\0';
+ }
+ }
+ }
+ free(pszText);
+ }
+ }
+ }
+ g_config->WriteString("recent_lastquery", pszQuery);
+ if (NULL != pszQuery)
+ free(pszQuery);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_MEDIAWND_PLAYSELECTEDFILES)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ }
+ else if (id == ID_MEDIAWND_ENQUEUESELECTEDFILES)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos= i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ }
+ }
+
+ playItem.wID = ID_MEDIAWND_ENQUEUESELECTEDFILES;
+ enqueueItem.wID = ID_MEDIAWND_PLAYSELECTEDFILES;
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu)
+{
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(hwndDlg, szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendMenuShortcuts(menu, szAccel, c, MSF_REPLACE);
+}
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x3
+#define GROUP_SEARCH 0x1
+#define GROUP_STATUSBAR 0x2
+#define GROUP_MAIN 0x3
+
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_SEARCH, IDC_SEARCHCAPTION, IDC_CLEAR, IDC_QUICKSEARCH,
+ GROUP_STATUSBAR, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_REMOVEBOOK, IDC_MEDIASTATUS,
+ GROUP_MAIN, IDC_LIST2
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.bottom == rc.top || rc.right == rc.left) return;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_SEARCH:
+ {
+ wchar_t buffer[128] = {0};
+ GetDlgItemTextW(hwnd, IDC_BUTTON_PLAY, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(GetDlgItem(hwnd, IDC_BUTTON_PLAY), buffer);
+
+ SetRect(&rg, rc.left, rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ break;
+ }
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_PLAY);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ break;
+ }
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_SEARCHCAPTION:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(2),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_CLEAR:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_QUICKSEARCH:
+ pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_BUTTON_PLAY:
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_CUSTOM:
+ case IDC_REMOVEBOOK:
+ if (IDC_BUTTON_CUSTOM != pl->id || customAllowed)
+ {
+ if (groupBtn && pl->id == IDC_BUTTON_PLAY && enqueuedef == 1)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && pl->id == IDC_BUTTON_ENQUEUE && enqueuedef != 1)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && (pl->id == IDC_BUTTON_PLAY || pl->id == IDC_BUTTON_ENQUEUE) && customAllowed)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ width, WASABI_API_APP->getScaleY(HIWORD(idealSize)));
+ pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_MEDIASTATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > WASABI_API_APP->getScaleX(16)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_LIST2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1),
+ (rg.right - rg.left) - WASABI_API_APP->getScaleX(3),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && !fUpdateAll && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+
+ pl++;
+ }
+ else if (!fUpdateAll && (fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ if (pc->rgn) DeleteObject(pc->rgn);
+ }
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void LayoutWindows2(HWND hwnd, BOOL fRedraw)
+{
+ RECT rc, rg;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ SetRect(&rg, 0, 0, 0, 0);
+
+ rc.top += WASABI_API_APP->getScaleY(2);
+ rc.right -= WASABI_API_APP->getScaleY(2);
+
+ if (rc.bottom <= rc.top || rc.right <= rc.left) return;
+
+ HWND temp = GetDlgItem(hwnd, IDC_DB_ERROR);
+ GetWindowRect(temp, &rg);
+ SetWindowPos(temp, NULL, WASABI_API_APP->getScaleX(20), WASABI_API_APP->getScaleY(20),
+ rc.right - rc.left - WASABI_API_APP->getScaleX(40),
+ rc.bottom - rc.top - WASABI_API_APP->getScaleY(45),
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+
+ temp = GetDlgItem(hwnd, IDC_RESET_DB_ON_ERROR);
+ GetWindowRect(temp, &rg);
+ SetWindowPos(temp, NULL, ((rc.right - rc.left) - (rg.right - rg.left)) / WASABI_API_APP->getScaleX(2),
+ rc.bottom - (rg.bottom - rg.top),
+ rg.right - rg.left, rg.bottom - rg.top,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ if (fRedraw)
+ {
+ UpdateWindow(hwnd);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ if (rgn)
+ {
+ OffsetRgn(rgn, rc.left, rc.top);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, rgn, RGN_OR);
+ }
+ }
+ ValidateRgn(hwnd, NULL);
+ if (rgn) DeleteObject(rgn);
+}
+
+static void history_ManageButtons(HWND hwndDlg)
+{
+ int has_selection = resultlist.GetSelectedCount();
+
+ const int buttonids[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_REMOVEBOOK};
+ for (size_t i = 0; i != sizeof(buttonids)/sizeof(buttonids[0]); i++)
+ {
+ HWND controlHWND = GetDlgItem(hwndDlg, buttonids[i]);
+ EnableWindow(controlHWND, has_selection);
+ }
+}
+
+void history_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+void UpdateMenuItems(HWND hwndDlg, HMENU menu)
+{
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ SyncMenuWithAccelerators(hwndDlg, menu);
+ if (swapPlayEnqueue) SwapPlayEnqueueInMenu(menu);
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL history_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0)
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem(hwndDlg, buttonId);
+
+ GetWindowRect(buttonHWND, &r);
+ UpdateMenuItems(hwndDlg, menu);
+ MLSkinnedButton_SetDropDownState(buttonHWND, TRUE);
+
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if (!(flags & BPM_WM_COMMAND))
+ tpmFlags |= TPM_RETURNCMD;
+ int x = Menu_TrackPopup(plugin.hwndLibraryParent, menu, tpmFlags, r.left, r.top, hwndDlg, NULL);
+ if ((flags & BPM_ECHO_WM_COMMAND) && x)
+ SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(x, 0), 0);
+ MLSkinnedButton_SetDropDownState(buttonHWND, FALSE);
+ return x;
+}
+
+static void history_Play(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(g_context_menus2, 0);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ history_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+BOOL CALLBACK view_historyDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a = (BOOL)dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+
+ static HMENU sendto_hmenu;
+ static librarySendToMenuStruct s;
+
+ switch(uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ }
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ {
+ ListView_SetTextColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ resultlist.SetFont(dialogSkinner.GetFont());
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+
+ case WM_CONTEXTMENU:
+ {
+ HWND hwndFrom = (HWND)wParam;
+ if (hwndFrom != resultlist.getwnd())
+ return 0;
+
+ int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
+ POINT pt = {x, y};
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = resultlist.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ resultlist.GetItemRect(selected, &itemRect);
+ ClientToScreen(resultlist.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(resultlist.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(hwndFrom, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(hwndFrom, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return 0;
+ }
+
+ HMENU g_context_menus=WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU menu=GetSubMenu(g_context_menus,0);
+ sendto_hmenu=GetSubMenu(menu,2);
+
+ UpdateMenuItems(hwndDlg, menu);
+
+ s.mode = 0;
+ s.hwnd = 0;
+ s.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = (INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&"LibrarySendToMenu",IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)0,IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[1] = 1;
+ s.build_hMenu = sendto_hmenu;
+ }
+
+ UINT menustate = 0;
+ int n=resultlist.GetSelectedCount();
+ if(n == 0)
+ {
+ menustate = MF_BYCOMMAND|MF_GRAYED;
+ }
+ else
+ {
+ menustate = MF_BYCOMMAND|MF_ENABLED;
+ }
+
+ EnableMenuItem(menu,ID_PE_ID3,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_PLAYSELECTEDFILES,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_ENQUEUESELECTEDFILES,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_EXPLOREFOLDER,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_REMOVEFROMLIBRARY,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY,menustate);
+ EnableMenuItem(menu, 2, MF_BYPOSITION|(n==0?MF_GRAYED:MF_ENABLED));
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL);
+ if(!SendMessage(hwndDlg,WM_COMMAND,r,0))
+ {
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
+ {
+ // build my data.
+ s.mode=3;
+ s.data_type=ML_TYPE_FILENAMESW;
+
+ //std::vector<wchar_t> sendStr;
+ std::wstring sendStr;
+
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ // HAKAN: why (len + 1) ?
+ //sendStr.append(itemCache.Items[i].filename, wcslen(itemCache.Items[i].filename)+1);
+ sendStr.append(itemCache.Items[i].filename, wcslen(itemCache.Items[i].filename));
+ }
+ }
+ // HAKAN: No need to add trailing zero
+ //sendStr.push_back(0);
+
+ s.data = (void*)sendStr.c_str();
+
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU)!=1)
+ {
+ s.mode=3;
+ s.data_type=ML_TYPE_FILENAMES;
+
+ //std::vector<char> sendStrA;
+ std::string sendStrA;
+
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ // HAKAN: why (len + 1) ?
+ //sendStrA.append(AutoCharFn(itemCache.Items[i].filename), strlen(AutoCharFn(itemCache.Items[i].filename))+1);
+ sendStrA.append(AutoCharFn(itemCache.Items[i].filename), strlen(AutoCharFn(itemCache.Items[i].filename)));
+ }
+ }
+ // HAKAN: No need to add trailing zero
+ //sendStrA.push_back(0);
+
+ s.data = (void*)sendStrA.c_str();
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU);
+ }
+ }
+ }
+ }
+
+ if (s.mode)
+ {
+ s.mode=4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+
+ sendto_hmenu=0;
+ DestroyMenu(g_context_menus);
+ Sleep(100);
+ MSG msg = {0};
+ while(PeekMessage(&msg,NULL,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE)); //eat return
+ }
+ return 0;
+
+ case WM_INITDIALOG:
+ {
+ m_hwnd=hwndDlg;
+ g_q.Set(L"");
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ history_cleanupifnecessary();
+
+ itemCache.Items=0;
+ itemCache.Alloc=0;
+ itemCache.Size=0;
+
+ resultlist.setwnd(GetDlgItem(hwndDlg,IDC_LIST2));
+ resultlist.ForceUnicode();
+ resultSkin = (int)(INT_PTR)resultlist.getwnd(); //Might be unsafe
+
+ groupBtn = g_config->ReadInt(L"groupbtn", 1);
+ enqueuedef = (g_config->ReadInt(L"enqueuedef", 0) == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_CUSTOM, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_history"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_CUSTOM, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ m.hwndToSkin = resultlist.getwnd();
+ MLSkinWindow(mediaLibrary.library, &m);
+
+ m.skinType = SKINNEDWND_TYPE_BUTTON;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0);
+
+ FLICKERFIX ff = {0, FFM_ERASEINPAINT};
+ const int buttonids[] = {IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM};
+ for (size_t i=0;i!=sizeof(buttonids)/sizeof(buttonids[0]);i++)
+ {
+ m.hwndToSkin = ff.hwnd = GetDlgItem(hwndDlg, buttonids[i]);
+ if (IsWindow(m.hwndToSkin))
+ {
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+ }
+
+ INT ffcl[] = {IDC_REMOVEBOOK, IDC_CLEAR, IDC_MEDIASTATUS, IDC_QUICKSEARCH, IDC_SEARCHCAPTION};
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ for (INT index = 0; index < sizeof(ffcl) / sizeof(INT); index++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]);
+ if (IsWindow(ff.hwnd))
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ m.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ }
+
+ ListView_SetTextColor(resultlist.getwnd(),dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(resultlist.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+
+ resultlist.SetFont(dialogSkinner.GetFont());
+
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_LAST_PLAYED), g_config->ReadInt(L"recent_col_lp", 111));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_PLAY_COUNT), g_config->ReadInt(L"recent_col_count", 70));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TITLE), g_config->ReadInt(L"recent_col_title", 238));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_LENGTH), g_config->ReadInt(L"recent_col_len", 50));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_FILENAME), g_config->ReadInt(L"recent_col_filename", 285));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_OFFSET), g_config->ReadInt(L"recent_col_offset", 80));
+
+ m_headerhwnd=ListView_GetHeader(resultlist.getwnd());
+
+ {
+ char *query="";
+ if (g_config->ReadInt(L"remembersearch",0)) query = g_config->ReadString("recent_lastquery", "");
+ AutoWide queryUnicode(query, CP_UTF8);
+ SetDlgItemTextW(hwndDlg,IDC_QUICKSEARCH,queryUnicode);
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ doQuery(hwndDlg,queryUnicode,0);
+ }
+
+ {
+ int l_sc=g_config->ReadInt(L"recent_sort_by", HISTORY_SORT_LASTPLAYED);
+ int l_sd=g_config->ReadInt(L"recent_sort_dir", 0);
+ mediaLibrary.ListViewSort(resultSkin, l_sc, l_sd);
+ mediaLibrary.ListViewShowSort(resultSkin, TRUE);
+ }
+
+ history_ManageButtons(hwndDlg);
+ history_UpdateButtonText(hwndDlg, enqueuedef == 1);
+
+ search_oldWndProc = (WNDPROC)(LONG_PTR)SetWindowLongPtrW(GetDlgItem(hwndDlg,IDC_QUICKSEARCH),GWLP_WNDPROC,(LONG_PTR)search_newWndProc);
+ break;
+ }
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_MOUSEMOVE:
+ if (GetCapture()==hwndDlg)
+ {
+ POINT p;
+ p.x=GET_X_LPARAM(lParam);
+ p.y=GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg,&p);
+ mlDropItemStruct m={0};
+ m.type=ML_TYPE_FILENAMESW;
+ m.p=p;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG,(WPARAM)&m);
+ break;
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (GetCapture()==hwndDlg)
+ {
+ ReleaseCapture();
+ POINT p;
+ p.x=GET_X_LPARAM(lParam);
+ p.y=GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg,&p);
+ mlDropItemStruct m={0};
+ m.type=ML_TYPE_FILENAMESW;
+ m.p=p;
+ m.flags=ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG,(WPARAM)&m);
+
+ if (m.result>0)
+ {
+ size_t buf_size=4096;
+ wchar_t *buf=(wchar_t*)calloc(buf_size, sizeof(wchar_t));
+ int buf_pos=0;
+
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ const wchar_t *tbuf = itemCache.Items[i].filename;
+ size_t cchFilename = wcslen(tbuf) + 1;
+ size_t newsize=buf_pos + cchFilename;
+ if (newsize < buf_size)
+ {
+ size_t old_buf_size = buf_size;
+ buf_size=newsize+4096;
+ wchar_t *reallocated_buf = (wchar_t*)realloc(buf,buf_size*sizeof(wchar_t));
+ if (reallocated_buf)
+ {
+ buf = reallocated_buf;
+ }
+ else
+ {
+ wchar_t *newbuf = (wchar_t*)malloc(buf_size*sizeof(wchar_t));
+ if (!newbuf) // out of memory? well we can at least send what we've got
+ break;
+ memcpy(newbuf, buf, old_buf_size);
+ free(buf);
+ buf = newbuf;
+ }
+ }
+ StringCchCopyNW(buf+buf_pos,buf_size-buf_pos,tbuf,cchFilename);
+ buf_pos=(int)newsize;
+ }
+ }
+
+ if (buf_pos)
+ {
+ buf[buf_pos]=0;
+ m.flags=0;
+ m.result=0;
+ m.data=(void*)buf;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP,(WPARAM)&m);
+ }
+
+ free(buf);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ if (GetFocus() != GetDlgItem(hwndDlg, IDC_QUICKSEARCH))
+ {
+ switch(LOWORD(wParam))
+ {
+ case IDC_REMOVEBOOK:
+ removeSelectedItems(0);
+ break;
+ case IDC_CLEAR:
+ SetDlgItemText(hwndDlg,IDC_QUICKSEARCH,TEXT(""));
+ break;
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg,UPDATE_QUERY_TIMER_ID,100,NULL);
+ }
+ break;
+ case IDC_BUTTON_PLAY:
+ case ID_MEDIAWND_PLAYSELECTEDFILES:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_MEDIAWND_ENQUEUESELECTEDFILES:
+ case IDC_BUTTON_CUSTOM:
+ {
+ if (HIWORD(wParam) == MLBN_DROPDOWN)
+ {
+ history_Play(hwndDlg, (HWND)lParam, LOWORD(wParam));
+ }
+ else
+ {
+ int action;
+ if (LOWORD(wParam) == IDC_BUTTON_PLAY || LOWORD(wParam) == ID_MEDIAWND_PLAYSELECTEDFILES)
+ {
+ action = (HIWORD(wParam) == 1) ? g_config->ReadInt(L"enqueuedef", 0) == 1 : 0;
+ }
+ else if (LOWORD(wParam) == IDC_BUTTON_ENQUEUE || LOWORD(wParam) == ID_MEDIAWND_ENQUEUESELECTEDFILES)
+ {
+ action = (HIWORD(wParam) == 1) ? g_config->ReadInt(L"enqueuedef", 0) != 1 : 1;
+ }
+ else
+ break;
+
+ playFiles(action, 0);
+ }
+ break;
+ }
+ case ID_MEDIAWND_SELECTALL:
+ ListView_SetItemState(resultlist.getwnd(), -1, LVIS_SELECTED, LVIS_SELECTED);
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ removeSelectedItems(0);
+ break;
+ case ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY:
+ removeSelectedItemOffsets(0);
+ break;
+ case ID_MEDIAWND_EXPLOREFOLDER:
+ exploreItemFolder(hwndDlg);
+ break;
+ case ID_PE_ID3:
+ fileInfoDialogs(hwndDlg);
+ break;
+ }
+ }
+ else
+ {
+ switch(LOWORD(wParam))
+ {
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg,UPDATE_QUERY_TIMER_ID,100,NULL);
+ }
+ break;
+ case ID_MEDIAWND_SELECTALL:
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ {
+ DWORD start = -1, end = -1;
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
+ if (start != -1)
+ {
+ if (start == end)
+ {
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, start, end + 1);
+ }
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_REPLACESEL, TRUE, (LPARAM)L"");
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, start, start);
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ case WM_TIMER:
+ if (wParam == 123)
+ {
+ if (history_bgThread_Handle)
+ {
+ ListView_SetItemCount(resultlist.getwnd(),1);
+ ListView_RedrawItems(resultlist.getwnd(),0,0);
+ }
+ }
+ else if (wParam == UPDATE_QUERY_TIMER_ID)
+ {
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ wchar_t text[512] = {0};
+ GetWindowTextW(GetDlgItem(hwndDlg,IDC_QUICKSEARCH),text,511-1);
+ text[511]=0;
+ doQuery(hwndDlg,text);
+ }
+ return 0;
+
+ case WM_APP+3: // sent by bgthread
+ if (wParam == 0x69)
+ {
+ history_bgQuery_Stop();
+
+ ListView_SetItemCount(resultlist.getwnd(),0);
+ ListView_SetItemCount(resultlist.getwnd(),itemCache.Size);
+ if (itemCache.Size>0) ListView_RedrawItems(resultlist.getwnd(),0,itemCache.Size-1);
+
+ if (m_lv_last_topidx)
+ {
+ ListView_EnsureVisible(resultlist.getwnd(),m_lv_last_topidx,FALSE);
+ m_lv_last_topidx=0;
+ }
+
+ unsigned int total_plays=0;
+ int x;
+ for (x = 0; x < itemCache.Size; x ++)
+ {
+ total_plays += itemCache.Items[x].playcnt;
+ }
+
+ int total_length_s = (int)lParam & 0x7FFFFFFF;
+ int uncert=(int)(lParam>>31);
+ wchar_t buf[1024] = {0}, itemStr[16] = {0}, playStr[16] = {0};
+
+ StringCchPrintfW(buf, 1024,
+ L"%d %s, %u %s ",
+ itemCache.Size,
+ WASABI_API_LNGSTRINGW_BUF(itemCache.Size==1?IDS_ITEM:IDS_ITEMS,itemStr,16),
+ total_plays,
+ WASABI_API_LNGSTRINGW_BUF(total_plays==1?IDS_PLAY:IDS_PLAYS,playStr,16));
+
+ if (total_length_s < 60*60)
+ {
+ StringCchPrintfW(buf+wcslen(buf), 64, L"[%s%u:%02u]",
+ uncert ? L"~" : L"", total_length_s / 60,
+ total_length_s % 60);
+ }
+ else if (total_length_s < 60*60*24)
+ {
+ StringCchPrintfW(buf+wcslen(buf), 64, L"[%s%u:%02u:%02u]",
+ uncert ? L"~" : L"", total_length_s / 60 / 60,
+ (total_length_s / 60) % 60, total_length_s % 60);
+ }
+ else
+ {
+ wchar_t days[16] = {0};
+ int total_days = total_length_s / (60 * 60 * 24);
+ total_length_s -= total_days * 60 * 60 * 24;
+ StringCchPrintfW(buf+wcslen(buf), 64,
+ L"[%s%u %s+%u:%02u:%02u]",
+ uncert ? L"~" : L"", total_days,
+ WASABI_API_LNGSTRINGW_BUF(total_days == 1 ? IDS_DAY : IDS_DAYS, days, 16),
+ total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ }
+
+ SetDlgItemTextW(hwndDlg,IDC_MEDIASTATUS,buf);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (resultlist.getwnd())
+ {
+ g_config->WriteInt(L"recent_col_lp", resultlist.GetColumnWidth(0));
+ g_config->WriteInt(L"recent_col_count", resultlist.GetColumnWidth(1));
+ g_config->WriteInt(L"recent_col_title", resultlist.GetColumnWidth(2));
+ g_config->WriteInt(L"recent_col_len", resultlist.GetColumnWidth(3));
+ g_config->WriteInt(L"recent_col_filename", resultlist.GetColumnWidth(4));
+ g_config->WriteInt(L"recent_col_offset", resultlist.GetColumnWidth(5));
+ }
+
+ History_SaveLastQuery(hwndDlg);
+
+ if (g_table_dirty && g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ emptyRecentRecordList(&itemCache);
+ free(itemCache.Items);
+ itemCache.Items=0;
+ itemCache.Alloc=0;
+ itemCache.Size=0;
+
+ m_hwnd=0;
+ g_q.Set(L"");
+ WASABI_API_APP->app_removeAccelerators(hwndDlg);
+ break;
+
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_QUICKSEARCH|DCW_SUNKENBORDER, IDC_LIST2|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 1 + !!IsWindowVisible(GetDlgItem(hwndDlg, IDC_QUICKSEARCH)));
+ }
+ return 0;
+
+ case WM_APP+1:
+ history_bgQuery((int)lParam);
+ break;
+
+ case WM_ML_CHILDIPC:
+ if(lParam == ML_CHILDIPC_GO_TO_SEARCHBAR)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg,IDC_QUICKSEARCH));
+ }
+ else if (lParam == ML_CHILDIPC_REFRESH_SEARCH)
+ {
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ }
+ break;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_LIST2) // media view
+ {
+ if (l->code == NM_DBLCLK)
+ {
+ playFiles((!!g_config->ReadInt(L"enqueuedef", 0)) ^ (!!(GetAsyncKeyState(VK_SHIFT)&0x8000)),0);
+ }
+ else if (l->code == LVN_ODFINDITEMW) // yay we find an item (for kb shortcuts)
+ {
+ if (history_bgThread_Handle) return 0;
+ NMLVFINDITEMW *t = (NMLVFINDITEMW *)lParam;
+ int i=t->iStart;
+ if (i >= itemCache.Size) i=0;
+
+ int cnt=itemCache.Size-i;
+ if (t->lvfi.flags & LVFI_WRAP) cnt+=i;
+
+ int by = g_config->ReadInt(L"recent_sort_by", HISTORY_SORT_LASTPLAYED);
+
+ while (cnt-->0)
+ {
+ historyRecord *thisitem=itemCache.Items + i;
+ wchar_t tmp[128] = {0};
+ const wchar_t *name=0;
+
+ switch (by)
+ {
+ case HISTORYVIEW_COL_FILENAME:
+ name=thisitem->filename;
+ if (!wcsstr(name,L"://"))
+ {
+ while (name && *name) name++;
+ while (name && name >= thisitem->filename && *name != '/' && *name != '\\') name--;
+ if (name) name++;
+ }
+ break;
+ case HISTORYVIEW_COL_TITLE: name=thisitem->title; break;
+ case HISTORYVIEW_COL_LASTPLAYED:
+ tmp[0]=0;
+ if (thisitem->lastplayed > 0)
+ {
+ __time64_t timev = thisitem->lastplayed;
+ MakeDateStringW(timev, tmp, ARRAYSIZE(tmp));
+ }
+ name=tmp;
+ break;
+ case HISTORYVIEW_COL_PLAYCOUNT:
+ StringCchPrintfW(tmp,128,L"%u",thisitem->playcnt);
+ name=tmp;
+ break;
+ case HISTORYVIEW_COL_LENGTH:
+ tmp[0]=0;
+ if (thisitem->length >= 0) StringCchPrintfW(tmp,128,L"%d:%02d",thisitem->length/60,thisitem->length%60);
+ name=tmp;
+ break;
+ case HISTORYVIEW_COL_OFFSET:
+ tmp[0]=0;
+ if (thisitem->offset > 0) StringCchPrintfW(tmp,128,L"%d:%02d",(thisitem->offset/1000)/60,(thisitem->offset/1000)%60);
+ name=tmp;
+ break;
+ }
+
+ if (!name) name=L"";
+ else SKIP_THE_AND_WHITESPACE(name)
+
+ if (t->lvfi.flags & (4|LVFI_PARTIAL))
+ {
+ if (!_wcsnicmp(name,t->lvfi.psz,lstrlenW(t->lvfi.psz)))
+ {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else if (t->lvfi.flags & LVFI_STRING)
+ {
+ if (!_wcsicmp(name,t->lvfi.psz))
+ {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else
+ {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ if (++i == itemCache.Size) i=0;
+ }
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ else if (l->code == LVN_GETDISPINFOW)
+ {
+ NMLVDISPINFOW *lpdi = (NMLVDISPINFOW*) lParam;
+ int item=lpdi->item.iItem;
+
+ if (history_bgThread_Handle)
+ {
+ if (!item && lpdi->item.iSubItem == 0 && lpdi->item.mask & LVIF_TEXT)
+ {
+ static char bufpos;
+ static char chars[4]={'/','-','\\','|'};
+ StringCchPrintfW(lpdi->item.pszText,lpdi->item.cchTextMax,
+ L"%s %c",WASABI_API_LNGSTRINGW(IDS_SCANNING),
+ chars[bufpos++&3]);
+ return 0;
+ }
+ }
+
+ if (item < 0 || item >= itemCache.Size) return 0;
+
+ historyRecord *thisitem = itemCache.Items + item;
+
+ if (lpdi->item.mask & (LVIF_TEXT|/*LVIF_IMAGE*/0)) // we can always do images too :)
+ {
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ wchar_t tmpbuf[128] = {0};
+ const wchar_t *nameptr=0;
+ switch (lpdi->item.iSubItem)
+ {
+ case HISTORYVIEW_COL_FILENAME:
+ nameptr=thisitem->filename;
+ if (!wcsstr(nameptr,L"://"))
+ {
+ while (nameptr && *nameptr) nameptr++;
+ while (nameptr && nameptr >= thisitem->filename && *nameptr != L'/' && *nameptr != L'\\') nameptr--;
+ if (nameptr) nameptr++;
+ }
+ break;
+ case HISTORYVIEW_COL_TITLE: nameptr=thisitem->title; break;
+ case HISTORYVIEW_COL_LASTPLAYED:
+ if (thisitem->lastplayed > 0)
+ {
+ __time64_t timev = thisitem->lastplayed;
+ MakeDateStringW(timev, tmpbuf, ARRAYSIZE(tmpbuf));
+ }
+ nameptr=tmpbuf;
+ break;
+ case HISTORYVIEW_COL_PLAYCOUNT:
+ StringCchPrintfW(tmpbuf,128,L"%u",thisitem->playcnt);
+ nameptr=tmpbuf;
+ break;
+ case HISTORYVIEW_COL_LENGTH:
+ if (thisitem->length >= 0)
+ StringCchPrintfW(tmpbuf,128,L"%d:%02d",thisitem->length/60,thisitem->length%60);
+ nameptr=tmpbuf;
+ break;
+ case HISTORYVIEW_COL_OFFSET:
+ if (thisitem->offset > 0)
+ StringCchPrintfW(tmpbuf,128,L"%d:%02d",(thisitem->offset/1000)/60,(thisitem->offset/1000)%60);
+ nameptr=tmpbuf;
+ break;
+ }
+ if (nameptr) lstrcpynW(lpdi->item.pszText,nameptr,lpdi->item.cchTextMax);
+ else lpdi->item.pszText[0]=0;
+ }
+ // if(lpdi->item.mask & LVIF_IMAGE)
+ } // bother
+ return 0;
+ } // LVN_GETDISPINFO
+ else if (l->code == LVN_COLUMNCLICK)
+ {
+ NMLISTVIEW *p=(NMLISTVIEW*)lParam;
+ int l_sc=g_config->ReadInt(L"recent_sort_by",HISTORY_SORT_LASTPLAYED);
+ int l_sd=g_config->ReadInt(L"recent_sort_dir",0);
+ if (p->iSubItem == l_sc) l_sd=!l_sd;
+ else { l_sd=0; l_sc=p->iSubItem; }
+
+ g_config->WriteInt(L"recent_sort_by",l_sc);
+ g_config->WriteInt(L"recent_sort_dir",l_sd);
+
+ mediaLibrary.ListViewSort(resultSkin, l_sc, l_sd);
+ sortResults(&itemCache,
+ g_config->ReadInt(L"recent_sort_by",HISTORY_SORT_LASTPLAYED),
+ g_config->ReadInt(L"recent_sort_dir",0));
+ ListView_SetItemCount(resultlist.getwnd(),0);
+ ListView_SetItemCount(resultlist.getwnd(),itemCache.Size);
+ ListView_RedrawItems(resultlist.getwnd(),0,itemCache.Size-1);
+ }
+ else if (l->code == LVN_BEGINDRAG)
+ {
+ SetCapture(hwndDlg);
+ }
+ else if (l->code == LVN_ITEMCHANGED)
+ {
+ history_ManageButtons(hwndDlg);
+ }
+ }
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ history_UpdateButtonText(hwndDlg, (int)wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+ }
+ return FALSE;
+}
+
+void nukeHistory(HWND hwndDlg)
+{
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(hwndDlg, WASABI_API_LNGSTRINGW(IDS_REMOVE_ALL_HISTORY),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,titleStr,32),
+ MB_YESNO | MB_ICONQUESTION) == IDYES)
+ {
+ closeDb();
+
+ wchar_t tmp[MAX_PATH] = {0};
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\recent.dat", g_tableDir);
+ DeleteFileW(tmp);
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\recent.idx", g_tableDir);
+ DeleteFileW(tmp);
+
+ openDb();
+
+ // trigger a refresh of the current view
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+}
+
+BOOL CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a= (BOOL)dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DB_ERROR),
+ (wchar_t*)WASABI_API_LOADRESFROMFILEW(TEXT("TEXT"), MAKEINTRESOURCE((nde_error ? IDR_NDE_ERROR : IDR_DB_ERROR)), 0));
+
+ if (nde_error)
+ DestroyWindow(GetDlgItem(hwndDlg, IDC_RESET_DB_ON_ERROR));
+
+ FLICKERFIX ff;
+ INT index;
+ INT ffcl[] = { IDC_DB_ERROR,
+ IDC_RESET_DB_ON_ERROR,
+ };
+
+ ff.mode = FFM_ERASEINPAINT;
+ for (index = 0; index < (sizeof(ffcl) / sizeof(INT)); index++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]);
+ if (IsWindow(ff.hwnd))
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+ }
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.hwndToSkin = hwndDlg;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_RESET_DB_ON_ERROR)
+ {
+ nukeHistory(hwndDlg);
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows2(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER+66:
+ if (wParam == -1)
+ {
+ LayoutWindows2(hwndDlg, TRUE);
+ }
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ dialogSkinner.Draw(hwndDlg, 0, 0);
+ }
+ return 0;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ }
+ return FALSE;
+}
+
+void history_cleanupifnecessary()
+{
+ if (!g_table) return;
+
+ time_t now=time(NULL);
+
+ // if we've done it in the last 8 hours, don't do it again!
+ if (now < g_config->ReadInt(L"recent_limitlt",0) + 8*60*60) return;
+
+ // time to cleanup
+ int limit_d=g_config->ReadInt(L"recent_limitd",1);
+ int limit_dn=g_config->ReadInt(L"recent_limitnd",30);
+
+ if (!limit_d || limit_dn < 1) return;
+
+ g_config->WriteInt(L"recent_limitlt",(int)now);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ wchar_t str[512] = {0};
+ StringCchPrintfW(str,512,L"lastplay < [%d days ago]",limit_dn);
+ NDE_Scanner_Query(s, str);
+ NDE_Scanner_First(s);
+ for (;;)
+ {
+ if (!NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_LASTPLAYED)) break;
+
+ NDE_Scanner_Edit(s);
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty)
+ {
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ NDE_Table_Compact(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+}
+
+void history_onFile(const wchar_t *fn, int offset)
+{
+ if (!fn || fn && !*fn) return;
+
+ int isstream=!!wcsstr(fn,L"://");
+ if (isstream)
+ {
+ if (g_config->ReadInt(L"recent_track",1)&2) return;
+ }
+ else
+ {
+ if (!(g_config->ReadInt(L"recent_track",1)&1)) return;
+ }
+
+ if (!g_table && !openDb()) return;
+
+ const wchar_t *filename = fn;
+
+ int was_querying=0;
+ if (history_bgThread_Handle)
+ {
+ history_bgQuery_Stop();
+ was_querying=1;
+ }
+ KillTimer(m_hwnd,123);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ wchar_t filename2[2048] = {0}; // full lfn path if set
+ makeFilename2(filename,filename2,ARRAYSIZE(filename2));
+ int found=0;
+
+ if (filename2[0])
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME, FIRST_RECORD, filename2)) found = 2;
+ }
+ if (!found)
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME, FIRST_RECORD, filename)) found = 1;
+ }
+
+ int cnt=0;
+ if (found)
+ {
+ NDE_Scanner_Edit(s);
+ if (found == 1 && filename2[0]) db_setFieldString(s,HISTORYVIEW_COL_FILENAME,filename2); // if we have a better filename, update it
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_PLAYCOUNT);
+ cnt = f?NDE_IntegerField_GetValue(f):0;
+ }
+ else
+ {
+ NDE_Scanner_New(s);
+ db_setFieldString(s, HISTORYVIEW_COL_FILENAME, filename2[0] ? filename2 : filename);
+
+ int plidx= (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETLISTPOS);
+ const wchar_t *ft=(const wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, plidx, IPC_GETPLAYLISTTITLEW);
+ if (!ft || (INT_PTR)ft == 1) ft=fn;
+ const wchar_t *ftp=ft;
+ int length= (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 1, IPC_GETOUTPUTTIME);
+
+ if (*ftp == '[' && (ftp=wcsstr(ftp,L"]")))
+ {
+ ftp++;
+ while (ftp && *ftp == ' ') ftp++;
+ if (ftp && !*ftp) ftp=ft;
+ }
+ else ftp=ft;
+
+ db_setFieldInt(s, HISTORYVIEW_COL_LENGTH, length);
+ db_setFieldString(s, HISTORYVIEW_COL_TITLE, ftp);
+ }
+
+ if (offset >= 0)
+ {
+ db_setFieldInt(s, HISTORYVIEW_COL_OFFSET, (offset > 0 ? offset : -1));
+ }
+ else
+ {
+ db_setFieldInt(s, HISTORYVIEW_COL_LASTPLAYED, (int)time(NULL));
+ db_setFieldInt(s, HISTORYVIEW_COL_PLAYCOUNT, cnt+1);
+ }
+
+ NDE_Scanner_Post(s);
+
+ NDE_Table_DestroyScanner(g_table, s);
+ NDE_Table_Sync(g_table);
+ g_table_dirty++;
+
+ // changed to save the history when updated to prevent it being
+ // lost but retains the 8hr cleanup, etc which is otherwise run
+ if (g_table_dirty > 100)
+ {
+ history_cleanupifnecessary();
+ }
+ if (g_table_dirty)
+ {
+ // and to keep existing behaviour for the dirty count, we
+ // ensure that even on saving we maintain the dirty count
+ closeDb(false);
+ openDb();
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (was_querying)
+ history_bgQuery();
+
+ if (IsWindow(m_hwnd))
+ {
+ m_lv_last_topidx=ListView_GetTopIndex(resultlist.getwnd());
+ SendMessage(m_hwnd,WM_APP+1,0,0);
+ }
+}
+
+int retrieve_offset(const wchar_t *fn)
+{
+ int offset = -1;
+
+ int isstream=!!wcsstr(fn,L"://");
+ if (isstream)
+ {
+ if (g_config->ReadInt(L"recent_track",1)&2) return offset;
+ }
+ else
+ {
+ if (!(g_config->ReadInt(L"recent_track",1)&1)) return offset;
+ }
+
+ if (!g_table && !openDb()) return offset;
+
+ const wchar_t *filename=fn;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ wchar_t filename2[2048] = {0}; // full lfn path if set
+ makeFilename2(filename,filename2,ARRAYSIZE(filename2));
+ int found=0;
+
+ if (filename2[0])
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,filename2)) found=2;
+ }
+ if (!found)
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,filename)) found=1;
+ }
+
+ if (found)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_OFFSET);
+ offset = f?NDE_IntegerField_GetValue(f):-1;
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+
+ return offset;
+}
+
+void fileInfoDialogs(HWND hwndParent)
+{
+ history_bgQuery_Stop();
+ int l=resultlist.GetCount(),i;
+ int needref=0;
+ for(i=0;i<l;i++)
+ {
+ if(!resultlist.GetSelected(i)) continue;
+ historyRecord *song=(historyRecord *)itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+
+ infoBoxParamW p;
+ p.filename=song->filename;
+ p.parent=hwndParent;
+ if (SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&p,IPC_INFOBOXW)) break;
+ needref=1;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s= NDE_Table_CreateScanner(g_table);
+ if (NDE_Scanner_LocateNDEFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,song->filename))
+ {
+ wchar_t ft[1024]={0};
+ basicFileInfoStructW bi = {0};
+ bi.filename=p.filename;
+ bi.length=-1;
+ bi.title=ft;
+ bi.titlelen=ARRAYSIZE(ft);
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&bi,IPC_GET_BASIC_FILE_INFOW);
+
+ db_setFieldInt(s,HISTORYVIEW_COL_LENGTH,bi.length);
+ db_setFieldString(s,HISTORYVIEW_COL_TITLE,ft);
+
+ NDE_Scanner_Post(s);
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ g_table_dirty++;
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ if (g_table_dirty && g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ if (needref)
+ {
+ SendMessage(hwndParent,WM_TIMER,UPDATE_QUERY_TIMER_ID,0);
+ }
+
+ MSG msg;
+ while(PeekMessage(&msg,NULL,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE)); //eat return
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_hotmixradio/api__ml_hotmixradio.h b/Src/Plugins/Library/ml_hotmixradio/api__ml_hotmixradio.h
new file mode 100644
index 00000000..527ac359
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/api__ml_hotmixradio.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_API_ML_HOTMIXRADIO_H
+#define NULLSOFT_API_ML_HOTMIXRADIO_H
+
+#include <api/service/waServiceFactory.h>
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#endif // !NULLSOFT_API_ML_HOTMIXRADIO_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_hotmixradio/listview.cpp b/Src/Plugins/Library/ml_hotmixradio/listview.cpp
new file mode 100644
index 00000000..a3ceb22c
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/listview.cpp
@@ -0,0 +1,128 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+
+#include <windows.h>
+#include <commctrl.h>
+#include "listview.h"
+
+#ifdef GEN_ML_EXPORTS
+#include "main.h" // for getting the font
+#include "config.h"
+#endif
+// bp Comment: all the calls beginning "ListView_" are
+// MACROs defined in commctrl.h
+
+void W_ListView :: AddCol (char *text, int w)
+{
+ LVCOLUMN lvc={0,};
+ lvc.mask = LVCF_TEXT|LVCF_WIDTH;
+ lvc.pszText = text;
+ if (w) lvc.cx=w;
+ ListView_InsertColumn (m_hwnd, m_col, &lvc);
+ m_col++;
+}
+
+int W_ListView::GetColumnWidth (int col)
+{
+ if (col < 0 || col >= m_col) return 0;
+ return ListView_GetColumnWidth (m_hwnd, col);
+}
+
+
+int W_ListView::GetParam (int p)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = p;
+ ListView_GetItem (m_hwnd, &lvi);
+ return lvi.lParam;
+}
+
+int W_ListView::InsertItem (int p, char *text, int param)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = p;
+ lvi.pszText = text;
+ lvi.cchTextMax=strlen (text);
+ lvi.lParam = param;
+ return ListView_InsertItem (m_hwnd, &lvi);
+}
+
+
+void W_ListView::SetItemText (int p, int si, char *text)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.iSubItem = si;
+ lvi.mask = LVIF_TEXT;
+ lvi.pszText = text;
+ lvi.cchTextMax = strlen (text);
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::SetItemParam (int p, int param)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.mask=LVIF_PARAM;
+ lvi.lParam=param;
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::refreshFont ()
+{
+ if (m_font)
+ {
+ DeleteFont (m_font);
+ SetWindowFont (m_hwnd, NULL, FALSE);
+ }
+ m_font = NULL;
+
+ HWND h;
+#ifdef GEN_ML_EXPORTS
+ h=g_hwnd;
+#else
+ h=m_libraryparent;
+#endif
+ if (h && m_allowfonts)
+ {
+ int a=SendMessage (h, WM_USER+0x1000 /*WM_ML_IPC*/,66, 0x0600 /*ML_IPC_SKIN_WADLG_GETFUNC*/);
+ if (a)
+ {
+ m_font= (HFONT)a;
+ SetWindowFont (m_hwnd, m_font, FALSE);
+ }
+ }
+ InvalidateRect (m_hwnd, NULL, TRUE);
+}
+
+void W_ListView::setallowfonts (int allow)
+{
+ m_allowfonts=allow;
+}
+
+void W_ListView::setwnd (HWND hwnd)
+{
+ m_hwnd = hwnd;
+ if (hwnd)
+ {
+ ListView_SetExtendedListViewStyle (hwnd, LVS_EX_FULLROWSELECT|LVS_EX_UNDERLINEHOT );
+ refreshFont ();
+ }
+}
diff --git a/Src/Plugins/Library/ml_hotmixradio/listview.h b/Src/Plugins/Library/ml_hotmixradio/listview.h
new file mode 100644
index 00000000..5938a4dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/listview.h
@@ -0,0 +1,144 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+#if 0
+#ifndef _LISTVIEW_H_
+#define _LISTVIEW_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+class W_ListView
+{
+public:
+ W_ListView()
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ }
+ W_ListView(HWND hwnd)
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ setwnd(hwnd);
+ }
+ ~W_ListView()
+ {
+ if (m_font) DeleteFont(m_font);
+ m_font=0;
+ }
+
+ void refreshFont();
+
+#ifndef GEN_ML_EXPORTS
+ void setLibraryParentWnd(HWND hwndParent)
+ {
+ m_libraryparent=hwndParent;
+ }// for Winamp Font getting stuff
+#endif
+ void setallowfonts(int allow=1);
+ void setwnd(HWND hwnd);
+ void AddCol(char *text, int w);
+ int GetCount(void)
+ {
+ return ListView_GetItemCount(m_hwnd);
+ }
+ int GetParam(int p);
+ void DeleteItem(int n)
+ {
+ ListView_DeleteItem(m_hwnd,n);
+ }
+ void Clear(void)
+ {
+ ListView_DeleteAllItems(m_hwnd);
+ }
+ int GetSelected(int x)
+ {
+ return(ListView_GetItemState(m_hwnd, x, LVIS_SELECTED) & LVIS_SELECTED)?1:0;
+ }
+
+ int GetSelectedCount()
+ {
+ return ListView_GetSelectedCount(m_hwnd);
+ }
+
+ int GetSelectionMark()
+ {
+ return ListView_GetSelectionMark(m_hwnd);
+ }
+ void SetSelected(int x)
+ {
+ ListView_SetItemState(m_hwnd,x,LVIS_SELECTED,LVIS_SELECTED);
+ }
+ int InsertItem(int p, char *text, int param);
+ void GetItemRect(int i, RECT *r)
+ {
+ ListView_GetItemRect(m_hwnd, i, r, LVIR_BOUNDS);
+ }
+ void SetItemText(int p, int si, char *text);
+ void SetItemParam(int p, int param);
+
+ void GetText(int p, int si, char *text, int maxlen)
+ {
+ ListView_GetItemText(m_hwnd, p, si, text, maxlen);
+ }
+ int FindItemByParam(int param)
+ {
+ LVFINDINFO fi={LVFI_PARAM,0,param};
+ return ListView_FindItem(m_hwnd,-1,&fi);
+ }
+ int FindItemByPoint(int x, int y)
+ {
+ int l=GetCount();
+ for (int i=0;i<l;i++)
+ {
+ RECT r;
+ GetItemRect(i, &r);
+ if (r.left<=x && r.right>=x && r.top<=y && r.bottom>=y) return i;
+ }
+ return -1;
+ }
+ int GetColumnWidth(int col);
+ HWND getwnd(void)
+ {
+ return m_hwnd;
+ }
+
+protected:
+ HWND m_hwnd;
+ HFONT m_font;
+ int m_col;
+ int m_allowfonts;
+#ifndef GEN_ML_EXPORTS
+ HWND m_libraryparent;
+#endif
+};
+
+#endif//_LISTVIEW_H_
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_hotmixradio/main.cpp b/Src/Plugins/Library/ml_hotmixradio/main.cpp
new file mode 100644
index 00000000..5d153ff5
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/main.cpp
@@ -0,0 +1,111 @@
+#define PLUGIN_VERSION L"0.1"
+
+#include <strsafe.h>
+
+#include "Main.h"
+#include "../nu/AutoWide.h"
+
+#include "../../General/gen_ml/menu.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+#include "../WAT/WAT.h"
+
+static int Init();
+static void Quit();
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_hotmixradio.dll)",
+ Init,
+ Quit,
+ hotmixradio_pluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+int hotmixradio_treeItem = 0;
+
+HCURSOR hDragNDropCursor;
+C_Config *g_config = 0;
+WNDPROC waProc = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_application *WASABI_API_APP = 0;
+
+static DWORD WINAPI wa_newWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
+{
+ if ( waProc )
+ return (DWORD)CallWindowProcW( waProc, hwnd, msg, wParam, lParam );
+ else
+ return (DWORD)DefWindowProc( hwnd, msg, wParam, lParam );
+}
+
+int Init()
+{
+ waProc = (WNDPROC)SetWindowLongPtrW( plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc );
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( AnonymousStatsGUID );
+ if ( sf )
+ AGAVE_API_STATS = reinterpret_cast<api_stats *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application *>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlHotmixRadioLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_HOTMIXRADIO ), PLUGIN_VERSION );
+ plugin.description = (char *)szDescription;
+
+ wchar_t inifile[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins", inifile, MAX_PATH );
+ CreateDirectoryW( inifile, NULL );
+
+ mediaLibrary.BuildPath( L"Plugins\\gen_ml.ini", inifile, MAX_PATH );
+ g_config = new C_Config( inifile );
+
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.pszText = WASABI_API_LNGSTRINGW( IDS_HOTMIXRADIO );
+ nis.item.pszInvariant = L"Hotmix Radio";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.iSelectedImage = nis.item.iImage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_HOTMIXRADIO );
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = { sizeof( NAVITEM ),0,NIMF_ITEMID, };
+ nvItem.hItem = MLNavCtrl_InsertItem( plugin.hwndLibraryParent, &nis );
+ MLNavItem_GetInfo( plugin.hwndLibraryParent, &nvItem );
+ hotmixradio_treeItem = nvItem.id;
+
+
+ return 0;
+}
+
+void Quit()
+{
+ if ( g_config != 0 )
+ delete g_config;
+}
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_hotmixradio/main.h b/Src/Plugins/Library/ml_hotmixradio/main.h
new file mode 100644
index 00000000..4bd45b41
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/main.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_HOTMIXRADIO_MAIN_H
+#define NULLSOFT_HOTMIXRADIO_MAIN_H
+
+#include <windows.h>
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "resource.h"
+#include <windowsx.h>
+#include "resource.h"
+#include "../winamp/wa_ipc.h"
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../Plugins/General/gen_ml/config.h"
+#include "api__ml_hotmixradio.h"
+
+
+#define HOTMIXRADIO_BASE_URL L"https://hotmixradio.com/?mtm_source=winamp_desktop_player"
+
+extern winampMediaLibraryPlugin plugin;
+INT_PTR hotmixradio_pluginMessageProc( int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3 );
+extern int hotmixradio_treeItem;
+
+
+
+#endif // !NULLSOFT_HOTMIXRADIO_MAIN_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.rc b/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.rc
new file mode 100644
index 00000000..7f8de7cf
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.rc
@@ -0,0 +1,128 @@
+// 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 (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+#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_VIEW_HOTMIXRADIO DIALOGEX 0, 0, 184, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_HOTMIXRADIO,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_TABSTOP,0,0,184,79
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_HOTMIXRADIO BITMAP "resources\\HOTMIXRADIO_16x16.bmp"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_VIEW_HOTMIXRADIO, DIALOG
+ BEGIN
+ RIGHTMARGIN, 123
+ BOTTOMMARGIN, 61
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_VIEW_HOTMIXRADIO AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_HOTMIXRADIO "Nullsoft Hotmix Radio v%s"
+ 65535 "{4959A8EF-B760-4C33-B18C-715EF018B365}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_HOTMIXRADIO "Hotmix Radio"
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj b/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj
new file mode 100644
index 00000000..f058a009
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj
@@ -0,0 +1,313 @@
+<?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>{C04F4352-5CB9-4D6B-800C-E9B2B1C821CF}</ProjectGuid>
+ <RootNamespace>ml_hotmixradio</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <VcpkgEnabled>false</VcpkgEnabled>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_HOTMIXRADIO_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_HOTMIXRADIO_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_HOTMIXRADIO_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_HOTMIXRADIO_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <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="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="view.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="api__ml_hotmixradio.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_hotmixradio.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\hotmixradio_16x16.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\WAT\WAT.vcxproj">
+ <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</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_hotmixradio/ml_hotmixradio.vcxproj.filters b/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj.filters
new file mode 100644
index 00000000..652362e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/ml_hotmixradio.vcxproj.filters
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="view.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_hotmixradio.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{b4c6c983-ad68-4d2d-a542-7f1bf6b1c155}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{f072eba6-51ff-4c40-b136-bf3c79f3e120}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b04fff17-1af4-4d74-b609-169c1a32b097}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{56f5b459-ac0f-423d-b448-563135bf4bd8}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{0f139e2a-24a0-4f2f-8ef5-9f429704fc09}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{33c224c5-9689-4e39-9fc1-95b30d7f72fd}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{2a93abc4-8246-41d7-b991-d111d36d5353}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{b1c88e02-70f6-4a54-80fc-60a246835520}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_hotmixradio.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\hotmixradio_16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_hotmixradio/resource.h b/Src/Plugins/Library/ml_hotmixradio/resource.h
new file mode 100644
index 00000000..6598c6c3
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/resource.h
@@ -0,0 +1,20 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_hotmixradio.rc
+//
+#define IDS_HOTMIXRADIO 1
+#define IDD_VIEW_HOTMIXRADIO 102
+#define IDC_LIST_HOTMIXRADIO 1001
+#define IDB_TREEITEM_HOTMIXRADIO 1003
+#define IDS_NULLSOFT_HOTMIXRADIO 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 114
+#define _APS_NEXT_COMMAND_VALUE 40013
+#define _APS_NEXT_CONTROL_VALUE 1010
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_hotmixradio/resources/HOTMIXRADIO_16x16.bmp b/Src/Plugins/Library/ml_hotmixradio/resources/HOTMIXRADIO_16x16.bmp
new file mode 100644
index 00000000..036407bf
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/resources/HOTMIXRADIO_16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_hotmixradio/version.rc2 b/Src/Plugins/Library/ml_hotmixradio/version.rc2
new file mode 100644
index 00000000..cd393c2b
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 0,1,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", "0,1,0,0"
+ VALUE "InternalName", "Nullsoft Hotmix Radio Library"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_hotmixradio.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_hotmixradio/view.cpp b/Src/Plugins/Library/ml_hotmixradio/view.cpp
new file mode 100644
index 00000000..3d55daaa
--- /dev/null
+++ b/Src/Plugins/Library/ml_hotmixradio/view.cpp
@@ -0,0 +1,388 @@
+#include <strsafe.h>
+
+#include "main.h"
+
+#include "../nu/DialogSkinner.h"
+#include "../nu/ListView.h"
+
+#include "../../General/gen_ml/ml_ipc.h"
+#include "../../General/gen_ml/menu.h"
+
+#include "../WAT/WAT.h"
+
+INT_PTR CALLBACK view_HOTMIXRADIODialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static W_ListView m_hotmixradio_list;
+static HWND m_headerhwnd, m_hwnd;
+extern C_Config *g_config;
+extern char *g_ext_list;
+static int customAllowed;
+static viewButtons view;
+
+C_Config *g_wa_config = 0;
+char *g_ext_list = 0;
+
+// used for the send-to menu bits
+static INT_PTR IPC_LIBRARY_SENDTOMENU;
+static librarySendToMenuStruct s;
+BOOL myMenu = FALSE;
+
+extern HMENU g_context_menus, g_context_menus2;
+extern HCURSOR hDragNDropCursor;
+
+
+static int HOTMIXRADIO_contextMenu( INT_PTR param1, HWND hHost, POINTS pts )
+{
+ return TRUE;
+}
+
+static int pluginHandleIpcMessage(int msg, int param)
+{
+ return (int)SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, param, msg );
+}
+
+INT_PTR hotmixradio_pluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_NO_CONFIG)
+ {
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_TREE_ONCREATEVIEW && param1 == hotmixradio_treeItem )
+ {
+ return (INT_PTR)WASABI_API_CREATEDIALOGW(IDD_VIEW_HOTMIXRADIO, (HWND)param2, view_HOTMIXRADIODialogProc);
+ }
+ else if (message_type == ML_MSG_NAVIGATION_CONTEXTMENU)
+ {
+ return HOTMIXRADIO_contextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT || message_type == ML_MSG_TREE_ONDROPTARGET)
+ {
+ // set with droptarget defaults =)
+ UINT_PTR type = 0,data = 0;
+
+ if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)hotmixradio_pluginMessageProc) return 0;
+
+ type=(int)param1;
+ data = (int)param2;
+ }
+ else
+ {
+ if (param1 != hotmixradio_treeItem ) return 0;
+
+ type=(int)param2;
+ data=(int)param3;
+
+ if (!data)
+ {
+ return (type == ML_TYPE_ITEMRECORDLISTW || type == ML_TYPE_ITEMRECORDLIST ||
+ type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES ||
+ type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW ||
+ type == ML_TYPE_CDTRACKS ||
+ type == ML_TYPE_PLAYLIST || type == ML_TYPE_PLAYLISTS) ? 1 : -1;
+ }
+ }
+ }
+
+
+ return 0;
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, GROUP_MAIN, IDC_LIST_HOTMIXRADIO
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if ( rc.right == rc.left || rc.bottom == rc.top )
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_LIST_HOTMIXRADIO:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1),
+ rg.right - rg.left + WASABI_API_APP->getScaleY(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for(pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static BOOL HOTMIXRADIO_OnDisplayChange()
+{
+ ListView_SetTextColor(m_hotmixradio_list.getwnd(),dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(m_hotmixradio_list.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(m_hotmixradio_list.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+ m_hotmixradio_list.SetFont(dialogSkinner.GetFont());
+ LayoutWindows(m_hwnd, TRUE);
+ return 0;
+}
+
+
+enum
+{
+ BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+static BOOL HOTMIXRADIO_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
+{
+ return FALSE;
+}
+
+static BOOL HOTMIXRADIO_OnDestroy(HWND hwnd)
+{
+ m_hwnd = 0;
+
+ return FALSE;
+}
+
+static BOOL HOTMIXRADIO_OnNotify(HWND hwnd, NMHDR *notification)
+{
+ if (notification->idFrom== IDC_LIST_HOTMIXRADIO )
+ {
+ if (notification->code == LVN_BEGINDRAG)
+ {
+ SetCapture(hwnd);
+ }
+ }
+ return FALSE;
+}
+
+static BOOL HOTMIXRADIO_OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
+{
+ m_hwnd = hwndDlg;
+ m_hotmixradio_list.setwnd(GetDlgItem(hwndDlg, IDC_LIST_HOTMIXRADIO ));
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+
+ HOTMIXRADIO_OnDisplayChange();
+
+ m_headerhwnd = ListView_GetHeader( m_hotmixradio_list.getwnd() );
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ customAllowed = FALSE;
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ m.hwndToSkin = hwndDlg;
+
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+
+
+ ShellExecuteW( NULL, L"open", HOTMIXRADIO_BASE_URL, NULL, NULL, SW_SHOWNORMAL );
+
+ delete g_wa_config;
+
+ SetTimer( hwndDlg, 100, 15, NULL );
+
+
+ return TRUE;
+}
+
+INT_PTR CALLBACK view_HOTMIXRADIODialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam);
+ if (a) return a;
+
+ switch(uMsg)
+ {
+ HANDLE_MSG(hwndDlg, WM_INITDIALOG, HOTMIXRADIO_OnInitDialog);
+ HANDLE_MSG(hwndDlg, WM_COMMAND, HOTMIXRADIO_OnCommand);
+ HANDLE_MSG(hwndDlg, WM_DESTROY, HOTMIXRADIO_OnDestroy);
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST_HOTMIXRADIO |DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ myMenu = TRUE;
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ myMenu = FALSE;
+ }
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ return HOTMIXRADIO_OnDisplayChange();
+
+ case WM_NOTIFY:
+ return HOTMIXRADIO_OnNotify(hwndDlg, (LPNMHDR)lParam);
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/ImportPlaylists.cpp b/Src/Plugins/Library/ml_impex/ImportPlaylists.cpp
new file mode 100644
index 00000000..ccb75281
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ImportPlaylists.cpp
@@ -0,0 +1,404 @@
+#include "api__ml_impex.h"
+#include "../xml/obj_xml.h"
+#include <map>
+#include <bfc/string/url.h>
+#include "importer.h"
+#include "resource.h"
+#include "../plist/loader.h"
+#include "../playlist/ifc_playlist.h"
+#include "../Winamp/wa_ipc.h"
+#include <shlwapi.h>
+
+struct iTunesFileInfo
+{
+ iTunesFileInfo(const wchar_t *_filename, uint64_t _length)
+ {
+ filename = _wcsdup(_filename);
+ length = _length;
+ }
+ ~iTunesFileInfo()
+ {
+ free(filename);
+ }
+ wchar_t *filename;
+ uint64_t length;
+};
+typedef std::map<int64_t, iTunesFileInfo*> FilesList;
+extern winampMediaLibraryPlugin plugin;
+int Load(const wchar_t *filename, obj_xml *parser);
+
+class PlistPlaylist : public ifc_playlist
+{
+public:
+ PlistPlaylist(const plistArray *_items, FilesList &_files) : items(_items), files(_files)
+ {
+ length_sum = 0;
+ }
+ size_t GetNumItems();
+ size_t GetItem(size_t item, wchar_t *filename, size_t filenameCch);
+ int GetItemLengthMilliseconds(size_t item);
+ uint64_t length_sum;
+protected:
+ RECVS_DISPATCH;
+ const plistArray *items;
+ FilesList &files;
+};
+
+size_t PlistPlaylist::GetNumItems()
+{
+ return items->getNumItems();
+}
+
+size_t PlistPlaylist::GetItem(size_t item, wchar_t *filename, size_t filenameCch)
+{
+ plistDict *item_dict = (plistDict *)items->enumItem((int)item);
+ if (item_dict)
+ {
+ plistKey *id_key = item_dict->getKey(L"Track ID");
+ if (id_key)
+ {
+ plistInteger *id_data = (plistInteger *)id_key->getData();
+ if (id_data)
+ {
+ int64_t key = id_data->getValue();
+ iTunesFileInfo *info = files[key];
+ if (info)
+ {
+ const wchar_t *track_name = info->filename;
+ if (track_name)
+ {
+ length_sum += info->length;
+ StringCchCopyW(filename, filenameCch, track_name);
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+int PlistPlaylist::GetItemLengthMilliseconds(size_t item)
+{
+ plistDict *item_dict = (plistDict *)items->enumItem((int)item);
+ if (item_dict)
+ {
+ plistKey *id_key = item_dict->getKey(L"Track ID");
+ if (id_key)
+ {
+ plistInteger *id_data = (plistInteger *)id_key->getData();
+ if (id_data)
+ {
+ int64_t key = id_data->getValue();
+ iTunesFileInfo *info = files[key];
+ if (info)
+ {
+ return (int)info->length;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+#define CBCLASS PlistPlaylist
+START_DISPATCH;
+CB(IFC_PLAYLIST_GETNUMITEMS, GetNumItems)
+CB(IFC_PLAYLIST_GETITEM, GetItem)
+CB(IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds)
+END_DISPATCH;
+#undef CBCLASS
+
+static bool GetInteger(const plistDict *dict, const wchar_t *key_name, int64_t *int_val)
+{
+ plistKey *key = dict->getKey(key_name);
+ if (!key)
+ return false;
+
+ plistData *data = key->getData();
+ if (!data)
+ return false;
+
+ if (data->getType() != PLISTDATA_INTEGER)
+ return false;
+
+ plistInteger *data_int = static_cast<plistInteger *>(data);
+ *int_val = data_int->getValue();
+ return true;
+}
+
+static bool GetString(const plistDict *dict, const wchar_t *key_name, const wchar_t **str_val)
+{
+ plistKey *key = dict->getKey(key_name);
+ if (!key)
+ return false;
+
+ plistData *data = key->getData();
+ if (!data)
+ return false;
+
+ if (data->getType() != PLISTDATA_STRING)
+ return false;
+
+ plistString *data_str = static_cast<plistString *>(data);
+ *str_val = data_str->getString();
+ return true;
+}
+
+static bool GetArray(const plistDict *dict, const wchar_t *key_name, plistArray **array_val)
+{
+ plistKey *key = dict->getKey(key_name);
+ if (!key)
+ return false;
+
+ plistData *data = key->getData();
+ if (!data)
+ return false;
+
+ if (data->getType() != PLISTDATA_ARRAY)
+ return false;
+
+ *array_val = static_cast<plistArray *>(data);
+ return true;
+}
+
+static bool CheckDuplicatePlaylist(uint64_t playlist_id64, GUID &dup_guid)
+{
+ AGAVE_API_PLAYLISTS->Lock();
+ size_t numPlaylists = AGAVE_API_PLAYLISTS->GetCount();
+ uint64_t compare_id64=0;
+ for (size_t i=0;i!=numPlaylists;i++)
+ {
+ if (AGAVE_API_PLAYLISTS->GetInfo(i, api_playlists_iTunesID, &compare_id64, sizeof(compare_id64)) == API_PLAYLISTS_SUCCESS)
+ {
+ if (compare_id64 == playlist_id64)
+ {
+ dup_guid = AGAVE_API_PLAYLISTS->GetGUID(i);
+ AGAVE_API_PLAYLISTS->Unlock();
+ return true;
+ }
+ }
+ }
+ AGAVE_API_PLAYLISTS->Unlock();
+ return false;
+}
+
+enum
+{
+ DUPLICATE_PLAYLIST_SKIP,
+ DUPLICATE_PLAYLIST_REPLACE,
+ DUPLICATE_PLAYLIST_NEW,
+};
+
+static int PromptReplaceSkipNew(GUID &dup_guid)
+{
+ /* TODO:
+ * get name and stuff from api_playlists
+ * we'll need an HWND for the UI
+ * we'll need some passed-in state variable to remember "do for all" choice
+ */
+ return DUPLICATE_PLAYLIST_SKIP;
+}
+
+HINSTANCE cloud_hinst = 0;
+int IPC_GET_CLOUD_HINST = -1, IPC_GET_CLOUD_ACTIVE = -1;
+int cloudAvailable()
+{
+ if (IPC_GET_CLOUD_HINST == -1) IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_GET_CLOUD_ACTIVE == -1) IPC_GET_CLOUD_ACTIVE = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudActive", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+
+ return (/*0/*/!(!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))/**/);
+}
+
+static void AddPlaylist(plistDict *playlist, FilesList &files)
+{
+ const wchar_t *name;
+ const wchar_t *playlist_persistent_id;
+ int64_t playlist_id;
+ int64_t visible;
+ plistArray *items;
+ uint64_t playlist_id64;
+
+ if (GetString(playlist, L"Name", &name)
+ && GetArray(playlist, L"Playlist Items", &items)
+ && GetInteger(playlist, L"Playlist ID", &playlist_id)
+ && GetString(playlist, L"Playlist Persistent ID", &playlist_persistent_id)
+ && (!GetInteger(playlist, L"Visible", &visible) || visible))
+ {
+ playlist_id64 =_wcstoui64(playlist_persistent_id, 0, 16);
+ // see if it's already in the database
+
+ GUID dup_guid; // so we know the GUID we clash with, in case we want to replace it instead of skip it
+ if (playlist_id64 && CheckDuplicatePlaylist(playlist_id64, dup_guid))
+ {
+ switch(PromptReplaceSkipNew(dup_guid))
+ {
+ case DUPLICATE_PLAYLIST_SKIP:
+ break;
+ case DUPLICATE_PLAYLIST_REPLACE:
+ // TODO
+ break;
+ case DUPLICATE_PLAYLIST_NEW:
+ // TODO
+ break;
+ }
+ }
+ else
+ {
+ PlistPlaylist plist_playlist(items, files);
+
+ const wchar_t *user_folder = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t destination[MAX_PATH] = {0};
+ PathCombineW(destination, user_folder, L"plugins\\ml\\playlists");
+
+ wchar_t playlist_filename[MAX_PATH] = {0};
+ StringCbPrintfW(playlist_filename, sizeof(playlist_filename), L"i_%I64u.m3u8", playlist_id);
+ PathAppendW(destination, playlist_filename);
+
+ static wchar_t ml_ini_file[MAX_PATH] = {0};
+ if (!ml_ini_file[0]) lstrcpynW(ml_ini_file, (const wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW), MAX_PATH);
+ size_t cloud = (cloudAvailable() ? GetPrivateProfileIntW(L"gen_ml_config", L"cloud_always", 1, ml_ini_file) : 0);
+
+ AGAVE_API_PLAYLISTMANAGER->Save(destination, &plist_playlist);
+ AGAVE_API_PLAYLISTS->Lock();
+ int new_index = (!cloud ? (int)AGAVE_API_PLAYLISTS->AddPlaylist(destination, name) : (int)AGAVE_API_PLAYLISTS->AddCloudPlaylist(destination, name));
+ if (new_index >= 0)
+ {
+ uint32_t numItems = (uint32_t)plist_playlist.GetNumItems();
+ uint64_t totalLength = plist_playlist.length_sum/1000;
+ AGAVE_API_PLAYLISTS->SetInfo(new_index, api_playlists_totalTime, &totalLength, sizeof(totalLength));
+ AGAVE_API_PLAYLISTS->SetInfo(new_index, api_playlists_itemCount, &numItems, sizeof(numItems));
+ if (cloud) AGAVE_API_PLAYLISTS->SetInfo(new_index, api_playlists_cloud, &cloud, sizeof(cloud));
+ AGAVE_API_PLAYLISTS->SetInfo(new_index, api_playlists_iTunesID, &playlist_id64, sizeof(playlist_id64));
+ }
+ AGAVE_API_PLAYLISTS->Unlock();
+
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"ml_playlist_refresh", IPC_REGISTER_WINAMP_IPCMESSAGE));
+ }
+ }
+}
+
+void FixPath(const wchar_t *strdata, StringW &f);
+
+int ImportPlaylists(HWND parent, const wchar_t *library_file)
+{
+ FilesList files;
+ // create an XML parser
+ obj_xml *parser=0;
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(obj_xmlGUID);
+ if (factory)
+ parser = (obj_xml *)factory->getInterface();
+
+ if (parser)
+ {
+ // create status window
+ //HWND hwndDlg = WASABI_API_CREATEDIALOGW(IDD_INFODIALOG,plugin.hwndLibraryParent,import_dlgproc);
+
+ // init it
+ //ShowWindow(hwndDlg, SW_NORMAL);
+ //UpdateWindow(hwndDlg);
+
+ // create an iTunes XML library reader
+ plistLoader it;
+
+ // load the XML, this creates an iTunes DB in memory, and returns the root key
+ parser->xmlreader_open();
+ parser->xmlreader_registerCallback(L"plist\f*", &it);
+ Load(library_file, parser);
+ parser->xmlreader_unregisterCallback(&it);
+ parser->xmlreader_close();
+ plistKey *root_key = &it;
+
+ // show import progress controls
+ //ShowWindow(GetDlgItem(hwndDlg, IDC_PROCESSING_STATE), SW_HIDE);
+ //ShowWindow(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), SW_SHOWNORMAL);
+ //ShowWindow(GetDlgItem(hwndDlg, IDC_TRACKS), SW_SHOWNORMAL);
+ //UpdateWindow(hwndDlg);
+
+ // we start at the root key
+ if (root_key)
+ {
+ // the root key contains a dictionary
+ plistData *root_dict = root_key->getData();
+ if (root_dict && root_dict->getType() == PLISTDATA_DICT)
+ {
+ // that dictionary contains a number of keys, one of which contains a dictionary of tracks
+ plistKey *tracks_key = ((plistDict*)root_dict)->getKey(L"Tracks");
+ plistData *tracks_dict = tracks_key?tracks_key->getData():0;
+ if (tracks_dict && tracks_dict->getType() == PLISTDATA_DICT)
+ {
+ // we have the tracks dictionary ...
+ plistDict *tracks = (plistDict *)tracks_dict;
+ int n =tracks?tracks->getNumKeys():0;
+ // ... now enumerate tracks
+ for (int i=0;i<n;i++)
+ {
+ // each track is a key in the tracks dictionary, and contains a dictionary of properties
+ plistKey *track_key = tracks->enumKey(i);
+ plistData *track_dict = track_key->getData();
+ // prepare an item record
+
+ if (track_dict->getType() == PLISTDATA_DICT)
+ {
+ // we have the track's dictionary of properties...
+ plistDict *track = (plistDict *)track_dict;
+ int64_t id = 0;
+ const wchar_t *location = 0;
+ if (GetInteger(track, L"Track ID", &id) && GetString(track, L"Location", &location))
+ {
+ StringW f;
+ FixPath(location, f);
+
+ int64_t length = 0;
+ GetInteger(track, L"Total Time", &length);
+ // done
+ wchar_t *filename = _wcsdup(f);
+ files[id] = new iTunesFileInfo(filename, length);
+
+ // show progress
+ //SetDlgItemText(hwndDlg, IDC_TRACKS, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_TRACKS_IMPORTED_X), ++count));
+ //SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, (int)((double)count/n*100.0), 0);
+ //if (count % 10 == 0 || count == n)
+ // UpdateWindow(hwndDlg);
+ }
+ }
+ }
+ }
+
+ // ok we're done building the track list, now let's enumerate the playlists
+ plistKey *playlists_key = ((plistDict*)root_dict)->getKey(L"Playlists");
+ plistData *playlists_dict = playlists_key?playlists_key->getData():0;
+ if (playlists_dict && playlists_dict->getType() == PLISTDATA_ARRAY)
+ {
+ plistArray *playlists = (plistArray *)playlists_dict;
+ int n =playlists?playlists->getNumItems():0;
+ // ... now enumerate playlists
+ for (int i=0;i<n;i++)
+ {
+ // each playlist is a key in the playlists dictionary, and contains a dictionary of properties
+ plistData *playlist_dict = playlists->enumItem(i);
+ if (playlist_dict->getType() == PLISTDATA_DICT)
+ {
+ // we have the playlist's dictionary of properties...
+ plistDict *playlist = (plistDict *)playlist_dict;
+ AddPlaylist(playlist, files);
+ }
+ }
+ }
+ }
+ }
+ //DestroyWindow(hwndDlg);
+ factory->releaseInterface(parser);
+ }
+ else
+ return DISPATCH_FAILURE;
+
+ FilesList::iterator itr;
+ for (itr = files.begin(); itr!= files.end(); itr++)
+ {
+ iTunesFileInfo *info = itr->second;
+ delete info;
+ }
+ return DISPATCH_SUCCESS;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/ImporterAPI.cpp b/Src/Plugins/Library/ml_impex/ImporterAPI.cpp
new file mode 100644
index 00000000..3961b8ea
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ImporterAPI.cpp
@@ -0,0 +1,299 @@
+#include "ImporterAPI.h"
+#include "../xml/obj_xml.h"
+#include "importer.h"
+#include "resource.h"
+#include "api__ml_impex.h"
+#include "../plist/loader.h"
+#include <api/service/waservicefactory.h>
+#include <shlobj.h>
+
+extern winampMediaLibraryPlugin plugin;
+
+int Load(const wchar_t *filename, obj_xml *parser)
+{
+ if (!parser)
+ return 1; // no sense in continuing if there's no parser available
+
+ HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
+
+ if (file == INVALID_HANDLE_VALUE)
+ return 1;
+
+ while (true)
+ {
+ char data[1024] = {0};
+ DWORD bytesRead = 0;
+ if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead)
+ {
+ parser->xmlreader_feed(data, bytesRead);
+ }
+ else
+ break;
+ }
+
+ CloseHandle(file);
+ parser->xmlreader_feed(0, 0);
+ return 0;
+}
+
+static bool GetiTunesPreferencesPath(wchar_t *path, size_t path_cch)
+{
+ wchar_t appdata[MAX_PATH] = {0};
+ SHGetSpecialFolderPathW(NULL, appdata, CSIDL_APPDATA, FALSE);
+ StringCchPrintf(path, path_cch, L"%s\\Apple Computer\\iTunes\\iTunesPrefs.xml", appdata);
+ return true;
+}
+
+static bool GetiTunesLibraryPath(wchar_t *path, size_t path_cch)
+{
+ wchar_t itunes_pref[MAX_PATH] = {0};
+ if (GetiTunesPreferencesPath(itunes_pref, MAX_PATH))
+ {
+ plistLoader it;
+
+ obj_xml *parser=0;
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(obj_xmlGUID);
+ if (factory)
+ parser = (obj_xml *)factory->getInterface();
+
+ if (parser)
+ {
+ // load the XML, this creates an iTunes DB in memory, and returns the root key
+ parser->xmlreader_open();
+ parser->xmlreader_registerCallback(L"plist\f*", &it);
+ Load(itunes_pref, parser);
+ parser->xmlreader_unregisterCallback(&it);
+ parser->xmlreader_close();
+ plistKey *root_key = &it;
+ plistData *root_dict = root_key->getData();
+ if (root_dict)
+ {
+ plistKey *prefs_key = ((plistDict*)root_dict)->getKey(L"User Preferences");
+ if (prefs_key)
+ {
+ plistData *prefs_dict= prefs_key->getData();
+ if (prefs_dict)
+ {
+ plistKey *location_key = ((plistDict*)prefs_dict)->getKey(L"iTunes Library XML Location:1");
+ if (location_key)
+ {
+ plistData *location_data = location_key->getData();
+ if (location_data)
+ {
+ plistRaw *location_data_raw = (plistRaw *)location_data;
+ if (location_data_raw)
+ {
+ int size;
+ const wchar_t *mem = (const wchar_t *)location_data_raw->getMem(&size);
+ if (mem)
+ {
+ memcpy(path, mem, size);
+ path[size/2]=0;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+
+}
+
+// -----------------------------------------------------------------------
+// import status window proc
+// -----------------------------------------------------------------------
+static BOOL CALLBACK import_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ // show import progress controls
+ ShowWindow(GetDlgItem(hwndDlg, IDC_PROCESSING_STATE), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), SW_SHOWNORMAL);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_TRACKS), SW_SHOWNORMAL);
+
+ SetWindowText(hwndDlg,WASABI_API_LNGSTRINGW(IDS_IMPORTING_DATABASE));
+ SetDlgItemText(hwndDlg, IDC_PROCESSING_STATE, WASABI_API_LNGSTRINGW(IDS_LOADING_XML));
+ SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, 0, 0);
+
+ setDialogIcon(hwndDlg);
+
+ SetTimer(hwndDlg, 666, 250, 0);
+ SetForegroundWindow(hwndDlg);
+ return 0;
+ }
+
+ case WM_TIMER:
+ if (wParam == 666)
+ {
+ int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (progress[0] == -666)
+ {
+ KillTimer(hwndDlg, 666);
+ EndDialog(hwndDlg, 0);
+ }
+ else
+ {
+ // display progress
+ SetDlgItemText(hwndDlg, IDC_TRACKS, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_TRACKS_IMPORTED_X), progress[0]));
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, (int)((double)progress[0]/progress[1]*100.0), 0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ {
+ int wID = LOWORD(wParam);
+ switch (wID) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwndDlg, wID);
+ break;
+ }
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static DWORD CALLBACK ImportThread(LPVOID param)
+{
+ WASABI_API_DIALOGBOXPARAMW(IDD_INFODIALOG, NULL, import_dlgproc, (LPARAM)param);
+ return 0;
+}
+
+int ImporterAPI::ImportFromFile(HWND parent, const wchar_t *library_file)
+{
+ // create an XML parser
+ obj_xml *parser=0;
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(obj_xmlGUID);
+ if (factory)
+ parser = (obj_xml *)factory->getInterface();
+
+ if (parser)
+ {
+ // create status window
+ int progress[2] = {0};
+ DWORD threadId = 0;
+ HANDLE importThread = CreateThread(0, 0, ImportThread, progress, 0, &threadId);
+
+ // create an iTunes XML library reader
+ plistLoader it;
+
+ // load the XML, this creates an iTunes DB in memory, and returns the root key
+ parser->xmlreader_open();
+ parser->xmlreader_registerCallback(L"plist\f*", &it);
+ Load(library_file, parser);
+ parser->xmlreader_unregisterCallback(&it);
+ parser->xmlreader_close();
+ plistKey *root_key = &it;
+
+ // we start at the root key
+ if (root_key) {
+ // the root key contains a dictionary
+ plistData *root_dict = root_key->getData();
+ if (root_dict && root_dict->getType() == PLISTDATA_DICT) {
+ // that dictionary contains a number of keys, one of which contains a dictionary of tracks
+ plistKey *tracks_key = ((plistDict*)root_dict)->getKey(L"Tracks");
+ plistData *tracks_dict = tracks_key?tracks_key->getData():0;
+ if (tracks_dict && tracks_dict->getType() == PLISTDATA_DICT) {
+ // we have the tracks dictionary ...
+ plistDict *tracks = (plistDict *)tracks_dict;
+ progress[1]=tracks?tracks->getNumKeys():0;
+ // ... now enumerate tracks
+ for (int i=0;i<progress[1];i++) {
+ // each track is a key in the tracks dictionary, and contains a dictionary of properties
+ plistKey *track_key = tracks->enumKey(i);
+ plistData *track_dict = track_key->getData();
+ // prepare an item record
+ itemRecordW ir;
+ MEMZERO(&ir, sizeof(ir));
+ ir.year = ir.track = ir.length = -1;
+ ir.lastplay = -1;
+ ir.type = 0; // this makes it an Audio file (unless otherwise specified
+ if (track_dict->getType() == PLISTDATA_DICT) {
+ // we have the track's dictionary of properties...
+ plistDict *track = (plistDict *)track_dict;
+ int tn = track->getNumKeys();
+ // ... now enumerate the properties
+ for (int j=0;j<tn;j++) {
+ plistKey *prop = track->enumKey(j);
+ Importer_AddKeyToItemRecord(prop, ir);
+ }
+ // add or update the file
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ir, ML_IPC_DB_ADDORUPDATEITEMW);
+ // free the record
+ freeRecord(&ir);
+ // show progress
+ ++progress[0];
+ }
+ }
+ }
+ }
+ }
+
+ //done
+ progress[0]=-666;
+ if (importThread)
+ {
+ WaitForSingleObject(importThread, INFINITE);
+ CloseHandle(importThread);
+ }
+
+ // tell gen_ml we modified the db
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_DB_SYNCDB);
+
+ factory->releaseInterface(parser);
+ }
+ else
+ return DISPATCH_FAILURE;
+ return DISPATCH_SUCCESS;
+}
+
+bool ImporterAPI::iTunesExists()
+{
+ wchar_t itunes_path[MAX_PATH] = {0};
+ if (GetiTunesPreferencesPath(itunes_path, MAX_PATH))
+ {
+ return GetFileAttributesW(itunes_path) != INVALID_FILE_ATTRIBUTES;
+ }
+ return false;
+}
+
+int ImporterAPI::ImportFromiTunes(HWND parent)
+{
+ wchar_t itunes_path[MAX_PATH] = {0};
+ if (GetiTunesLibraryPath(itunes_path, MAX_PATH))
+ {
+ return ImportFromFile(parent, itunes_path);
+ }
+ return DISPATCH_FAILURE;
+}
+
+int ImportPlaylists(HWND parent, const wchar_t *library_file);
+int ImporterAPI::ImportPlaylistsFromiTunes(HWND parent)
+{
+ wchar_t itunes_path[MAX_PATH] = {0};
+ if (GetiTunesLibraryPath(itunes_path, MAX_PATH))
+ {
+ return ImportPlaylists(parent, itunes_path);
+ }
+ return DISPATCH_FAILURE;
+}
+
+#define CBCLASS ImporterAPI
+START_DISPATCH;
+CB(API_ITUNES_IMPORTER_ITUNESEXISTS, iTunesExists)
+CB(API_ITUNES_IMPORTER_IMPORTFROMFILE, ImportFromFile)
+CB(API_ITUNES_IMPORTER_IMPORTFROMITUNES, ImportFromiTunes)
+CB(API_ITUNES_IMPORTER_IMPORTPLAYLISTSFROMITUNES, ImportPlaylistsFromiTunes)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/ImporterAPI.h b/Src/Plugins/Library/ml_impex/ImporterAPI.h
new file mode 100644
index 00000000..728f2359
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ImporterAPI.h
@@ -0,0 +1,13 @@
+#include "api_importer.h"
+
+class ImporterAPI : public api_itunes_importer
+{
+public:
+ bool iTunesExists();
+ int ImportFromFile(HWND parent, const wchar_t *library_file);
+ int ImportFromiTunes(HWND parent);
+ int ImportPlaylistsFromiTunes(HWND parent);
+
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/api__ml_impex.h b/Src/Plugins/Library/ml_impex/api__ml_impex.h
new file mode 100644
index 00000000..8b137122
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/api__ml_impex.h
@@ -0,0 +1,20 @@
+#ifndef NULLSOFT_ML_IMPEX_API_H
+#define NULLSOFT_ML_IMPEX_API_H
+
+#include <api/service/waservicefactory.h>
+
+#include "../Agave/Language/api_language.h"
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../playlist/api_playlists.h"
+extern api_playlists *playlistsApi;
+#define AGAVE_API_PLAYLISTS playlistsApi
+
+#endif // !NULLSOFT_ML_IMPEX_API_H
diff --git a/Src/Plugins/Library/ml_impex/api_importer.h b/Src/Plugins/Library/ml_impex/api_importer.h
new file mode 100644
index 00000000..1d9dc433
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/api_importer.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include <api/service/services.h>
+
+// {A32C39BC-CDF7-4c9b-9EA2-9DF7E0D651D2}
+static const GUID iTunesImporterGUID =
+{ 0xa32c39bc, 0xcdf7, 0x4c9b, { 0x9e, 0xa2, 0x9d, 0xf7, 0xe0, 0xd6, 0x51, 0xd2 } };
+
+class api_itunes_importer : public Dispatchable
+{
+protected:
+ api_itunes_importer() {}
+ ~api_itunes_importer() {}
+public:
+ static FOURCC getServiceType() { return WaSvc::UNIQUE; }
+ static const char *getServiceName() { return "iTunes Importer Service"; }
+ static GUID getServiceGuid() { return iTunesImporterGUID; }
+
+ bool iTunesExists();
+ int ImportFromFile(HWND parent, const wchar_t *library_file);
+ int ImportFromiTunes(HWND parent);
+ int ImportPlaylistsFromiTunes(HWND parent);
+
+ enum
+ {
+ API_ITUNES_IMPORTER_ITUNESEXISTS = 0,
+ API_ITUNES_IMPORTER_IMPORTFROMFILE = 1,
+ API_ITUNES_IMPORTER_IMPORTFROMITUNES = 2,
+ API_ITUNES_IMPORTER_IMPORTPLAYLISTSFROMITUNES = 3,
+ };
+};
+
+inline bool api_itunes_importer::iTunesExists()
+{
+ return _call(API_ITUNES_IMPORTER_ITUNESEXISTS, (bool)false);
+}
+
+inline int api_itunes_importer::ImportFromFile(HWND parent, const wchar_t *library_file)
+{
+ return _call(API_ITUNES_IMPORTER_IMPORTFROMFILE, (int)DISPATCH_FAILURE, parent, library_file);
+}
+
+inline int api_itunes_importer::ImportFromiTunes(HWND parent)
+{
+ return _call(API_ITUNES_IMPORTER_IMPORTFROMITUNES, (int)DISPATCH_FAILURE, parent);
+}
+
+inline int api_itunes_importer::ImportPlaylistsFromiTunes(HWND parent)
+{
+ return _call(API_ITUNES_IMPORTER_IMPORTPLAYLISTSFROMITUNES, (int)DISPATCH_FAILURE, parent);
+}
diff --git a/Src/Plugins/Library/ml_impex/impex.cpp b/Src/Plugins/Library/ml_impex/impex.cpp
new file mode 100644
index 00000000..f37eaa65
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/impex.cpp
@@ -0,0 +1,625 @@
+//------------------------------------------------------------------------
+//
+// iTunes XML Import/Export Plugin
+// Copyright © 2003-2014 Winamp SA
+//
+//------------------------------------------------------------------------
+//#define PLUGIN_NAME "Nullsoft Database Import/Export"
+#define PLUGIN_VERSION L"2.65"
+
+#include <windows.h>
+#include <commdlg.h>
+#include <commctrl.h>
+#include <stdio.h>
+#include "api__ml_impex.h"
+#include "../../General/gen_ml/ml.h"
+#include "../winamp/wa_ipc.h"
+#include "resource.h"
+#include <bfc/string/url.h>
+#include "itunesxmlwrite.h"
+#include "importer.h"
+#include "ImporterAPI.h"
+#include "../nu/Singleton.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+
+static ImporterAPI importAPI;
+static SingletonServiceFactory<api_itunes_importer, ImporterAPI> importerFactory;
+// -----------------------------------------------------------------------
+
+#define ID_IMPORT 35443
+#define ID_EXPORT 35444
+#define ID_CVTPLAYLISTS 35445
+#define ID_IMPORT_ITUNES 35446
+
+#define ID_FILE_ADDTOLIBRARY 40344
+#define ID_DOSHITMENU_ADDNEWVIEW 40030
+#define IDM_LIBRARY_CONFIG 40050
+#define ID_DOSHITMENU_ADDNEWPLAYLIST 40031
+#define WINAMP_MANAGEPLAYLISTS 40385
+
+// -----------------------------------------------------------------------
+
+api_application *WASABI_API_APP = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+wchar_t* GetFilterListString(void) { // "iTunes XML Library\0*.xml\0\0"
+ static wchar_t filterString[128] = {0};
+ wchar_t* end = 0;
+ StringCchCopyEx(filterString, 128, WASABI_API_LNGSTRINGW(IDS_ITUNES_XML_LIBRARY), &end, 0, 0);
+ StringCchCopyEx(end+1, 128, L"*.xml", 0, 0, 0);
+ return filterString;
+}
+
+// -----------------------------------------------------------------------
+
+static LRESULT WINAPI ml_newParentWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static LRESULT WINAPI ml_newMlWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+static WNDPROC ml_oldParentWndProc;
+static WNDPROC ml_oldMlWndProc;
+static HWND export_wnd, hwnd_winamp, mlWnd;
+extern winampMediaLibraryPlugin plugin;
+HMENU mlMenu=NULL;
+void exportDatabase();
+void importDatabase();
+
+// -----------------------------------------------------------------------
+// dummy plugin message procs, we just ignore everything
+// -----------------------------------------------------------------------
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_NO_CONFIG)
+ return TRUE;
+
+ return FALSE;
+}
+
+// -----------------------------------------------------------------------
+// plugin, exported to gen_ml
+// -----------------------------------------------------------------------
+static int init();
+static void quit();
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_impex.dll)",
+ init,
+ quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+// -----------------------------------------------------------------------
+// export
+// -----------------------------------------------------------------------
+extern "C" {
+ __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+};
+
+// -----------------------------------------------------------------------
+// returns the position of a command within an HMENU
+// -----------------------------------------------------------------------
+int getMenuItemPos(HMENU menu, UINT command) {
+ for (int i=0;i<256;i++) {
+ MENUITEMINFO mii={sizeof(mii),MIIM_ID,};
+ if (!GetMenuItemInfo(menu, i, TRUE, &mii)) break;
+ if (mii.wID == command) return i;
+ }
+ return -1;
+}
+
+// -----------------------------------------------------------------------
+// entry point, gen_ml is starting up and we've just been loaded
+// -----------------------------------------------------------------------
+int init()
+{
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(languageApiGUID);
+ if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface());
+
+ sf = plugin.service->service_getServiceByGuid(applicationApiServiceGuid);
+ if (sf) WASABI_API_APP = reinterpret_cast<api_application*>(sf->getInterface());
+
+ sf = plugin.service->service_getServiceByGuid(api_playlistmanagerGUID);
+ if (sf) AGAVE_API_PLAYLISTMANAGER = reinterpret_cast<api_playlistmanager*>(sf->getInterface());
+
+ sf = plugin.service->service_getServiceByGuid(api_playlistsGUID);
+ if (sf) AGAVE_API_PLAYLISTS = reinterpret_cast<api_playlists*>(sf->getInterface());
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlImpexLangGUID);
+
+ importerFactory.Register(plugin.service, &importAPI);
+
+ static wchar_t szDescription[256];
+ StringCchPrintf(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_ML_IMPEX_DESC), PLUGIN_VERSION);
+ plugin.description = (char*)szDescription;
+
+ HWND w = plugin.hwndWinampParent;
+ while (GetParent(w) != NULL) w = GetParent(w);
+ hwnd_winamp = w;
+
+ ml_oldParentWndProc = (WNDPROC)SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)ml_newParentWndProc);
+
+ mlMenu = (HMENU)SendMessage(hwnd_winamp, WM_WA_IPC, 9, IPC_GET_HMENU);
+ int IPC_GETMLWINDOW=(int)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&"LibraryGetWnd",IPC_REGISTER_WINAMP_IPCMESSAGE);
+ mlWnd = (HWND)SendMessage(hwnd_winamp, WM_WA_IPC, -1, IPC_GETMLWINDOW);
+
+ ml_oldMlWndProc = (WNDPROC)SetWindowLongPtrW(mlWnd, GWLP_WNDPROC, (LONG_PTR)ml_newMlWndProc);
+
+ int p = getMenuItemPos(mlMenu, ID_FILE_ADDTOLIBRARY);
+ MENUITEMINFO mii={sizeof(mii),MIIM_ID|MIIM_TYPE, MFT_SEPARATOR, };
+ InsertMenuItem(mlMenu, ++p, TRUE, &mii);
+
+ if (importAPI.iTunesExists())
+ {
+ MENUITEMINFO mii2={sizeof(mii2),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT_ITUNES, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_DB), 0};
+ InsertMenuItem(mlMenu, ++p, TRUE, &mii2);
+ MENUITEMINFO mii5={sizeof(mii5),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_CVTPLAYLISTS, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_PL), 0};
+ InsertMenuItem(mlMenu, ++p, TRUE, &mii5);
+ }
+ MENUITEMINFO mii3={sizeof(mii),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_DATABASE), 0};
+ InsertMenuItem(mlMenu, ++p, TRUE, &mii3);
+ MENUITEMINFO mii4={sizeof(mii),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_EXPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_EXPORT_DATABASE), 0};
+ InsertMenuItem(mlMenu, ++p, TRUE, &mii4);
+
+ int IPC_GET_ML_HMENU = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibraryGetHmenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ HMENU context_menu = (HMENU) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_ML_HMENU);
+ if (context_menu)
+ {
+ HMENU hmenuPopup = GetSubMenu(context_menu, 0);
+ if (hmenuPopup)
+ {
+ int p = getMenuItemPos(hmenuPopup, WINAMP_MANAGEPLAYLISTS);
+ if (getMenuItemPos(hmenuPopup, IDM_LIBRARY_CONFIG) != -1) // sanity check
+ {
+ bool end_separator=true;
+ if (p != -1)
+ {
+ MENUITEMINFO mii={sizeof(mii),MIIM_ID|MIIM_TYPE, MFT_SEPARATOR, };
+ InsertMenuItem(hmenuPopup, ++p, TRUE, &mii);
+ end_separator=false;
+ }
+ if (importAPI.iTunesExists())
+ {
+ MENUITEMINFO mii2={sizeof(mii2),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT_ITUNES, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_DB), 0};
+ InsertMenuItem(hmenuPopup, ++p, TRUE, &mii2);
+ MENUITEMINFO mii5={sizeof(mii5),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_CVTPLAYLISTS, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_PL), 0};
+ InsertMenuItem(hmenuPopup, ++p, TRUE, &mii5);
+ }
+ MENUITEMINFO mii3={sizeof(mii3),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_DATABASE), 0};
+ InsertMenuItem(hmenuPopup, ++p, TRUE, &mii3);
+ MENUITEMINFO mii4={sizeof(mii4),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_EXPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_EXPORT_DATABASE), 0};
+ InsertMenuItem(hmenuPopup, ++p, TRUE, &mii4);
+ if (end_separator)
+ {
+ MENUITEMINFO mii={sizeof(mii),MIIM_ID|MIIM_TYPE, MFT_SEPARATOR, };
+ InsertMenuItem(hmenuPopup, ++p, TRUE, &mii);
+ }
+ }
+ }
+ }
+
+ return ML_INIT_SUCCESS;
+}
+
+// -----------------------------------------------------------------------
+// entry point, gen_ml is shutting down and we are being unloaded
+// -----------------------------------------------------------------------
+void quit() {
+ if (IsWindow(plugin.hwndWinampParent)) SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)ml_oldParentWndProc);
+ if (IsWindow(mlWnd)) SetWindowLongPtrW(mlWnd, GWLP_WNDPROC, (LONG_PTR)ml_oldMlWndProc);
+ importerFactory.Deregister(plugin.service);
+}
+
+void handleMenuItem(int wID) {
+ switch (wID) {
+ case ID_EXPORT:
+ exportDatabase();
+ break;
+ case ID_IMPORT:
+ importDatabase();
+ break;
+ case ID_IMPORT_ITUNES:
+ {
+ importAPI.ImportFromiTunes(plugin.hwndLibraryParent);
+ }
+ break;
+ case ID_CVTPLAYLISTS:
+ {
+ importAPI.ImportPlaylistsFromiTunes(plugin.hwndLibraryParent);
+ }
+ break;
+ }
+}
+
+void setDialogIcon(HWND hwndDlg)
+{
+ static HICON wa_icy;
+ if (wa_icy)
+ {
+ wa_icy = (HICON)LoadImage(GetModuleHandle(L"winamp.exe"),
+ MAKEINTRESOURCE(102), IMAGE_ICON,
+ GetSystemMetrics(SM_CXSMICON),
+ GetSystemMetrics(SM_CYSMICON),
+ LR_SHARED | LR_LOADTRANSPARENT | LR_CREATEDIBSECTION);
+ }
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)wa_icy);
+}
+
+// -----------------------------------------------------------------------
+// library HWND subclass
+// -----------------------------------------------------------------------
+static LRESULT WINAPI ml_newParentWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_COMMAND:
+ {
+ int wID = LOWORD(wParam);
+ handleMenuItem(wID);
+ }
+ break;
+ }
+ return CallWindowProcW(ml_oldParentWndProc,hwndDlg,uMsg,wParam,lParam);
+}
+
+// -----------------------------------------------------------------------
+static LRESULT WINAPI ml_newMlWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_COMMAND:
+ {
+ int wID = LOWORD(wParam);
+ handleMenuItem(wID);
+ }
+ break;
+ }
+ return CallWindowProcW(ml_oldMlWndProc,hwndDlg,uMsg,wParam,lParam);
+}
+
+// {55334B63-68D5-4389-A209-797F123E207F}
+static const GUID ML_IMPEX =
+{ 0x55334b63, 0x68d5, 0x4389, { 0xa2, 0x9, 0x79, 0x7f, 0x12, 0x3e, 0x20, 0x7f } };
+
+//------------------------------------------------------------------------
+// pick an input file
+//------------------------------------------------------------------------
+static int pickFile(HWND hwndDlg, StringW *file)
+{
+ wchar_t oldCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, oldCurPath);
+
+ OPENFILENAME l={sizeof(l),};
+ wchar_t *temp;
+ const int len=256*1024-128;
+
+ temp = (wchar_t *)GlobalAlloc(GPTR,len*sizeof(*temp));
+ l.hwndOwner = hwndDlg;
+ extern wchar_t* GetFilterListString(void);
+ l.lpstrFilter = GetFilterListString();//L"iTunes XML Library\0*.xml\0\0"; // IDS_ITUNES_XML_LIBRARY
+ l.lpstrFile = temp;
+ l.nMaxFile = len-1;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_IMPORT_DATABASE);
+ l.lpstrDefExt = L"";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();;
+ l.Flags = OFN_HIDEREADONLY|OFN_EXPLORER;
+ if(GetOpenFileName(&l))
+ {
+ wchar_t newCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, newCurPath);
+ WASABI_API_APP->path_setWorkingPath(newCurPath);
+ SetCurrentDirectoryW(oldCurPath);
+ *file = temp;
+ return 1;
+ }
+ SetCurrentDirectoryW(oldCurPath);
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// import an iTunes XML library into gen_ml
+// -----------------------------------------------------------------------
+void importDatabase()
+{
+ StringW file;
+ // pick an inputfile
+ if (pickFile(plugin.hwndLibraryParent, &file))
+ {
+ importAPI.ImportFromFile(plugin.hwndLibraryParent, file.getValue());
+ // TODO ideally should only do this if the current view is from ml_local
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+}
+
+// -----------------------------------------------------------------------
+int only_itunes = -1; // ask
+
+// -----------------------------------------------------------------------
+// ask the user if we should also write files unsupported by iTunes
+// -----------------------------------------------------------------------
+static BOOL CALLBACK exporttype_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ only_itunes = 0;
+ setDialogIcon(hwndDlg);
+
+ CheckDlgButton(hwndDlg, IDC_RADIO_ALLFILES, TRUE);
+ CheckDlgButton(hwndDlg, IDC_RADIO_ONLYSUPPORTED, FALSE);
+ SetForegroundWindow(hwndDlg);
+ return 0;
+ }
+
+ case WM_COMMAND: {
+ int wID = LOWORD(wParam);
+ switch (wID) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwndDlg, wID);
+ SetForegroundWindow(export_wnd);
+ break;
+ case IDC_RADIO_ONLYSUPPORTED:
+ only_itunes = 1;
+ break;
+ case IDC_RADIO_ALLFILES:
+ only_itunes = 0;
+ break;
+ }
+ }
+ return 0;
+ }
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// export status window proc
+// -----------------------------------------------------------------------
+static BOOL CALLBACK export_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ export_wnd = hwndDlg;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ // init it
+ ShowWindow(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), SW_SHOWNORMAL);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_TRACKS), SW_SHOWNORMAL);
+
+ SetWindowText(hwndDlg,WASABI_API_LNGSTRINGW(IDS_EXPORTING_DATABASE));
+ SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, 0, 0);
+
+ setDialogIcon(hwndDlg);
+
+ SetTimer(hwndDlg, 666, 250, 0);
+ SetForegroundWindow(hwndDlg);
+ return 0;
+ }
+
+ case WM_TIMER:
+ if (wParam == 666)
+ {
+ int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (progress[0] == -333)
+ {
+ // show "saving xml"
+ ShowWindow(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_TRACKS), SW_HIDE);
+ SetDlgItemText(hwndDlg,IDC_PROCESSING_STATE,WASABI_API_LNGSTRINGW(IDS_WRITINGING_XML));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_PROCESSING_STATE), SW_SHOWNORMAL);
+ }
+ else if (progress[0] == -666)
+ {
+ KillTimer(hwndDlg, 666);
+ EndDialog(hwndDlg, 0);
+ }
+ else
+ {
+ // display progress
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, (int)((double)progress[0] / progress[1] * 100.0), 0);
+ SetDlgItemText(hwndDlg, IDC_TRACKS, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_TRACKS_EXPORTED_X), progress[0]));
+ }
+ }
+ break;
+
+ case WM_COMMAND: {
+ int wID = LOWORD(wParam);
+ switch (wID) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwndDlg, wID);
+ break;
+ }
+ }
+ return 0;
+ }
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// returns 1 if str ends with e
+// -----------------------------------------------------------------------
+int endsIn(const wchar_t *str, const wchar_t *e) {
+ return !_wcsicmp(str+wcslen(str)-wcslen(e), e);
+}
+
+static DWORD CALLBACK ExportThread(LPVOID param)
+{
+ WASABI_API_DIALOGBOXPARAMW(IDD_INFODIALOG, NULL, export_dlgproc, (LPARAM)param);
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// exports an iTunes XML library from gen_ml's database
+// -----------------------------------------------------------------------
+void exportDatabase() {
+ // create an iTunes XML library writer
+ iTunesXmlWrite w;
+
+ // pick an output file
+ if (w.pickFile(plugin.hwndLibraryParent)) {
+ // if a file unsupported by iTunes is about to be exported, ask confirmation
+ only_itunes = -1;
+
+ // create status window
+ int progress[2] = {0};
+ DWORD threadId = 0;
+ HANDLE exportThread = CreateThread(0, 0, ExportThread, progress, 0, &threadId);
+
+ // create an iTunes DB in memory, start with a root key
+ plistKey *rootKey = new plistKey(L"root");
+ // add a dictionary to it
+ plistDict *rootDict = new plistDict();
+ rootKey->setData(rootDict);
+
+ // add a few useless fields
+ rootDict->addKey(new plistKey(L"Major Version", new plistInteger(1)));
+ rootDict->addKey(new plistKey(L"Minor Version", new plistInteger(1)));
+ rootDict->addKey(new plistKey(L"Application Version", new plistString(L"7.6.1"))); // we pretend to be iTunes 7.6.1
+
+ // create the Tracks key and its dictionary of tracks
+ plistDict *dict_tracks = new plistDict();
+ rootDict->addKey(new plistKey(L"Tracks", dict_tracks));
+
+ // run an empty query, so we get all items in the db
+ mlQueryStructW query;
+ query.query = L"";
+ query.max_results = 0;
+ query.results.Size = 0;
+ query.results.Items = NULL;
+ query.results.Alloc = 0;
+
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&query, ML_IPC_DB_RUNQUERYW);
+
+ // my, what a big number of items...
+ progress[1] = query.results.Size;
+
+ // ... enumerate them
+ for (int x = 0; x < progress[1]; x ++) {
+ // take the xth item
+ itemRecordW ir = query.results.Items[x];
+
+ // check if it's supported by iTunes, if we've already answered the question, use last answer
+ if ((only_itunes > 0 || only_itunes < 0) && (!endsIn(ir.filename, L".mp3") && !endsIn(ir.filename, L".m4a") && !endsIn(ir.filename, L".wav") && !endsIn(ir.filename, L".aiff"))) {
+ if (only_itunes < 0) {
+ // prompt user, should we export files unsupported by iTunes ?
+ WASABI_API_DIALOGBOXW(IDD_EXPORTTYPE, plugin.hwndLibraryParent, exporttype_dlgproc);
+ }
+ // if not, continue with the next item
+ if (only_itunes) continue;
+ }
+
+ // create a track key, its name is the number of the track (not counting skipped tracks)
+ plistKey *key_track = new plistKey(StringPrintfW(L"%d", progress[0]));
+ dict_tracks->addKey(key_track);
+
+ // give it a dictionary to hold its properties
+ plistDict *dict_track = new plistDict();
+ key_track->setData(dict_track);
+
+ // create the properties as needed
+ dict_track->addKey(new plistKey(L"Track ID", new plistInteger(progress[0])));
+ if (ir.title) dict_track->addKey(new plistKey(L"Name", new plistString(ir.title)));
+ if (ir.artist) dict_track->addKey(new plistKey(L"Artist", new plistString(ir.artist)));
+ if (ir.albumartist) dict_track->addKey(new plistKey(L"Album Artist", new plistString(ir.albumartist)));
+ if (ir.album) dict_track->addKey(new plistKey(L"Album", new plistString(ir.album)));
+ if (ir.genre) dict_track->addKey(new plistKey(L"Genre", new plistString(ir.genre)));
+ if (ir.comment) dict_track->addKey(new plistKey(L"Comments", new plistString(ir.comment)));
+ dict_track->addKey(new plistKey(L"Kind", new plistString(L"MPEG audio file")));
+
+ // changed in 5.64 to use the 'realsize' if it's available, otherwise map to filesize scaled to bytes (stored as kb otherwise)
+ const wchar_t *realsize = getRecordExtendedItem(&ir, L"realsize");
+ if (realsize) dict_track->addKey(new plistKey(L"Size", new plistInteger(_wtoi64(realsize))));
+ else if (ir.filesize > 0) dict_track->addKey(new plistKey(L"Size", new plistInteger(ir.filesize * 1024)));
+
+ if (ir.length >= 0) dict_track->addKey(new plistKey(L"Total Time", new plistInteger(ir.length * 1000)));
+ if (ir.track >= 0) dict_track->addKey(new plistKey(L"Track Number", new plistInteger(ir.track)));
+ if (ir.year >= 0) dict_track->addKey(new plistKey(L"Year", new plistInteger(ir.year)));
+ if (ir.filetime> 0) dict_track->addKey(new plistKey(L"Date Modified", new plistDate((time_t)ir.filetime)));
+ if (ir.lastupd> 0) dict_track->addKey(new plistKey(L"Date Added", new plistDate((time_t)ir.lastupd)));
+ //if (ir.lastplay> 0) dict_track->addKey(new plistKey(L"Play Date", new plistInteger((time_t)ir.lastplay)));
+ if (ir.lastplay > 0) dict_track->addKey(new plistKey(L"Play Date UTC", new plistDate((time_t)ir.lastplay)));
+ if (ir.bitrate> 0) dict_track->addKey(new plistKey(L"Bit Rate", new plistInteger(ir.bitrate)));
+ if (ir.playcount> 0) dict_track->addKey(new plistKey(L"Play Count", new plistInteger(ir.playcount)));
+ if (ir.rating> 0) dict_track->addKey(new plistKey(L"Rating", new plistInteger(ir.rating * 20)));
+ if (ir.composer) dict_track->addKey(new plistKey(L"Composer", new plistString(ir.composer)));
+ if (ir.publisher) dict_track->addKey(new plistKey(L"Publisher", new plistString(ir.publisher)));
+ if (ir.type == 1) dict_track->addKey(new plistKey(L"Has Video", new plistInteger(1)));
+ if (ir.disc> 0) dict_track->addKey(new plistKey(L"Disc Number", new plistInteger(ir.disc)));
+ if (ir.discs> 0) dict_track->addKey(new plistKey(L"Disc Count", new plistInteger(ir.discs)));
+ if (ir.tracks > 0) dict_track->addKey(new plistKey(L"Track Count", new plistInteger(ir.tracks)));
+ if (ir.bpm > 0) dict_track->addKey(new plistKey(L"BPM", new plistInteger(ir.bpm)));
+ const wchar_t *category = getRecordExtendedItem(&ir, L"category");
+ if (category) dict_track->addKey(new plistKey(L"Grouping", new plistString(category)));
+ const wchar_t *producer = getRecordExtendedItem(&ir, L"producer");
+ if (producer) dict_track->addKey(new plistKey(L"Producer", new plistString(producer)));
+ const wchar_t *director = getRecordExtendedItem(&ir, L"director");
+ if (director) dict_track->addKey(new plistKey(L"Director", new plistString(director)));
+ const wchar_t *width = getRecordExtendedItem(&ir, L"width");
+ if (width) dict_track->addKey(new plistKey(L"Video Width", new plistInteger(_wtoi64(width))));
+ const wchar_t *height = getRecordExtendedItem(&ir, L"height");
+ if (height) dict_track->addKey(new plistKey(L"Video Height", new plistInteger(_wtoi64(height))));
+ // convert the filename's backslashes to slashes
+ StringW s = ir.filename;
+
+ if (WCSNICMP(s, L"http://", 7))
+ {
+ wchar_t *val = s.getNonConstVal();
+ // TODO: we could do this with less malloc usage
+ AutoChar utf8(val, CP_UTF8);
+ String encoded = (const char *)utf8;
+ Url::encode(encoded, 0, URLENCODE_EXCLUDEALPHANUM|URLENCODE_EXCLUDESLASH, Url::URLENCODE_STYLE_PERCENT);
+
+ s = StringPrintfW(L"%s%s", ITUNES_FILENAME_HEADER, AutoWide(encoded, CP_UTF8));
+ }
+
+ // done
+ dict_track->addKey(new plistKey(L"Location", new plistString(s)));
+ dict_track->addKey(new plistKey(L"File Folder Count", new plistInteger(-1)));
+ dict_track->addKey(new plistKey(L"Library Folder Count", new plistInteger(-1)));
+
+ // we have one more item in our exported db
+ progress[0]++;
+ }
+
+ // show "saving xml"
+ progress[0]=-333;
+
+ // free query results
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&query, ML_IPC_DB_FREEQUERYRESULTSW);
+
+ // save the xml
+ w.saveXml(rootKey);
+
+ // done
+ progress[0]=-666;
+ if (exportThread)
+ {
+ WaitForSingleObject(exportThread, INFINITE);
+ CloseHandle(exportThread);
+ }
+
+ // destroy the db, this deletes all the children too
+ delete rootKey;
+ }
+}
+
+//------------------------------------------------------------------------ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/importer.cpp b/Src/Plugins/Library/ml_impex/importer.cpp
new file mode 100644
index 00000000..e6b5c271
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/importer.cpp
@@ -0,0 +1,327 @@
+#include "../plist/types.h"
+#include "importer.h"
+#include "../../General/gen_ml/ml.h"
+#include <bfc/string/stringdict.h>
+#include <bfc/string/url.h>
+
+BEGIN_STRINGDICTIONARY(_itunesprops)
+SDI(L"Track ID", IT_TRACKID);
+SDI(L"Name", IT_NAME);
+SDI(L"Artist", IT_ARTIST);
+SDI(L"Album Artist", IT_ALBUMARTIST);
+SDI(L"Album", IT_ALBUM);
+SDI(L"Genre", IT_GENRE);
+SDI(L"Comments", IT_COMMENTS);
+SDI(L"Kind", IT_KIND);
+SDI(L"Size", IT_SIZE);
+SDI(L"Total Time", IT_TOTALTIME);
+SDI(L"Track Number", IT_TRACKNUM);
+SDI(L"Track Count", IT_TRACKCOUNT);
+SDI(L"Year", IT_YEAR);
+SDI(L"Date Modified", IT_DATEMODIFIED);
+SDI(L"Date Added", IT_DATEADDED);
+SDI(L"Bit Rate", IT_BITRATE);
+SDI(L"Bitrate", IT_BITRATE);
+SDI(L"Sample Rate", IT_SAMPLERATE);
+SDI(L"Rating", IT_RATING);
+SDI(L"Location", IT_LOCATION);
+SDI(L"File Folder Count", IT_FOLDERCOUNT);
+SDI(L"Library Folder Count", IT_LIBFOLDERCOUNT);
+SDI(L"Play Count", IT_PLAYCOUNT);
+SDI(L"Play Date", IT_PLAYDATE);
+SDI(L"Play Date UTC", IT_PLAYDATE_UTC);
+SDI(L"Composer", IT_COMPOSER);
+SDI(L"Publisher", IT_PUBLISHER);
+SDI(L"Disc Number", IT_DISCNUMBER);
+SDI(L"Disc Count", IT_DISCCOUNT);
+SDI(L"BPM", IT_BPM);
+SDI(L"Has Video", IT_HAS_VIDEO);
+SDI(L"Grouping", IT_GROUPING);
+SDI(L"Producer", IT_PRODUCER);
+SDI(L"Director", IT_DIRECTOR);
+SDI(L"Artwork Count", IT_ARTWORK_COUNT);
+SDI(L"Persistent ID", IT_PERSISTENT_ID);
+SDI(L"Track Type", IT_TRACK_TYPE);
+SDI(L"HD", IT_HD);
+SDI(L"Video Width", IT_VIDEO_WIDTH);
+SDI(L"Video Height", IT_VIDEO_HEIGHT);
+SDI(L"Movie", IT_MOVIE);
+SDI(L"Release Date", IT_RELEASE_DATE);
+SDI(L"Normalization", IT_NORMALIZATION);
+SDI(L"Sort Name", IT_SORTNAME);
+SDI(L"Purchased", IT_PURCHASED);
+SDI(L"iTunesU", IT_ITUNESU);
+SDI(L"Skip Count", IT_SKIPCOUNT);
+SDI(L"Skip Date", IT_SKIPDATE);
+SDI(L"Sort Album", IT_SORTALBUM);
+SDI(L"Sort Composer", IT_SORTCOMPOSER);
+SDI(L"Part Of Gapless Album", IT_PART_OF_GAPLESS_ALBUM);
+SDI(L"Compilation", IT_COMPILATION);
+SDI(L"Sort Album Artist", IT_SORT_ALBUM_ARTIST);
+SDI(L"Sort Artist", IT_SORT_ARTIST);
+END_STRINGDICTIONARY(_itunesprops, itunesprops)
+
+void FixPath(const wchar_t *strdata, StringW &f)
+{
+ f = strdata;
+ // if the file starts with the local filename header, strip it
+ if (!_wcsnicmp(f, ITUNES_FILENAME_HEADER, wcslen(ITUNES_FILENAME_HEADER))) {
+ if (f[wcslen(ITUNES_FILENAME_HEADER)] == '/')
+ f = StringW(f.getValue()+wcslen(ITUNES_FILENAME_HEADER)-1);
+ else
+ f = StringW(f.getValue()+wcslen(ITUNES_FILENAME_HEADER));
+ // and then convert the slashes to backslashes
+ wchar_t *p = f.getNonConstVal();
+ while (p && *p) { if (*p == '/') *p = '\\'; p++; }
+ }
+ // oddly enough, iTunes XML library filenames have a trailing slash, go figure... and strip it!
+ if (f.lastChar() == '\\') f.trunc((int)f.len()-1);
+ else if (f.lastChar() == '/') f.trunc((int)f.len()-1); // if this is a url, there was no / to \ conversion
+ // decode %XX
+ Url::decode(f);
+}
+
+static void Importer_AddKeyToItemRecord(int t, const plistString *data, itemRecordW &ir)
+{
+ const wchar_t *strdata = data->getString();
+
+ // load this property into the appropriate gen_ml field
+ switch (t)
+ {
+ case IT_TRACKID:
+ // ignored
+ break;
+ case IT_NAME:
+ ir.title = _wcsdup(strdata);
+ break;
+ case IT_ARTIST:
+ ir.artist = _wcsdup(strdata);
+ break;
+ case IT_ALBUMARTIST:
+ ir.albumartist = _wcsdup(strdata);
+ break;
+ case IT_ALBUM:
+ ir.album = _wcsdup(strdata);
+ break;
+ case IT_GENRE:
+ ir.genre = _wcsdup(strdata);
+ break;
+ case IT_COMMENTS:
+ ir.comment = _wcsdup(strdata);
+ break;
+ case IT_KIND:
+ // ignored
+ break;
+ case IT_LOCATION:
+ {
+ StringW f;
+ FixPath(strdata, f);
+ // done
+ ir.filename = _wcsdup(f);
+ break;
+ }
+ case IT_COMPOSER:
+ ir.composer = _wcsdup(strdata);
+ break;
+ case IT_PUBLISHER:
+ ir.publisher = _wcsdup(strdata);
+ break;
+ case IT_GROUPING:
+ setRecordExtendedItem(&ir, L"category", strdata);
+ break;
+ case IT_PRODUCER:
+ setRecordExtendedItem(&ir, L"producer", strdata);
+ break;
+ case IT_DIRECTOR:
+ setRecordExtendedItem(&ir, L"director", strdata);
+ break;
+ case IT_PERSISTENT_ID:
+ break;
+ case IT_TRACK_TYPE:
+ break;
+ case IT_SORTNAME:
+ break;
+ case IT_SORTALBUM:
+ break;
+ case IT_SORTCOMPOSER:
+ break;
+ case IT_SORT_ALBUM_ARTIST:
+ break;
+ case IT_SORT_ARTIST:
+ break;
+ default:
+ //DebugStringW(L"Unknown property: %s\n", prop->getName());
+ break;
+ }
+}
+
+static void Importer_AddKeyToItemRecord(int t, const plistInteger *data, itemRecordW &ir)
+{
+ int64_t value = data->getValue();
+
+ /* benski> we need to keep the ones that were changed to plistBoolean,
+ because old exported libraries will still be written with integers */
+
+ // load this property into the appropriate gen_ml field
+ switch (t)
+ {
+ case IT_TRACKID:
+ // ignore
+ break;
+ case IT_SIZE:
+ ir.filesize = (int)(value >> 10);
+ setRecordExtendedItem(&ir, L"realsize", data->getString());
+ break;
+ case IT_TOTALTIME:
+ if (value)
+ ir.length = (int)(value / 1000);
+ break;
+ case IT_TRACKNUM:
+ ir.track = (int)value;
+ break;
+ case IT_TRACKCOUNT:
+ if (value)
+ ir.tracks = (int)value;
+ break;
+ case IT_YEAR:
+ if (value)
+ ir.year = (int)value;
+ break;
+ case IT_BITRATE:
+ ir.bitrate = (int)value;
+ break;
+ case IT_SAMPLERATE:
+ // ignored
+ break;
+ case IT_RATING:
+ ir.rating = (int)(((double)value / 100.0) * 5.0);
+ break;
+ case IT_FOLDERCOUNT:
+ // ignored
+ break;
+ case IT_LIBFOLDERCOUNT:
+ // ignored
+ break;
+ case IT_PLAYCOUNT:
+ if (value > 0)
+ ir.playcount = (int)value;
+ break;
+ case IT_PLAYDATE:
+ if (value)
+ ir.lastplay = value;
+ break;
+ case IT_DISCNUMBER:
+ if (value)
+ ir.disc = (int)value;
+ break;
+ case IT_DISCCOUNT:
+ if (value)
+ ir.discs = (int)value;
+ break;
+ case IT_BPM:
+ if (value)
+ ir.bpm = (int)value;
+ break;
+ case IT_HAS_VIDEO:
+ if (value == 1)
+ ir.type = 1;
+ break;
+ case IT_ARTWORK_COUNT:
+ break;
+ case IT_VIDEO_WIDTH:
+ setRecordExtendedItem(&ir, L"width", data->getString());
+ break;
+ case IT_VIDEO_HEIGHT:
+ setRecordExtendedItem(&ir, L"height", data->getString());
+ break;
+ case IT_NORMALIZATION:
+ // TODO: can we convert this to replay gain?
+ break;
+ case IT_SKIPCOUNT:
+ break;
+ default:
+ break;
+ }
+}
+
+static void Importer_AddKeyToItemRecord(int t, const plistBoolean *data, itemRecordW &ir)
+{
+ int value = !!data->getValue();
+
+ // load this property into the appropriate gen_ml field
+ switch (t)
+ {
+ case IT_HAS_VIDEO:
+ ir.type = value;
+ break;
+ case IT_HD:
+ break;
+ case IT_MOVIE:
+ break;
+ case IT_PURCHASED:
+ break;
+ case IT_ITUNESU:
+ break;
+ case IT_PART_OF_GAPLESS_ALBUM:
+ break;
+ case IT_COMPILATION:
+ break;
+ default:
+ break;
+ }
+}
+
+static void Importer_AddKeyToItemRecord(int t, const plistDate *data, itemRecordW &ir)
+{
+ time_t date_value= data->getDate();
+
+ // load this property into the appropriate gen_ml field
+ switch (t)
+ {
+ case IT_DATEMODIFIED:
+ if (date_value != -1)
+ ir.filetime = date_value;
+ break;
+ case IT_DATEADDED:
+ if (date_value != -1)
+ ir.lastupd = date_value;
+ break;
+ case IT_PLAYDATE_UTC:
+ if (date_value != -1)
+ ir.lastplay = date_value;
+ break;
+ case IT_RELEASE_DATE:
+ break;
+ case IT_SKIPDATE:
+ break;
+ default:
+ break;
+ }
+}
+
+void Importer_AddKeyToItemRecord(const plistKey *prop, itemRecordW &ir)
+{
+ const plistData *data = prop->getData();
+
+ if (data)
+ {
+ int t = itunesprops.getId(prop->getName());
+ switch(data->getType())
+ {
+ case PLISTDATA_STRING:
+ Importer_AddKeyToItemRecord(t, (const plistString *)data, ir);
+ break;
+ case PLISTDATA_INTEGER:
+ Importer_AddKeyToItemRecord(t, (const plistInteger *)data, ir);
+ break;
+ case PLISTDATA_DATE:
+ Importer_AddKeyToItemRecord(t, (const plistDate *)data, ir);
+ break;
+ case PLISTDATA_BOOLEAN:
+ Importer_AddKeyToItemRecord(t, (const plistBoolean *)data, ir);
+ break;
+ default:
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/importer.h b/Src/Plugins/Library/ml_impex/importer.h
new file mode 100644
index 00000000..fbed11b6
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/importer.h
@@ -0,0 +1,66 @@
+#pragma once
+#include "../plist/types.h"
+#include "../../General/gen_ml/ml.h"
+
+// header for local filenames
+#define ITUNES_FILENAME_HEADER L"file://localhost/"
+
+enum
+{
+ IT_TRACKID=0,
+ IT_NAME,
+ IT_ARTIST,
+ IT_ALBUMARTIST,
+ IT_ALBUM,
+ IT_GENRE,
+ IT_COMMENTS,
+ IT_KIND,
+ IT_SIZE,
+ IT_TOTALTIME,
+ IT_TRACKNUM,
+ IT_TRACKCOUNT,
+ IT_YEAR,
+ IT_DATEMODIFIED,
+ IT_DATEADDED,
+ IT_BITRATE,
+ IT_RATING,
+ IT_SAMPLERATE,
+ IT_LOCATION,
+ IT_FOLDERCOUNT,
+ IT_LIBFOLDERCOUNT,
+ IT_PLAYCOUNT,
+ IT_PLAYDATE,
+ IT_PLAYDATE_UTC,
+ IT_COMPOSER,
+ IT_PUBLISHER,
+ IT_DISCNUMBER,
+ IT_DISCCOUNT,
+ IT_BPM,
+ IT_HAS_VIDEO,
+ IT_GROUPING,
+ IT_PRODUCER,
+ IT_DIRECTOR,
+ IT_ARTWORK_COUNT,
+ IT_PERSISTENT_ID,
+ IT_TRACK_TYPE,
+ IT_HD,
+ IT_VIDEO_WIDTH,
+ IT_VIDEO_HEIGHT,
+ IT_MOVIE,
+ IT_RELEASE_DATE,
+ IT_NORMALIZATION,
+ IT_SORTNAME,
+ IT_PURCHASED,
+ IT_ITUNESU,
+ IT_SKIPCOUNT,
+ IT_SKIPDATE,
+ IT_SORTALBUM,
+ IT_SORTCOMPOSER,
+ IT_PART_OF_GAPLESS_ALBUM,
+ IT_COMPILATION,
+ IT_SORT_ALBUM_ARTIST,
+ IT_SORT_ARTIST,
+};
+void Importer_AddKeyToItemRecord(const plistKey *prop, itemRecordW &ir);
+int ImportPlaylists(HWND parent, const wchar_t *library_file);
+void setDialogIcon(HWND hwndDlg); \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/itunesxmlwrite.cpp b/Src/Plugins/Library/ml_impex/itunesxmlwrite.cpp
new file mode 100644
index 00000000..b4f86352
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/itunesxmlwrite.cpp
@@ -0,0 +1,152 @@
+//------------------------------------------------------------------------
+//
+// iTunes XML Library Writer
+// Copyright © 2003-2014 Winamp SA
+//
+//------------------------------------------------------------------------
+
+#include <windows.h>
+#include <commdlg.h>
+#include <api/xml/xmlwrite.h>
+#include <bfc/string/url.h>
+#include "itunesxmlwrite.h"
+#include "../plist/types.h"
+#include "api__ml_impex.h"
+#include "resource.h"
+
+//------------------------------------------------------------------------
+iTunesXmlWrite::iTunesXmlWrite() {
+}
+
+//------------------------------------------------------------------------
+iTunesXmlWrite::~iTunesXmlWrite() {
+}
+
+//------------------------------------------------------------------------
+int iTunesXmlWrite::pickFile(HWND hwndDlg, const wchar_t *title)
+{
+ wchar_t oldCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, oldCurPath);
+
+ OPENFILENAME l={sizeof(l),};
+ wchar_t *temp;
+ const int len=256*1024-128;
+
+ temp = (wchar_t *)GlobalAlloc(GPTR,len*sizeof(*temp));
+ l.hwndOwner = hwndDlg;
+ //l.lpstrFilter = L"iTunes XML Library\0*.xml\0\0"; // IDS_ITUNES_XML_LIBRARY
+ extern wchar_t* GetFilterListString(void);
+ l.lpstrFilter = GetFilterListString();//L"iTunes XML Library\0*.xml\0\0"; // IDS_ITUNES_XML_LIBRARY
+ l.lpstrFile = temp;
+ l.nMaxFile = len-1;
+ l.lpstrTitle = title ? title : WASABI_API_LNGSTRINGW(IDS_EXPORT_DATABASE);
+ l.lpstrDefExt = L"xml";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
+ l.Flags = OFN_HIDEREADONLY|OFN_EXPLORER|OFN_OVERWRITEPROMPT;
+ if (GetSaveFileName(&l))
+ {
+ wchar_t newCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, newCurPath);
+ WASABI_API_APP->path_setWorkingPath(newCurPath);
+ SetCurrentDirectoryW(oldCurPath);
+ file = temp;
+ return 1;
+ }
+ SetCurrentDirectoryW(oldCurPath);
+ return 0;
+}
+
+//------------------------------------------------------------------------
+void iTunesXmlWrite::saveXml(plistKey *rootkey) {
+ if (file.isempty()) return;
+ XMLWrite w(file, L"plist version=\"1.0\"", L"plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"", 1);
+ writeData(&w, rootkey->getData());
+}
+
+//------------------------------------------------------------------------
+void iTunesXmlWrite::writeData(XMLWrite *writer, plistData *data)
+{
+ switch (data->getType())
+ {
+ case PLISTDATA_KEY:
+ {
+ plistKey *key = (plistKey *)data;
+ writer->writeAttrib(data->getTypeString(), data->getString(), key->getData()->getType() == PLISTDATA_DICT || key->getData()->getType() == PLISTDATA_ARRAY || key->getData()->getType() == PLISTDATA_RAW);
+ writeData(writer, key->getData());
+ break;
+ }
+ case PLISTDATA_DICT:
+ {
+ plistDict *dict = (plistDict *)data;
+ writer->pushCategory(data->getTypeString());
+ for (int i=0;i<dict->getNumKeys();i++)
+ {
+ writeData(writer, dict->enumKey(i));
+ }
+ writer->popCategory();
+ break;
+ }
+ case PLISTDATA_ARRAY:
+ {
+ plistArray *array = (plistArray *)data;
+ writer->pushCategory(data->getTypeString());
+ for (int i=0;i<array->getNumItems();i++) {
+ writeData(writer, array->enumItem(i));
+ }
+ writer->popCategory();
+ break;
+ }
+ case PLISTDATA_INTEGER:
+ case PLISTDATA_DATE:
+ case PLISTDATA_RAW:
+ {
+ const wchar_t *str = data->getString();
+ if (str && *str)
+ {
+ writer->writeAttrib(data->getTypeString(), str, 1, 0);
+ }
+ }
+ break;
+ case PLISTDATA_STRING:
+ {
+ const wchar_t *str = data->getString();
+ if (str && *str)
+ {
+ // not pretty but it'll strip out control characters
+ // in the 0 - 31 range that will cause import issues
+ wchar_t *temp = 0;
+ int len = (int)wcslen(str) + 1;
+ temp = (wchar_t*)calloc(len, sizeof(wchar_t));
+ wchar_t *ptr = temp;
+ while(str && *str)
+ {
+ int chr = *str;
+ if (chr >= 0 && chr <= 31)
+ {
+ if(chr == 9 || chr == 10 || chr == 13)
+ {
+ *ptr = *str;
+ ptr++;
+ }
+ }
+ else
+ {
+ *ptr = *str;
+ ptr++;
+ }
+ str = CharNextW(str);
+ }
+ *ptr=0;
+ writer->writeAttrib(data->getTypeString(), (temp ? temp : str), 1, 0);
+ if (temp) free(temp);
+ }
+ }
+ break;
+ case PLISTDATA_BOOLEAN:
+ {
+ //plistBoolean *booldata = (plistBoolean *)data;
+ writer->writeAttribEmpty(data->getString(), 1, 0);
+ }
+ break;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/itunesxmlwrite.h b/Src/Plugins/Library/ml_impex/itunesxmlwrite.h
new file mode 100644
index 00000000..caf47453
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/itunesxmlwrite.h
@@ -0,0 +1,33 @@
+//------------------------------------------------------------------------
+//
+// iTunes XML Library Writer
+// Copyright © 2003-2014 Winamp SA
+//
+//------------------------------------------------------------------------
+
+#ifndef _ITUNESXMLWRITE_H
+#define _ITUNESXMLWRITE_H
+
+class plistKey;
+class XMLWrite;
+class plistData;
+#include <bfc/string/stringw.h>
+//------------------------------------------------------------------------
+
+class iTunesXmlWrite {
+public:
+ iTunesXmlWrite();
+ virtual ~iTunesXmlWrite();
+
+ int pickFile(HWND hwndDlg, const wchar_t *title=NULL);
+ void saveXml(plistKey *rootkey);
+
+ void writeData(XMLWrite *writer, plistData *data);
+
+private:
+ StringW file;
+};
+
+#endif
+
+//------------------------------------------------------------------------ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/ml_impex.rc b/Src/Plugins/Library/ml_impex/ml_impex.rc
new file mode 100644
index 00000000..4d745564
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ml_impex.rc
@@ -0,0 +1,144 @@
+// 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
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_INFODIALOG DIALOGEX 0, 0, 188, 47
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "Progress1",IDC_PROGRESS_PERCENT,"msctls_progress32",PBS_SMOOTH | NOT WS_VISIBLE | WS_BORDER,7,7,174,12
+ CTEXT "",IDC_TRACKS,7,27,174,8,NOT WS_VISIBLE
+ CTEXT "",IDC_PROCESSING_STATE,35,19,117,8
+END
+
+IDD_EXPORTTYPE DIALOGEX 0, 0, 229, 71
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Unsupported File Formats"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Some files in your database are not supported by iTunes.",IDC_STATIC,9,11,184,8
+ CONTROL "Export all files anyway.",IDC_RADIO_ALLFILES,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,15,24,91,10
+ CONTROL "Export only files supported by iTunes (mp3/aac/wav/aiff).",IDC_RADIO_ONLYSUPPORTED,
+ "Button",BS_AUTORADIOBUTTON | WS_TABSTOP,15,35,201,10
+ DEFPUSHBUTTON "OK",IDOK,172,50,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_INFODIALOG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 181
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 40
+ END
+
+ IDD_EXPORTTYPE, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 222
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 64
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_ML_IMPEX_DESC "Nullsoft Database Import / Export v%s"
+ 65535 "{22661553-8D22-4012-8D3B-0FF8FE57A9ED}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_IMPORT_DATABASE "Import Media Database..."
+ IDS_EXPORT_DATABASE "Export Media Database..."
+ IDS_TRACKS_IMPORTED_X "Tracks imported : %d"
+ IDS_TRACKS_EXPORTED_X "Tracks exported : %d"
+ IDS_ITUNES_XML_LIBRARY "iTunes XML Library"
+ IDS_LOADING_XML "Loading XML..."
+ IDS_WRITINGING_XML "Writing XML..."
+ IDS_IMPORTING_DATABASE "Importing Database"
+ IDS_EXPORTING_DATABASE "Exporting Database"
+ IDS_IMPORT_ITUNES_DB "Import iTunes Library"
+ IDS_IMPORT_ITUNES_PL "Import iTunes Playlists"
+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_impex/ml_impex.sln b/Src/Plugins/Library/ml_impex/ml_impex.sln
new file mode 100644
index 00000000..e5e06ad5
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ml_impex.sln
@@ -0,0 +1,50 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_impex", "ml_impex.vcxproj", "{DDD08D55-B0E6-4070-9C67-39B495A0B4ED}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plist", "..\plist\plist.vcxproj", "{5ED1729B-EA41-4163-9506-741A8B76F625}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ Debug|x64 = Debug|x64
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Debug|Win32.Build.0 = Debug|Win32
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Debug|x64.ActiveCfg = Debug|x64
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Debug|x64.Build.0 = Debug|x64
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Release|Win32.ActiveCfg = Release|Win32
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Release|Win32.Build.0 = Release|Win32
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Release|x64.ActiveCfg = Release|x64
+ {DDD08D55-B0E6-4070-9C67-39B495A0B4ED}.Release|x64.Build.0 = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.Build.0 = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.Build.0 = Release|x64
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|Win32.ActiveCfg = Debug|Win32
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|Win32.Build.0 = Debug|Win32
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|x64.ActiveCfg = Debug|x64
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|x64.Build.0 = Debug|x64
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|Win32.ActiveCfg = Release|Win32
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|Win32.Build.0 = Release|Win32
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|x64.ActiveCfg = Release|x64
+ {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1C93F5AD-DB79-444D-A5CE-58652C31D19F}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_impex/ml_impex.vcxproj b/Src/Plugins/Library/ml_impex/ml_impex.vcxproj
new file mode 100644
index 00000000..c1cf536d
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ml_impex.vcxproj
@@ -0,0 +1,317 @@
+<?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>{DDD08D55-B0E6-4070-9C67-39B495A0B4ED}</ProjectGuid>
+ <RootNamespace>ml_impex</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_IMPEX_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_IMPEX_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_IMPEX_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_IMPEX_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ </Link>
+ <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>
+ <ProjectReference Include="..\..\..\plist\plist.vcxproj">
+ <Project>{5ed1729b-ea41-4163-9506-741a8b76f625}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\bfc\bfc.vcxproj">
+ <Project>{d0ec862e-dddd-4f4f-934f-b75dc9062dc1}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="impex.cpp" />
+ <ClCompile Include="importer.cpp" />
+ <ClCompile Include="ImporterAPI.cpp" />
+ <ClCompile Include="ImportPlaylists.cpp" />
+ <ClCompile Include="itunesxmlwrite.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_impex.h" />
+ <ClInclude Include="api_importer.h" />
+ <ClInclude Include="importer.h" />
+ <ClInclude Include="ImporterAPI.h" />
+ <ClInclude Include="itunesxmlwrite.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_impex.rc" />
+ </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_impex/ml_impex.vcxproj.filters b/Src/Plugins/Library/ml_impex/ml_impex.vcxproj.filters
new file mode 100644
index 00000000..7db52b4c
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/ml_impex.vcxproj.filters
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="impex.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="importer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ImporterAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ImportPlaylists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="itunesxmlwrite.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_impex.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_importer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="importer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ImporterAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="itunesxmlwrite.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{367262fe-81b8-41c9-a013-150093098f81}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{91f9aff7-f6fc-45c6-9c44-88bf5ec938ce}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{a20e93fc-10cf-4f90-b11f-2e91d427b1b1}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{5957e24d-fc45-47d0-8c55-b6a758580b36}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_impex.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_impex/resource.h b/Src/Plugins/Library/ml_impex/resource.h
new file mode 100644
index 00000000..2b999df1
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/resource.h
@@ -0,0 +1,37 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_impex.rc
+//
+#define IDS_IMPORT_DATABASE 1
+#define IDS_EXPORT_DATABASE 2
+#define IDS_TRACKS_IMPORTED_X 3
+#define IDS_TRACKS_EXPORTED_X 4
+#define IDS_ITUNES_XML_LIBRARY 5
+#define IDS_LOADING_XML 6
+#define IDS_WRITINGING_XML 7
+#define IDS_IMPORTING_DATABASE 8
+#define IDS_EXPORTING_DATABASE 9
+#define IDS_IMPORT_ITUNES_DB 10
+#define IDS_IMPORT_ITUNES_PL 11
+#define IDD_IMPORT 102
+#define IDD_INFODIALOG 102
+#define IDD_EXPORTTYPE 104
+#define IDC_TRACKS 1000
+#define IDC_PROGRESS_PERCENT 1001
+#define IDC_LOADING 1002
+#define IDC_PROCESSING_STATE 1002
+#define IDC_WRITING 1003
+#define IDC_RADIO_ONLYSUPPORTED 1004
+#define IDC_RADIO_ALLFILES 1005
+#define IDS_ML_IMPEX_DESC 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 109
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1007
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_impex/version.rc2 b/Src/Plugins/Library/ml_impex/version.rc2
new file mode 100644
index 00000000..80ed23e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_impex/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,65,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", "2,65,0,0"
+ VALUE "InternalName", "Nullsoft Database Import / Export"
+ VALUE "LegalCopyright", "Copyright © 2009-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_impex.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_local/AlbumArtCache.cpp b/Src/Plugins/Library/ml_local/AlbumArtCache.cpp
new file mode 100644
index 00000000..6478c009
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtCache.cpp
@@ -0,0 +1,508 @@
+#include "main.h"
+#include <nde/nde_c.h>
+#include "../nu/threadname.h"
+#include "AlbumArtContainer.h"
+#include <tataki/bitmap/bitmap.h>
+#include "MD5.h"
+#include "../replicant/nu/AutoLock.h"
+#include <strsafe.h>
+#include <map>
+#include <deque>
+
+static bool GetArtFromCache(const wchar_t *filename, int size, ARGB32 **bits);
+static bool SetArtCache(const wchar_t *filename, int size, const ARGB32 *bits, uint8_t hash[16]);
+static void CloseArtCache();
+static HANDLE artWake=0;
+static HANDLE mainThread=0;
+
+struct CreateCacheParameters
+{
+ AlbumArtContainer *container;
+ int w;
+ int h;
+ SkinBitmap *cache;
+ AlbumArtContainer::CacheStatus status;
+};
+
+static std::deque<CreateCacheParameters*> artQueue;
+static nu::LockGuard queueGuard;
+
+static void Adjust(int bmpw, int bmph, int &x, int &y, int &w, int &h)
+{
+ // maintain 'square' stretching
+ double aspX = (double)(w)/(double)bmpw;
+ double aspY = (double)(h)/(double)bmph;
+ double asp = min(aspX, aspY);
+ int newW = (int)(bmpw*asp);
+ int newH = (int)(bmph*asp);
+ x = (w - newW)/2;
+ y = (h - newH)/2;
+ w = newW;
+ h = newH;
+}
+
+static void CALLBACK CreateCacheCallbackAPC(ULONG_PTR parameter)
+{
+ CreateCacheParameters *parameters = (CreateCacheParameters *)parameter;
+ parameters->container->SetCache(parameters->cache, parameters->status);
+ parameters->container->Release();
+ WASABI_API_MEMMGR->sysFree(parameters);
+}
+
+static void CreateCache(CreateCacheParameters *parameters)
+{
+ // let's hope this doesn't overflow the stack
+ const wchar_t *filename = parameters->container->filename;
+
+ if ((unsigned int)(ULONG_PTR)filename < 65536)
+ {
+ parameters->cache = 0;
+ parameters->status = AlbumArtContainer::CACHE_NOTFOUND;
+ QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)parameters);
+ return;
+ }
+
+ int w = parameters->w, h = parameters->h;
+
+ ARGB32 *cacheBits = 0;
+ if (GetArtFromCache(filename, w, &cacheBits))
+ {
+ SkinBitmap *cache = new SkinBitmap(cacheBits, w, h, true);
+ parameters->cache = cache;
+ parameters->status = AlbumArtContainer::CACHE_CACHED;
+ }
+ else
+ {
+ int artsize = w;
+ int bmp_w = 0, bmp_h = 0;
+ ARGB32 *bits = 0;
+
+ if (AGAVE_API_ALBUMART && AGAVE_API_ALBUMART->GetAlbumArt(filename, L"Cover", &bmp_w, &bmp_h, &bits) == ALBUMART_SUCCESS)
+ {
+ uint8_t hash[16] = {0};
+ MD5_CTX hashCtx;
+ MD5Init(&hashCtx);
+ MD5Update(&hashCtx, (uint8_t *)bits, bmp_w*bmp_h*sizeof(ARGB32));
+ MD5Final(hash, &hashCtx);
+
+ BltCanvas canvas(w,h);
+ HQSkinBitmap temp(bits, bmp_w,bmp_h); // wrap into a SkinBitmap (no copying involved)
+ int x = 0, y = 0;
+ Adjust(bmp_w, bmp_h, x,y,w,h);
+ temp.stretch(&canvas,x,y,w,h);
+ SkinBitmap *cache = new SkinBitmap(&canvas);
+ parameters->cache = cache;
+ SetArtCache(filename, artsize, (ARGB32 *)canvas.getBits(), hash);
+ WASABI_API_MEMMGR->sysFree(bits);
+ parameters->status = AlbumArtContainer::CACHE_CACHED;
+ }
+ else
+ {
+ parameters->cache = 0;
+ parameters->status = AlbumArtContainer::CACHE_NOTFOUND;
+ }
+ }
+ QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)parameters);
+ return;
+}
+
+static int ArtLoadThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ // TODO? SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
+
+ queueGuard.Lock();
+ if (artQueue.empty())
+ {
+ queueGuard.Unlock();
+ return 0;
+ }
+
+ CreateCacheParameters *parameters = artQueue.front();
+ artQueue.pop_front();
+ queueGuard.Unlock();
+ CreateCache(parameters);
+ return 0;
+}
+
+enum
+{
+ ARTHASH_FILENAME = 0,
+ ARTHASH_HASH = 1,
+
+ ARTTABLE_HASH = 0,
+ ARTTABLE_ARGB32 = 1,
+};
+
+typedef std::map<int, nde_table_t> ArtCache;
+static ArtCache artcache;
+static nde_database_t art_db = 0;
+static nde_table_t artHashes = 0;
+
+static ThreadID *artThread=0;
+static void InitArtThread()
+{
+ if (!artThread)
+ {
+ artWake = CreateSemaphore(0, 0, 65536, 0);
+ mainThread = WASABI_API_APP->main_getMainThreadHandle();
+ artThread = WASABI_API_THREADPOOL->ReserveThread(0);
+ WASABI_API_THREADPOOL->AddHandle(artThread, artWake, ArtLoadThread, 0, 0, 0);
+ }
+}
+
+static int ArtKillThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ WASABI_API_THREADPOOL->RemoveHandle(artThread, artWake);
+ CloseArtCache();
+ SetEvent((HANDLE)user_data);
+ return 0;
+}
+
+void KillArtThread()
+{
+ if (artThread)
+ {
+ HANDLE artKill = CreateEvent(0, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(artThread, ArtKillThreadPoolFunc, (void *)artKill, 0, 0);
+ WaitForSingleObject(artKill, INFINITE);
+ CloseHandle(mainThread); mainThread = 0;
+ CloseHandle(artKill); artKill = 0;
+ CloseHandle(artWake); artWake = 0;
+ WASABI_API_THREADPOOL->ReleaseThread(artThread);
+ }
+}
+
+void MigrateArtCache()
+{
+ int size[] = {0, 60, 90, 120, 180};
+
+ for (int i = 0; i < sizeof(size)/sizeof(size[0]); i++)
+ {
+ if (!size[i])
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ art_db = NDE_CreateDatabase(plugin.hDllInstance);
+ wchar_t tableName[MAX_PATH] = {0}, oldTableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0}, oldIndexName[MAX_PATH] = {0};
+ PathCombineW(oldIndexName, inidir, L"Plugins\\ml");
+ PathCombineW(indexName, inidir, L"Plugins\\ml\\art");
+ CreateDirectoryW(indexName, NULL);
+ PathCombineW(tableName, indexName, L"art.dat");
+ PathCombineW(oldTableName, oldIndexName, L"art.dat");
+ PathAppendW(indexName, L"art.idx");
+ PathAppendW(oldIndexName, L"art.idx");
+
+ // migrate files to their own 'art' sub-folder
+ if (PathFileExistsW(oldIndexName) && !PathFileExistsW(indexName))
+ {
+ MoveFileW(oldIndexName, indexName);
+ MoveFileW(oldTableName, tableName);
+ }
+ }
+ else if (size[i])
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t temp[MAX_PATH] = {0}, tableName[MAX_PATH] = {0}, oldTableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0}, oldIndexName[MAX_PATH] = {0};
+ PathCombineW(temp, inidir, L"Plugins\\ml");
+ StringCchPrintfW(tableName, MAX_PATH, L"%s\\art\\art_%d.dat", temp, size[i]);
+ StringCchPrintfW(oldTableName, MAX_PATH, L"%s\\art_%d.dat", temp, size[i]);
+ StringCchPrintfW(indexName, MAX_PATH, L"%s\\art\\art_%d.idx", temp, size[i]);
+ StringCchPrintfW(oldIndexName, MAX_PATH, L"%s\\art_%d.idx", temp, size[i]);
+
+ // migrate files to their own 'art' sub-folder
+ if (PathFileExistsW(oldIndexName) && !PathFileExistsW(indexName))
+ {
+ MoveFileW(oldIndexName, indexName);
+ MoveFileW(oldTableName, tableName);
+ }
+ }
+ }
+}
+
+static bool InitArtCache(int size)
+{
+ if (!art_db)
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ art_db = NDE_CreateDatabase(plugin.hDllInstance);
+ wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(indexName, inidir, L"Plugins\\ml\\art");
+ PathCombineW(tableName, indexName, L"art.dat");
+ PathAppendW(indexName, L"art.idx");
+
+ artHashes = NDE_Database_OpenTable(art_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
+ NDE_Table_NewColumnW(artHashes, ARTHASH_FILENAME, DB_FIELDNAME_filename, FIELD_FILENAME);
+ NDE_Table_NewColumnW(artHashes, ARTHASH_HASH, L"hash", FIELD_INT128);
+ NDE_Table_PostColumns(artHashes);
+ NDE_Table_AddIndexByIDW( artHashes, ARTHASH_FILENAME, DB_FIELDNAME_filename );
+ }
+
+ if (size && !artcache[size])
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t temp[MAX_PATH] = {0}, tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(temp, inidir, L"Plugins\\ml");
+ StringCchPrintfW(tableName, MAX_PATH, L"%s\\art\\art_%d.dat", temp, size);
+ StringCchPrintfW(indexName, MAX_PATH, L"%s\\art\\art_%d.idx", temp, size);
+
+ nde_table_t artTable = NDE_Database_OpenTable(art_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_NOCACHE);
+ NDE_Table_NewColumnW(artTable, ARTTABLE_HASH, L"hash", FIELD_INT128);
+ NDE_Table_NewColumnW(artTable, ARTTABLE_ARGB32, L"art", FIELD_BINARY32);
+ NDE_Table_PostColumns(artTable);
+ NDE_Table_AddIndexByIDW(artTable, ARTTABLE_HASH, L"hash");
+ artcache[size] = artTable;
+ }
+ else
+ return artHashes != 0;
+
+ return artcache[size] != 0;
+}
+static void CloseArtHashes()
+{
+ __try
+ {
+ if (artHashes)
+ {
+ NDE_Database_CloseTable(art_db, artHashes);
+ }
+ artHashes = 0;
+
+ NDE_DestroyDatabase(art_db);
+ art_db = 0;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ artHashes = 0;
+ art_db = 0;
+ }
+}
+static void CloseArtCache()
+{
+ ArtCache::iterator itr;
+ for (itr = artcache.begin(); itr != artcache.end(); itr++)
+ {
+ if (itr->second)
+ NDE_Database_CloseTable(art_db, itr->second);
+ itr->second = 0;
+ }
+ artcache.clear();
+
+ CloseArtHashes();
+}
+/*
+@param size = art dimensions. e.g. size==120 is for 120x120 album art
+@param bits better be allocated to size*size*sizeof(ARGB32) or you're in for a world of hurt
+*/
+static bool GetArtFromCache(const wchar_t *filename, int size, ARGB32 **bits)
+{
+ if (InitArtCache(size))
+ {
+ nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
+ if (NDE_Scanner_LocateFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
+ if (field)
+ {
+ nde_scanner_t artscanner = NDE_Table_CreateScanner(artcache[size]);
+ if (NDE_Scanner_LocateField(artscanner, ARTTABLE_HASH, FIRST_RECORD, field))
+ {
+ nde_field_t field = NDE_Scanner_GetFieldByID(artscanner, ARTTABLE_ARGB32);
+ if (field)
+ {
+ size_t len = 0;
+ void *data = NDE_BinaryField_GetData(field, &len);
+ if (data && len == size*size*sizeof(ARGB32))
+ {
+ *bits = (ARGB32 *)WASABI_API_MEMMGR->sysMalloc(len);
+ memcpy(*bits, data, len);
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ return true;
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ }
+ }
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ }
+ return false;
+}
+
+static bool SetArtCache(const wchar_t *filename, int size, const ARGB32 *bits, uint8_t hash[16])
+{
+ if (InitArtCache(size))
+ {
+ nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
+ if (!NDE_Scanner_LocateFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
+ {
+ NDE_Scanner_New(hashscanner);
+ db_setFieldStringW(hashscanner, ARTHASH_FILENAME, filename);
+ }
+ else
+ NDE_Scanner_Edit(hashscanner);
+
+ nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
+ if (!field)
+ field = NDE_Scanner_NewFieldByID(hashscanner, ARTHASH_HASH);
+ NDE_Int128Field_SetValue(field, hash);
+
+ NDE_Scanner_Post(hashscanner);
+
+ nde_scanner_t artscanner = NDE_Table_CreateScanner(artcache[size]);
+ if (!NDE_Scanner_LocateField(artscanner, ARTTABLE_HASH, FIRST_RECORD, field))
+ {
+ NDE_Scanner_New(artscanner);
+ field = NDE_Scanner_NewFieldByID(artscanner, ARTTABLE_HASH);
+ NDE_Int128Field_SetValue(field, hash);
+ field = NDE_Scanner_NewFieldByID(artscanner, ARTTABLE_ARGB32);
+ // TODO when size is possibly zero, this is causing a crash in nde.dll
+ if (size > 0) NDE_BinaryField_SetData(field, bits, size*size*sizeof(ARGB32));
+ NDE_Scanner_Post(artscanner);
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ NDE_Table_Sync(artHashes);
+ return true;
+ }
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ NDE_Table_Sync(artHashes);
+ }
+
+ return false;
+}
+
+size_t maxCache = 65536/*100*/;
+void HintCacheSize(int _cachesize)
+{
+ maxCache = _cachesize;
+}
+
+void CreateCache(AlbumArtContainer *container, int w, int h)
+{
+ InitArtThread();
+ assert(artThread);
+ container->AddRef();
+ CreateCacheParameters *parameters = (CreateCacheParameters *)WASABI_API_MEMMGR->sysMalloc(sizeof(CreateCacheParameters));
+ parameters->container = container;
+ parameters->w = w;
+ parameters->h = h;
+ parameters->status = AlbumArtContainer::CACHE_UNKNOWN;
+ nu::AutoLock lock(queueGuard);
+ if (artQueue.size() > maxCache)
+ {
+ CreateCacheParameters *kill = artQueue.back();
+ artQueue.pop_back();
+ kill->cache = 0;
+ kill->status = AlbumArtContainer::CACHE_UNKNOWN;
+ QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)kill);
+ }
+ artQueue.push_front(parameters);
+ ReleaseSemaphore(artWake, 1, 0);
+}
+
+void FlushCache()
+{
+ nu::AutoLock lock(queueGuard);
+ while (!artQueue.empty())
+ {
+ CreateCacheParameters *kill = artQueue.front();
+ kill->container->SetCache(0, AlbumArtContainer::CACHE_UNKNOWN);
+ artQueue.pop_front();
+ WASABI_API_MEMMGR->sysFree(kill);
+ }
+}
+
+void ResumeCache()
+{
+}
+
+static int ClearFilenameCacheAPC(HANDLE handle, void *param, intptr_t id)
+{
+ wchar_t *filename = (wchar_t *)param;
+
+ if (InitArtCache(0))
+ {
+ nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
+ if (NDE_Scanner_LocateNDEFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
+ if (field)
+ {
+ nde_scanner_t deleteAllScanner = NDE_Table_CreateScanner(artHashes);
+ while (NDE_Scanner_LocateField(deleteAllScanner, ARTHASH_HASH, FIRST_RECORD, field))
+ {
+ NDE_Scanner_Delete(deleteAllScanner);
+ NDE_Scanner_Post(deleteAllScanner);
+ }
+ NDE_Table_DestroyScanner(artHashes, deleteAllScanner);
+ NDE_Table_Sync(artHashes);
+
+ /* delete it from the art table as well, but we'll just
+ use the already-opened ones */
+ for (ArtCache::iterator itr=artcache.begin();itr!=artcache.end();itr++)
+ {
+ nde_table_t table = itr->second;
+ if (table)
+ {
+ nde_scanner_t s= NDE_Table_CreateScanner(table);
+ while (NDE_Scanner_LocateField(s, ARTTABLE_HASH, FIRST_RECORD, field))
+ {
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ }
+ NDE_Table_DestroyScanner(table, s);
+ NDE_Table_Sync(table);
+ }
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ }
+ ndestring_release(filename);
+ return 0;
+}
+
+static int DeleteDatabaseAPC(HANDLE handle, void *user_data, intptr_t id)
+{
+ CloseArtCache();
+
+ wchar_t search_mask[MAX_PATH] = {0};
+ StringCchPrintfW(search_mask, MAX_PATH, L"%s\\art_*.*", g_tableDir);
+
+ wchar_t fn[MAX_PATH] = {0};
+ StringCchPrintfW(fn, MAX_PATH, L"%s\\art.idx", g_tableDir);
+ DeleteFileW(fn);
+ StringCchPrintfW(fn, MAX_PATH, L"%s\\art.dat", g_tableDir);
+ DeleteFileW(fn);
+
+ WIN32_FIND_DATAW findData;
+ HANDLE hFind = FindFirstFileW(search_mask, &findData);
+ if (hFind != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ if (!_wcsnicmp(findData.cFileName, L"art_", 4))
+ {
+ StringCchPrintfW(fn, MAX_PATH, L"%s\\%s", g_tableDir, findData.cFileName);
+ DeleteFileW(fn);
+ }
+ }
+ while (FindNextFileW(hFind, &findData));
+ FindClose(hFind);
+ }
+ return 0;
+}
+
+void DumpArtCache()
+{
+ InitArtThread();
+ assert(artThread);
+ WASABI_API_THREADPOOL->RunFunction(artThread, DeleteDatabaseAPC, 0, 0, 0);
+}
+
+void ClearCache(const wchar_t *filename)
+{
+ InitArtThread();
+ assert(artThread);
+ WASABI_API_THREADPOOL->RunFunction(artThread, ClearFilenameCacheAPC, ndestring_wcsdup(filename), 0, 0);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtCache.h b/Src/Plugins/Library/ml_local/AlbumArtCache.h
new file mode 100644
index 00000000..c413e0ee
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtCache.h
@@ -0,0 +1,14 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMARTCACHE_H
+#define NULLSOFT_ML_LOCAL_ALBUMARTCACHE_H
+
+#include "AlbumArtContainer.h"
+void KillArtThread();
+void CreateCache(AlbumArtContainer *container, int w, int h);
+void FlushCache();
+void ResumeCache();
+
+void HintCacheSize(int _cachesize);
+
+void ClearCache(const wchar_t *filename);
+void DumpArtCache();
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtContainer.cpp b/Src/Plugins/Library/ml_local/AlbumArtContainer.cpp
new file mode 100644
index 00000000..9b06b288
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtContainer.cpp
@@ -0,0 +1,80 @@
+#include "AlbumArtContainer.h"
+#include "api__ml_local.h"
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/canvas.h>
+#include <assert.h>
+#include "AlbumArtCache.h"
+#include "../nde/nde.h"
+#include <commctrl.h>
+
+AlbumArtContainer::AlbumArtContainer() :filename(0)
+{
+ references = 1;
+ cached = CACHE_UNKNOWN;
+ cache = 0;
+ updateMsg.hwnd = NULL;
+}
+
+void AlbumArtContainer::AddRef()
+{
+ references++;
+}
+
+void AlbumArtContainer::Release()
+{
+ if (--references == 0)
+ delete this;
+}
+
+AlbumArtContainer::~AlbumArtContainer()
+{
+ ndestring_release(filename);
+ if (cache) cache->Release();
+}
+
+void AlbumArtContainer::Reset()
+{
+ if (cache)
+ cache->Release();
+ cache = 0;
+ cached = AlbumArtContainer::CACHE_UNKNOWN;
+}
+
+void AlbumArtContainer::SetCache(SkinBitmap *bitmap, CacheStatus status)
+{
+ if (cache)
+ cache->Release();
+ cache = bitmap;
+ cached = status;
+
+ // lets post the update message
+ if(updateMsg.hwnd && (CACHE_CACHED == cached || CACHE_UNKNOWN == cached)) PostMessageW(updateMsg.hwnd,updateMsg.message,updateMsg.wParam,updateMsg.lParam);
+}
+
+int AlbumArtContainer::drawArt(DCCanvas *pCanvas, RECT *prcDst)
+{
+ switch (cached)
+ {
+ case CACHE_NOTFOUND:
+ return DRAW_NOART;
+ case CACHE_LOADING:
+ return DRAW_LOADING;
+ case CACHE_UNKNOWN:
+ cached = CACHE_LOADING;
+ CreateCache(this, prcDst->right - prcDst->left, prcDst->bottom - prcDst->top);
+ return DRAW_LOADING;
+ case CACHE_CACHED:
+ {
+ if (cache->getWidth() != (prcDst->right - prcDst->left) || cache->getHeight() != (prcDst->bottom - prcDst->top))
+ {
+ cached = CACHE_LOADING;
+ CreateCache(this, prcDst->right - prcDst->left, prcDst->bottom - prcDst->top);
+ return DRAW_LOADING;
+ }
+ if (pCanvas) cache->blitAlpha(pCanvas, prcDst->left, prcDst->top);
+ return DRAW_SUCCESS;
+ }
+ default:
+ return DRAW_NOART; // shouldn't reach this;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtContainer.h b/Src/Plugins/Library/ml_local/AlbumArtContainer.h
new file mode 100644
index 00000000..988721ff
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtContainer.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMARTCONTAINER_H
+#define NULLSOFT_ML_LOCAL_ALBUMARTCONTAINER_H
+
+#include <windows.h> // for HDC
+#include <tataki/canvas/bltcanvas.h>
+
+class AlbumArtContainer
+{
+public:
+ enum CacheStatus
+ {
+ CACHE_UNKNOWN,
+ CACHE_CACHED,
+ CACHE_NOTFOUND,
+ CACHE_LOADING,
+ };
+
+ AlbumArtContainer();
+ enum
+ {
+ DRAW_SUCCESS,
+ DRAW_NOART,
+ DRAW_LOADING,
+ };
+ int drawArt(DCCanvas *pCanvas, RECT *prcDst);
+ // benski> this definition is just temporary to get things going
+
+ void AddRef();
+ void Release();
+ wchar_t *filename; // actually an NDE reference counted string
+ MSG updateMsg;
+ void SetCache(SkinBitmap *bitmap, CacheStatus status);
+ void Reset();
+private:
+ ~AlbumArtContainer();
+ SkinBitmap * volatile cache;
+
+ volatile CacheStatus cached;
+ size_t references;
+
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtFilter.cpp b/Src/Plugins/Library/ml_local/AlbumArtFilter.cpp
new file mode 100644
index 00000000..d7295571
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtFilter.cpp
@@ -0,0 +1,1258 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "../nu/sort.h"
+#include "AlbumArtFilter.h"
+#include "resource.h"
+#include "../nu/AutoUrl.h"
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "AlbumArtContainer.h"
+#include <tataki/canvas/canvas.h>
+#include <tataki/bitmap/autobitmap.h>
+#include <api/service/waServiceFactory.h>
+#include <api/service/svcs/svc_imgload.h>
+
+static size_t m_sort_by, m_sort_dir, m_sort_which;
+
+void emptyQueryListObject(queryListObject *obj);
+int reallocQueryListObject(queryListObject *obj);
+void freeQueryListObject(queryListObject *obj);
+extern C_Config *g_config;
+int config_use_alternate_colors=1;
+
+#define WM_EX_GETREALLIST (WM_USER + 0x01)
+#define WM_EX_GETCOUNTPERPAGE (WM_USER + 0x04)
+#define LVN_EX_SIZECHANGED (LVN_LAST)
+
+AlbumArtFilter::AlbumArtFilter(HWND hwndDlg, int dlgitem, C_Config *c) : hwndDlg(hwndDlg), dlgitem(dlgitem),
+ notfound(L"winamp.cover.notfound"), notfound60(L"winamp.cover.notfound.60"), notfound90(L"winamp.cover.notfound.90"),
+ hbmpNames(NULL), ratingHotItem((DWORD)-1), bgBrush(NULL)
+{
+ mode = c->ReadInt(L"albumartviewmode",1);
+ icons_only = c->ReadInt(L"albumarticonmode",0);
+ ZeroMemory(classicnotfound, sizeof(classicnotfound));
+ DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+}
+
+AlbumArtFilter::~AlbumArtFilter()
+{
+ if (hbmpNames) DeleteObject(hbmpNames);
+ if (bgBrush) DeleteBrush(bgBrush);
+}
+
+static COLORREF GetWAColor(INT index)
+{
+ static int (*wad_getColor)(int idx) = NULL;
+ if (!wad_getColor) *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ return (wad_getColor) ? wad_getColor(index) : 0xFF00FF;
+}
+
+static void getImgSize(int mode, int &w, int &h)
+{
+ switch (mode)
+ {
+ case 0: w=h=60; break;
+ case 1: w=h=90; break;
+ case 2: w=h=120; break;
+ case 3: w=h=60; break;
+ case 4: w=h=90; break;
+ case 5: w=h=120; break;
+ case 6: w=h=180; break;
+ }
+}
+
+static bool isDetailsMode(int mode)
+{
+ return mode == 0 || mode == 1 || mode == 2;
+}
+
+void DrawRect(HDC dc, int x, int y, int w, int h)
+{
+ w-=1;
+ h-=1;
+ MoveToEx(dc,x,y,NULL);
+ LineTo(dc,x,y+h);
+ MoveToEx(dc,x,y+h,NULL);
+ LineTo(dc,x+w,y+h);
+ MoveToEx(dc,x+w,y+h,NULL);
+ LineTo(dc,x+w,y);
+ MoveToEx(dc,x+w,y,NULL);
+ LineTo(dc,x,y);
+}
+
+static int getStrExtent(HDC dc, const wchar_t * s)
+{
+ int ret=0;
+ while (s && *s)
+ {
+ int f;
+ GetCharWidth32(dc,*s,*s,&f);
+ s++;
+ ret+=f;
+ }
+ return int(ret);
+}
+
+ARGB32 * loadImg(const void * data, int len, int *w, int *h, bool ldata=false)
+{
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for (int i=0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if (sf)
+ {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if (l)
+ {
+ if (l->testData(data,len))
+ {
+ ARGB32* ret;
+ if (ldata) ret = l->loadImageData(data,len,w,h);
+ else ret = l->loadImage(data,len,w,h);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+ARGB32 * loadRrc(int id, char * sec, int *w, int *h, bool data=false)
+{
+ DWORD size = 0;
+ // as a nice little treat, allow lang packs to contain a custom IDR_IMAGE_NOTFOUND file
+ HGLOBAL resourceHandle = WASABI_API_LOADRESFROMFILEA((LPCSTR)sec, MAKEINTRESOURCEA(id), &size);
+ if(resourceHandle)
+ {
+ ARGB32* ret = loadImg(resourceHandle,size,w,h,data);
+ UnlockResource(resourceHandle);
+ return ret;
+ }
+ return NULL;
+}
+
+void adjustbmp(ARGB32 * p, int len, COLORREF fg)
+{
+ ARGB32 * end = p+len;
+ while (p < end)
+ {
+ int a = (*p>>24)&0xff ;
+ int b = a*((*p&0xff) * (fg&0xff)) / (0xff*0xff);
+ int g = a*(((*p>>8)&0xff) * ((fg>>8)&0xff)) / (0xff*0xff);
+ int r = a*(((*p>>16)&0xff) * ((fg>>16)&0xff)) / (0xff*0xff);
+ *p = (a<<24) | (r&0xff) | ((g&0xff)<<8) | ((b&0xff)<<16);
+ p++;
+ }
+}
+
+void AlbumArtFilter::drawArt(AlbumArtContainer *art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex)
+{
+ int res = AlbumArtContainer::DRAW_NOART;
+ if (art)
+ {
+ art->updateMsg.hwnd = hwndRealList;
+ art->updateMsg.message = LVM_REDRAWITEMS;
+ art->updateMsg.lParam = itemid;
+ art->updateMsg.wParam = itemid;
+ res = art->drawArt(pCanvas, prcDst);
+ }
+
+ if (res != AlbumArtContainer::DRAW_SUCCESS)
+ {
+ SkinBitmap *noart;
+
+ // Martin> This will just work for Modern Skins, but looks definitly better
+ int h = prcDst->right - prcDst->left;
+ if (h == 60)
+ noart = notfound60.getBitmap();
+ else if (h == 90)
+ noart = notfound90.getBitmap();
+ else
+ noart = notfound.getBitmap();
+
+ if (!noart || noart->isInvalid())
+ {
+ if(classicnotfound[imageIndex])
+ SkinBitmap(classicnotfound[imageIndex],classicnotfoundW,classicnotfoundH).stretchToRectAlpha(pCanvas, prcDst);
+ else
+ {
+ int w = prcDst->right - prcDst->left;
+ int h = prcDst->bottom - prcDst->top;
+ DrawRect(pCanvas->getHDC(), prcDst->left, prcDst->top, w, h);
+ wchar_t str1[32] = {0}, str2[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_IMAGE,str1,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_AVAILABLE,str2,32);
+ ExtTextOutW(pCanvas->getHDC(), w/2 - 22 + prcDst->left, w/2 - 14 + prcDst->top, 0,NULL,str1,wcslen(str1),0);
+ ExtTextOutW(pCanvas->getHDC(), w/2 - 22 + prcDst->left, w/2 + 1 + prcDst->top, 0,NULL,str2,wcslen(str2),0);
+ }
+ }
+ else
+ noart->stretchToRectAlpha(pCanvas, prcDst);
+ }
+}
+
+// icon view
+BOOL AlbumArtFilter::DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive)
+{
+ HDC hdc = plvcd->nmcd.hdc;
+ RECT ri, re, rcText;
+ int w=0, h=0, imageIndex=0;
+ getImgSize(mode,w,h);
+
+ SetBkColor(hdc, plvcd->clrTextBk);
+ SetTextColor(hdc, plvcd->clrText);
+ SetRect(&rcText, plvcd->nmcd.rc.left, (plvcd->nmcd.rc.bottom - textHeight),
+ plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - (icons_only ? textHeight : 4));
+
+ // background
+ SetRect(&re, plvcd->nmcd.rc.left, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, (icons_only ? (plvcd->nmcd.rc.bottom - textHeight - 5) : rcText.top));
+
+ if (IntersectRect(&ri, &re, prcClip))
+ ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ if (LVIS_SELECTED & itemState)
+ imageIndex = (bWndActive) ? 1 : 2;
+
+ INT of = ((plvcd->nmcd.rc.right - plvcd->nmcd.rc.left) - w)/2;
+ SetRect(&re, plvcd->nmcd.rc.left + of, plvcd->nmcd.rc.top + 2, plvcd->nmcd.rc.left + of + w, plvcd->nmcd.rc.top + 2 + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ AlbumArtContainer *art = GetArt(plvcd->nmcd.dwItemSpec+1);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ if (IntersectRect(&ri, &rcText, prcClip))
+ {
+ ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ const wchar_t *p = GetText(plvcd->nmcd.dwItemSpec);
+ if (p && *p)
+ {
+ SetRect(&ri, rcText.left + of, rcText.top - (textHeight/2) - 1, rcText.right - of, rcText.top + textHeight);
+ DrawTextW(hdc, p, -1, &ri, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_NOPREFIX);
+ }
+ }
+
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ /*SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));*/
+ SetBkColor(hdc, GetWAColor(WADLG_ITEMBG));
+
+ if (icons_only)
+ {
+ InflateRect(&re, 2, 2);
+ }
+ else
+ plvcd->nmcd.rc.bottom -= 4;
+
+ DrawFocusRect(hdc, (icons_only ? &re : &plvcd->nmcd.rc));
+ }
+ }
+
+ return TRUE;
+}
+
+//detail view
+BOOL AlbumArtFilter::PrepareDetails(HDC hdc)
+{
+ INT width(0), height, len;
+ HFONT hFont,hFontBold, hOldFont;
+ HDC hdcTmp;
+ HBITMAP hbmpOld;
+ LOGFONT l={0};
+ RECT ri;
+ wchar_t ratingstr[100] = {0}, buf[100] = {0};
+
+ hdcTmp = CreateCompatibleDC(hdc);
+ if (!hdcTmp) return FALSE;
+
+ hFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
+ GetObject(hFont, sizeof(LOGFONT), &l);
+ l.lfWeight = FW_BOLD;
+ hFontBold = CreateFontIndirect(&l);
+
+ hOldFont = (HFONT)SelectObject(hdcTmp, hFontBold);
+
+ int bypassColumnCount = 0;
+
+ for ( ListField *l_showcolumn : showncolumns )
+ {
+ if ( l_showcolumn->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ bypassColumnCount++;
+ continue;
+ }
+ int of = getStrExtent(hdcTmp, l_showcolumn->name);
+ if (of > width) width = of;
+ }
+ if (width) width += 20;
+
+ height = (showncolumns.size()-bypassColumnCount) * textHeight;
+ hbmpNames = CreateCompatibleBitmap(hdc, width * 4, height);
+ hbmpOld = (HBITMAP)SelectObject(hdcTmp, hbmpNames);
+
+ SetRect(&ri, 0, 0, width, height);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_RATING,ratingstr,100);
+ INT clrText[4] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, WADLG_ITEMFG2, };
+ INT clrBk[4] = { WADLG_ITEMBG, WADLG_SELBAR_BGCOLOR, WADLG_INACT_SELBAR_BGCOLOR, WADLG_ITEMBG2, };
+
+ for (int j = 0; j < 4; j++)
+ {
+ SetTextColor(hdcTmp, GetWAColor(clrText[j]));
+ SetBkColor(hdcTmp, GetWAColor(clrBk[j]));
+
+ ExtTextOutW(hdcTmp,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ for (size_t i=0, top = 0; i < showncolumns.size(); i++, top += textHeight)
+ {
+ if (showncolumns[i]->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ top -= textHeight;
+ continue;
+ }
+ if (-1 == ratingrow && 0 == lstrcmpW(showncolumns[i]->name, ratingstr)) ratingrow = i;
+ StringCchCopyW(buf, 100, (showncolumns[i]->name) ? showncolumns[i]->name : L"");
+ len = wcslen(buf);
+ if (len > 0 && len < 99) { buf[len] = L':'; len ++; }
+ ExtTextOutW(hdcTmp, ri.left + 1, top, ETO_CLIPPED, &ri, buf, len, NULL);
+ }
+ OffsetRect(&ri, width, 0);
+ }
+
+ SelectObject(hdcTmp, hbmpOld);
+ SelectObject(hdcTmp, hOldFont);
+ DeleteObject(hFontBold);
+ DeleteDC(hdcTmp);
+ return TRUE;
+}
+
+BOOL AlbumArtFilter::DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNames, INT namesWidth)
+{
+ RECT ri, re;
+ INT imageIndex = 0;
+ HDC hdc = plvcd->nmcd.hdc;
+
+ SetTextColor(hdc, plvcd->clrText);
+ SetBkColor(hdc, plvcd->clrTextBk);
+
+ // background
+ SetRect(&re, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if (IntersectRect(&ri, &re, prcClip)) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+ if (LVIS_SELECTED & itemState) { imageIndex = (bWndActive) ? 1 : 2; }
+
+ int w, h;
+ getImgSize(mode, w, h);
+ SetRect(&re, 6+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, 6+ plvcd->nmcd.rc.left + w, 3+ plvcd->nmcd.rc.top + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ AlbumArtContainer *art = GetArt(plvcd->nmcd.dwItemSpec + 1);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ // text
+ int limCY, limCX;
+
+ //select 4th bitmap for alternate coloring
+ if (g_config->ReadInt(L"alternate_items", config_use_alternate_colors) && imageIndex==0 && plvcd->nmcd.dwItemSpec%2)
+ imageIndex=3;
+
+ limCY = plvcd->nmcd.rc.bottom;
+ if (prcClip->bottom < plvcd->nmcd.rc.bottom) limCY = prcClip->bottom;
+ limCX = plvcd->nmcd.rc.right -1;
+ if (prcClip->right < plvcd->nmcd.rc.right) limCX = prcClip->right;
+
+ SetRect(&ri, w+16+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, limCX, limCY);
+
+ if (hdcNames && ri.left < ri.right)
+ {
+ BitBlt(hdc, ri.left, ri.top, min(ri.right - ri.left, namesWidth), ri.bottom - ri.top, hdcNames, namesWidth*imageIndex, 0, SRCCOPY);
+ ri.left += namesWidth;
+ }
+
+ ri.bottom = ri.top;
+
+ if (ri.left < ri.right)
+ {
+ for (size_t i=0; i < showncolumns.size() && ri.top < limCY; i++, ri.top += textHeight)
+ {
+ if (showncolumns[i]->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ ri.top -= textHeight;
+ continue;
+ }
+
+ wchar_t buf[100] = {0};
+ const wchar_t *p = CopyText2(plvcd->nmcd.dwItemSpec,i,buf,100);
+ ri.bottom += textHeight;
+ if (ri.bottom > limCY) ri.bottom = limCY;
+
+ if ((INT)i == ratingrow) // this is the ratings column, so draw graphical stars
+ {
+ int rating = wcslen(buf);
+ RATINGDRAWPARAMS p = {sizeof(RATINGDRAWPARAMS),hdc,
+ {ri.left, ri.top + ratingTop, ri.right,ri.bottom},
+ rating,5,0,RDS_SHOWEMPTY,NULL,0};
+ if(ratingHotItem == plvcd->nmcd.dwItemSpec) { p.fStyle |= RDS_HOT; p.trackingValue = ratingHotValue; }
+ MLRating_Draw(plugin.hwndLibraryParent,&p);
+ }
+ else {
+ //ri.left = re.left;
+ ri.right = plvcd->nmcd.rc.right;
+ DrawTextW(hdc, p, wcslen(p), &ri, DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_NOCLIP | DT_NOPREFIX);
+ }
+ }
+ }
+
+ // bottom line
+ MoveToEx(hdc,plvcd->nmcd.rc.left + 4,plvcd->nmcd.rc.bottom - 3,NULL);
+ LineTo(hdc,plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 3);
+
+ // focus rect
+ SetRect(&ri, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ /*SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));*/
+ SetBkColor(hdc, GetWAColor(WADLG_ITEMBG));
+ DrawFocusRect(hdc, &ri);
+ }
+ }
+
+ return TRUE;
+}
+
+static HWND CreateSmoothScrollList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"SmoothScrollList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+static HWND CreateHeaderIconList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"HeaderIconList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+BOOL AlbumArtFilter::OnKeyDown(NMLVKEYDOWN *plvkd)
+{
+ switch (plvkd->wVKey)
+ {
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL))
+ {
+ LVITEM item;
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(plvkd->hdr.hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtFilter::OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ bActive = (GetFocus() == hwndRealList);
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemIcon(plvcd, &activeCanvas, itemState, &rcClip, bActive);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtFilter::OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static HDC hdcNames;
+ static HBITMAP hbmpOld;
+ static HPEN penOld;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ {
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ FillRect(plvcd->nmcd.hdc,&plvcd->nmcd.rc,bgBrush);
+
+ if (!hbmpNames) PrepareDetails(plvcd->nmcd.hdc);
+ if (hbmpNames)
+ {
+ BITMAP bi;
+ GetObject(hbmpNames, sizeof(BITMAP), &bi);
+ namesWidth = bi.bmWidth/4;
+ hdcNames = CreateCompatibleDC(plvcd->nmcd.hdc);
+ hbmpOld = (hdcNames) ? (HBITMAP)SelectObject(hdcNames, hbmpNames) : NULL;
+ }
+ else
+ {
+ hdcNames = NULL;
+ namesWidth = 0;
+ }
+ bActive = (GetFocus() == hwndRealList);
+
+ penOld = (HPEN)SelectObject(plvcd->nmcd.hdc, CreatePen(PS_SOLID,1, GetWAColor(WADLG_HILITE)));
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+ }
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemDetail(plvcd, &activeCanvas, itemState, &rcClip, bActive, hdcNames, namesWidth);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ if (hdcNames)
+ {
+ SelectObject(hdcNames, hbmpOld);
+ DeleteDC(hdcNames);
+ }
+ if (penOld)
+ {
+ HPEN pen = (HPEN)SelectObject(plvcd->nmcd.hdc, penOld);
+ if (pen) DeleteObject(pen);
+ penOld = NULL;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtFilter::CalcuateItemHeight(void)
+{
+ HDC hdc;
+ int w, h;
+ HWND hwndList;
+ TEXTMETRIC tm = {0};
+
+ getImgSize(mode, w, h);
+
+ hwndList = GetDlgItem(hwndDlg, dlgitem);
+
+ textHeight = 0;
+
+ hdc = GetDC(hwndDlg);
+ if (hdc)
+ {
+ HFONT hFont = (HFONT)SendMessageW(hwndList, WM_GETFONT, 0, 0L);
+ if (!hFont) hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ HFONT hFontOld = (HFONT)SelectObject(hdc, hFont);
+
+ GetTextMetrics(hdc, &tm);
+ textHeight = tm.tmHeight;
+ SelectObject(hdc, hFontOld);
+ ReleaseDC(hwndDlg, hdc);
+ }
+
+ if (isDetailsMode(mode))
+ {
+ if (textHeight < 14) textHeight = 14;
+ RECT r;
+ MLRating_CalcRect(plugin.hwndLibraryParent, NULL, 5, &r);
+ r.bottom -= r.top;
+
+ if ( r.bottom >= textHeight ) ratingTop = 0;
+ else
+ {
+ if (tm.tmAscent > (r.bottom + (r.bottom/2))) ratingTop = tm.tmAscent - r.bottom;
+ else ratingTop = (textHeight - r.bottom)/2 + 1;
+ }
+
+ int bypassColumnCount = 0;
+ for ( ListField *l_showcolumn : showncolumns )
+ {
+ if ( l_showcolumn->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ bypassColumnCount++;
+ continue;
+ }
+ }
+
+ int newHeight = max(h, textHeight * ((INT)showncolumns.size() - bypassColumnCount)) + 12;
+ if (newHeight != itemHeight)
+ {
+ itemHeight = newHeight;
+ RECT rw;
+ GetWindowRect(hwndList, &rw);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left - 1, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
+ }
+ }
+ else
+ {
+ HIMAGELIST hIL = (HIMAGELIST)SendMessageW(hwndList, LVM_GETIMAGELIST, 0, 0L);
+ if (!hIL || !ImageList_GetIconSize(hIL, &w, &h)) { h += 4; w+= 4; }
+ SendMessageW(hwndList, LVM_SETICONSPACING, 0, MAKELPARAM(w, h + (icons_only ? 0 : textHeight)));
+ if (!tm.tmAveCharWidth) tm.tmAveCharWidth = 2;
+ itemHeight = w / tm.tmAveCharWidth;
+ }
+ return TRUE;
+}
+
+INT_PTR AlbumArtFilter::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ HWND hwnd = GetDlgItem(hwndDlg,dlgitem);
+ RECT r;
+ if (hwnd)
+ {
+ GetWindowRect(hwnd,&r);
+ MapWindowPoints(HWND_DESKTOP, hwndDlg, (POINT*)&r, 2);
+ DestroyWindow(hwnd);
+ }
+ else SetRect(&r, 0, 0, 1, 1);
+
+ if (isDetailsMode(mode))
+ {
+ hwnd = CreateSmoothScrollList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ }
+ else
+ {
+ hwnd = CreateHeaderIconList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ int w=0,h=0;
+ getImgSize(mode,w,h);
+ HIMAGELIST il = ImageList_Create(w + 4,h + 4,ILC_COLOR24,0,1); // add borders
+ ListView_SetImageList(hwnd,il,LVSIL_NORMAL);
+ }
+
+ hwndRealList = (HWND)SendMessageW(hwnd, WM_EX_GETREALLIST, 0, 0L);
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ if (isDetailsMode(mode) && NULL != hwndRealList)
+ MLSkinnedWnd_SetStyle(hwndRealList, MLSkinnedWnd_GetStyle(hwndRealList) | SWLVS_ALTERNATEITEMS);
+ }
+
+ case WM_DISPLAYCHANGE:
+ {
+ if (hbmpNames)
+ DeleteObject(hbmpNames);
+
+ hbmpNames = NULL;
+ ratingrow = -1;
+
+ if(bgBrush)
+ DeleteBrush(bgBrush);
+
+ bgBrush = CreateSolidBrush(GetWAColor(WADLG_ITEMBG));
+ CalcuateItemHeight();
+
+ //TODO
+ //config_use_alternate_colors = g_config->ReadInt("alternate_items", 1);
+
+ int rw,rh;
+ ARGB32 * bmp = loadRrc(IDR_IMAGE_NOTFOUND,"PNG",&rw,&rh,true);
+ classicnotfoundW = rw;
+ classicnotfoundH = rh;
+ INT color[] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, };
+
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i]) WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+ classicnotfound[i] = NULL;
+
+ if(bmp)
+ {
+ if (0 != i)
+ {
+ classicnotfound[i] = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*rw*rh);
+ CopyMemory(classicnotfound[i], bmp, sizeof(ARGB32)*rw*rh);
+ }
+ else
+ classicnotfound[i] = bmp;
+
+ adjustbmp(classicnotfound[i],rw*rh, GetWAColor(color[i]));
+ }
+ }
+
+ if(WM_DISPLAYCHANGE == uMsg)
+ PostMessageW(GetDlgItem(hwndDlg,dlgitem),uMsg,wParam,lParam);
+ else
+ ShowWindow(GetDlgItem(hwndDlg,dlgitem), SW_SHOWNORMAL);
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i])
+ WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+
+ classicnotfound[i] = NULL;
+ }
+ break;
+
+ case WM_MEASUREITEM:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ ((MEASUREITEMSTRUCT*)lParam)->itemHeight = itemHeight;
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+ break;
+
+ case WM_USER+600:
+ AppendMenuW((HMENU)wParam,MF_SEPARATOR,lParam++,0);
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ //AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_GET_ALBUM_ART));
+ AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_REFRESH_ALBUM_ART));
+ AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_OPEN_FOLDER));
+ AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_MORE_ARTIST_INFO));
+ break;
+
+ case WM_USER+601:
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ //RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ break;
+
+ case WM_USER+602:
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ #if 0
+ if (lParam == 1)
+ {
+ int c = list->GetCount();
+ for (int x = 0; x < c; x ++)
+ {
+ if(list->GetSelected(x) && albumList.Items[x+1].art)
+ {
+ artFetchData d = {sizeof(d),hwndDlg,albumList.Items[x+1].artist,albumList.Items[x+1].name,0};
+ d.gracenoteFileId = albumList.Items[x+1].gracenoteFileId;
+ d.showCancelAll = 1;
+ int r = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(LPARAM)&d,IPC_FETCH_ALBUMART);
+ if(r == -2) break; // cancel all was pressed
+ if(r == 0 && d.imgData && d.imgDataLen) // success, save art in correct location
+ {
+ AGAVE_API_ALBUMART->SetAlbumArt(albumList.Items[x+1].art->filename,L"cover",0,0,d.imgData,d.imgDataLen,d.type);
+ WASABI_API_MEMMGR->sysFree(d.imgData);
+ // clear the cache...
+ ClearCache(albumList.Items[x+1].art->filename);
+ albumList.Items[x+1].art->updateMsg.hwnd=0;
+ albumList.Items[x+1].art->Reset();
+ SendMessage(GetDlgItem(hwndDlg,dlgitem),LVM_REDRAWITEMS,x,x);
+ }
+ }
+ }
+ }
+ else
+ #endif
+ if(lParam == 1)
+ {
+ int c = list->GetCount();
+ for (int x = 0; x < c; x ++)
+ {
+ if(list->GetSelected(x) && albumList.Items[x+1].art)
+ {
+ ClearCache(albumList.Items[x+1].art->filename);
+ albumList.Items[x+1].art->updateMsg.hwnd=0;
+ albumList.Items[x+1].art->Reset();
+ SendMessage(GetDlgItem(hwndDlg,dlgitem),LVM_REDRAWITEMS,x,x);
+ }
+ }
+ }
+ else if(lParam == 2)
+ {
+ int opened=0;
+ int c = list->GetCount();
+ for (int x = 0; x < c; x ++)
+ {
+ if((ListView_GetItemState(list->getwnd(), x, LVIS_FOCUSED)&LVIS_FOCUSED) && albumList.Items[x+1].art)
+ {
+ // TODO change to use the explorer api
+ if(opened++ >= 10) break; // that's enough! Opening 400 exploerer windows may seem like fun, but windows _hates_ it.
+ wchar_t fn[MAX_PATH] = {0};
+ lstrcpynW(fn,albumList.Items[x+1].art->filename,MAX_PATH);
+ PathRemoveFileSpecW(fn);
+ ShellExecuteW(NULL,L"open",fn,NULL,NULL,SW_SHOW);
+ }
+ }
+ }
+ else if (lParam == 3)
+ {
+ int sel = list->GetNextSelected();
+ if (sel != LB_ERR)
+ {
+ char url[1024] = {0};
+ StringCchPrintfA(url, 1024, "http://client.winamp.com/nowplaying/artist?artistName=%s&icid=localmediagetartistinfo", AutoUrl(albumList.Items[sel+1].artist));
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(LPARAM)url,IPC_OPEN_URL);
+ }
+ }
+ break;
+
+ case WM_USER+700: // hit-info
+ {
+ DWORD hotold = ratingHotItem;
+ int hotoldval = ratingHotValue;
+ ratingHotItem = (DWORD)-1;
+
+ typedef struct {
+ int x,y,item;
+ HWND hwnd;
+ UINT msg;
+ } hitinfo;
+
+ hitinfo * info = (hitinfo *)wParam;
+ if(mode && info->hwnd == hwndRealList && info->item >= 0 && info->item < albumList.Size)
+ {
+ int w = 0, h = 0;
+ getImgSize(mode,w,h);
+ RATINGHITTESTPARAMS ratingHitTest = {sizeof(ratingHitTest),{info->x,info->y},{16+w+namesWidth,3+textHeight*ratingrow,0,0},5,RDS_NORMAL,NULL,-1,(UINT)-1};
+ ratingHitTest.rc.bottom = ratingHitTest.rc.top + textHeight;
+ ratingHitTest.rc.right = ratingHitTest.rc.left + 200;
+
+ LONG hitVal = MLRating_HitTest(plugin.hwndLibraryParent,&ratingHitTest);
+
+ ratingHitTest.rc.left -= 10; //this 10px is the area in which you can click to set a zero rating (see the PtInRect call below)
+
+ if(!hitVal && !PtInRect(&ratingHitTest.rc, ratingHitTest.pt))
+ {
+ if(hotold >=0) { ReleaseCapture(); ListView_RedrawItems(hwndRealList,hotold,hotold); }
+ break;
+ }
+
+ if(info->msg == WM_MOUSEMOVE)
+ {
+ ratingHotValue = hitVal;
+ ratingHotItem = info->item;
+ if(hotold != ratingHotItem || hotoldval != ratingHotValue)
+ {
+ ListView_RedrawItems(hwndRealList,info->item,info->item);
+ if(hotold >=0 && hotold != ratingHotItem)
+ ListView_RedrawItems(hwndRealList,hotold,hotold);
+
+ SetCapture(hwndRealList);
+ }
+ }
+ else if(info->msg == WM_LBUTTONDOWN)
+ {
+ /*
+ This is a slight race condition. We are part of the WM_MOUSEMOVE message in the listview. Selection state hasn't changed yet.
+ By doing a PostMessage, we're betting that by the time that the message gets processed, selections state HAS changed.
+ Haven't managed to make it misbehave yet though.
+ (Passing the this pointer is not dangerous, because we check it on the other side :)
+ */
+ PostMessage(hwndDlg,WM_USER+710,hitVal,(LPARAM)this);
+ ReleaseCapture();
+ }
+ else if(hotold >=0) { ReleaseCapture(); ListView_RedrawItems(hwndRealList,hotold,hotold); }
+ }
+ else if(hotold >=0) { ReleaseCapture(); ListView_RedrawItems(hwndRealList,hotold,hotold); }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ BOOL bProcessed(FALSE);
+ LRESULT result(0);
+ switch (((NMHDR*)lParam)->code)
+ {
+ case LVN_KEYDOWN: bProcessed = OnKeyDown((NMLVKEYDOWN*)lParam); break;
+ case NM_CUSTOMDRAW:
+ bProcessed = (isDetailsMode(mode)) ? OnCustomDrawDetails((NMLVCUSTOMDRAW*)lParam, &result) : OnCustomDrawIcon((NMLVCUSTOMDRAW*)lParam, &result);
+ break;
+ case LVN_GETDISPINFOW:
+ return TRUE;
+ case LVN_GETINFOTIPW:
+ {
+ const wchar_t *p = GetText(((NMLVGETINFOTIPW*)lParam)->iItem);
+ if (p && *p && lstrlenW(p) > itemHeight) // we use itemHeight to write number of average characters that fits label in icon mode
+ {
+ StringCchCopyW(((NMLVGETINFOTIPW*)lParam)->pszText, ((NMLVGETINFOTIPW*)lParam)->cchTextMax, p);
+ }
+ return TRUE;
+ }
+ case LVN_EX_SIZECHANGED:
+ {
+ INT hint = (INT)SendMessageW(((NMHDR*)lParam)->hwndFrom, WM_EX_GETCOUNTPERPAGE, 0, 0L);
+ if (hint > 0) HintCacheSize(hint);
+ }
+ return TRUE;
+ }
+
+ if (bProcessed) SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return bProcessed;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int getWidth(int def, int col)
+{
+ wchar_t buf[100] = {0};
+ char * name = AlbumFilter::getColConfig(col);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"av_art_col_%hs", name);
+ return g_view_metaconf->ReadInt(buf, def);
+}
+
+void AlbumArtFilter::AddColumns2()
+{
+ showncolumns.push_back(new ListField(7,getWidth(90,7),IDS_ARTIST,g_view_metaconf,"",false,false,false,0));
+ showncolumns.push_back(new ListField(0,getWidth(90,0),IDS_ALBUM,g_view_metaconf,"",false,false,false,1));
+ showncolumns.push_back(new ListField(3,getWidth(50,3),IDS_TRACKS_MENU,g_view_metaconf,"",false,false,false,2));
+ showncolumns.push_back(new ListField(1,getWidth(50,1),IDS_YEAR,g_view_metaconf,"",false,false,false,3));
+ if (mode != 0 && mode != 3)
+ {
+ showncolumns.push_back(new ListField(8,getWidth(50,8),IDS_GENRE,g_view_metaconf,"",false,false,false,4));
+ showncolumns.push_back(new ListField(9,getWidth(50,9),IDS_RATING,g_view_metaconf,"",false,false,false,5));
+ if (mode != 1 && mode != 4)
+ {
+ showncolumns.push_back(new ListField(6,getWidth(50,6),IDS_LENGTH,g_view_metaconf,"",false,false,false,6));
+ showncolumns.push_back(new ListField(5,getWidth(50,5),IDS_SIZE,g_view_metaconf,"",false,false,false,7));
+ }
+ }
+ showncolumns.push_back(new ListField(10,getWidth(90,10),IDS_LAST_UPDATED,g_view_metaconf,"",false,false,false,8));
+}
+
+static const wchar_t* getArtist(const itemRecordW *p)
+{
+ if (p->albumartist) return p->albumartist;
+ else if (p->artist) return p->artist;
+ return L"";
+}
+
+static wchar_t* retainArtist(itemRecordW *p)
+{
+ if (p->albumartist)
+ {
+ ndestring_retain(p->albumartist);
+ return p->albumartist;
+ }
+ else if (p->artist)
+ {
+ ndestring_retain(p->artist);
+ return p->artist;
+ }
+ return emptyQueryListString;
+}
+
+int AlbumArtFilter::MyBuildSortFunc(const void *elem1, const void *elem2, const void *context)
+{
+ AlbumArtFilter *sortFilter = (AlbumArtFilter *)context;
+ itemRecordW *a = (itemRecordW *)elem1;
+ itemRecordW *b = (itemRecordW *)elem2;
+ int v=WCSCMP_NULLOK(getArtist(a),getArtist(b));
+ if (v)
+ return v;
+ v=WCSCMP_NULLOK(a->album,b->album);
+ if (v)
+ return v;
+ if (sortFilter->nextFilter)
+ {
+ wchar_t ab[100] = {0}, bb[100] = {0};
+ v = WCSCMP_NULLOK(sortFilter->nextFilter->GroupText(a,ab,100),sortFilter->nextFilter->GroupText(b,bb,100));
+ if (v)
+ return v;
+ }
+ v = a->track - b->track;
+ if (v)
+ return v;
+ return WCSCMP_NULLOK(a->filename,b->filename);
+}
+
+void AlbumArtFilter::Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)
+{
+ if (numitems > 1)
+ qsort_itemRecord(items,numitems,this, MyBuildSortFunc);
+
+ if (killswitch && *killswitch) return;
+
+ emptyQueryListObject(&albumList);
+ reallocQueryListObject(&albumList);
+
+ itemRecordW *p = items;
+ int n = numitems;
+ int numalbumstotal = 0;
+ wchar_t *lastartist = 0;
+ wchar_t *lastalbum = 0;
+ const wchar_t *lastalb=0;
+ wchar_t albbuf[100] = {0}, albbuf2[100] = {0};
+ ZeroMemory(&albumList.Items[0],sizeof(queryListItem));
+ int isbl = 0;
+ while (n--)
+ {
+ if (killswitch && *killswitch) return;
+ if ((!lastartist || WCSCMP_NULLOK(lastartist, getArtist(p))) || (!lastalbum || WCSCMP_NULLOK(lastalbum, p->album)))
+ {
+ albumList.Size++;
+ if (reallocQueryListObject(&albumList)) break;
+ wchar_t *albumGain = p->replaygain_album_gain;
+ ndestring_retain(albumGain);
+ wchar_t *gracenoteFileId = getRecordExtendedItem_fast(p, extended_fields.GracenoteFileID);
+ ZeroMemory(&albumList.Items[albumList.Size],sizeof(queryListItem));
+ albumList.Items[albumList.Size].albumGain = albumGain;
+ albumList.Items[albumList.Size].gracenoteFileId = gracenoteFileId;
+ ndestring_retain(gracenoteFileId);
+
+ lastartist = albumList.Items[albumList.Size].artist = retainArtist(p);
+ if (p->album)
+ {
+ ndestring_retain(p->album);
+ lastalbum = albumList.Items[albumList.Size].name = p->album;
+ }
+ else
+ lastalbum = albumList.Items[albumList.Size].name = emptyQueryListString;
+
+ lastalb=0;
+ SKIP_THE_AND_WHITESPACEW(lastalbum) // optimization technique
+ SKIP_THE_AND_WHITESPACEW(lastartist) // optimization technique
+
+ albumList.Items[albumList.Size].art = new AlbumArtContainer();
+ ndestring_retain(p->filename);
+ albumList.Items[albumList.Size].art->filename = p->filename;
+
+ if (*lastalbum) numalbumstotal++;
+ }
+ if (p->year>0)
+ {
+ int y = albumList.Items[albumList.Size].ifields[2];
+ if (y == 0) y = MAKELONG((short)p->year,(short)p->year);
+ else if (p->year > (short)LOWORD(y)) y = MAKELONG((short)p->year,(short)HIWORD(y));
+ else if (p->year < (short)HIWORD(y)) y = MAKELONG((short)LOWORD(y),(short)p->year);
+ albumList.Items[albumList.Size].ifields[2] = y;
+ }
+
+ if (!albumList.Items[albumList.Size].genre && p->genre)
+ {
+ wchar_t *genre = p->genre;
+ ndestring_retain(genre);
+ albumList.Items[albumList.Size].genre = genre;
+ }
+
+ if (p->rating > 0) albumList.Items[albumList.Size].rating += p->rating;
+
+ if (p->lastupd > albumList.Items[albumList.Size].lastupd) albumList.Items[albumList.Size].lastupd = p->lastupd;
+
+ if (!p->album || !*p->album) isbl++;
+ if (albumList.Size)
+ {
+ albumList.Items[albumList.Size].ifields[1]++;
+ if (p->length>0) albumList.Items[albumList.Size].length += p->length;
+ if (p->filesize>0) albumList.Items[albumList.Size].size += p->filesize;
+ }
+ if (nextFilter && (!lastalb || WCSCMP_NULLOK(lastalb,nextFilter->GroupText(p,albbuf2,100))))
+ {
+ lastalb = nextFilter->GroupText(p,albbuf,100);
+ if (lastalb && *lastalb) albumList.Items[albumList.Size].ifields[0]++;
+ if (lastalb) SKIP_THE_AND_WHITESPACEW(lastalb) // optimization technique
+ }
+ p++;
+ }
+ wchar_t buf[64] = {0}, sStr[16] = {0}, langBuf[64] = {0};
+ if (isbl)
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM, langBuf, 64), albumList.Size - 1, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 2 ? IDS_ALBUM : IDS_ALBUMS,sStr,16), isbl);
+ }
+ else
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS, langBuf, 64), albumList.Size, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 1 ? IDS_ALBUM : IDS_ALBUMS,sStr,16));
+ }
+ albumList.Items[0].name = ndestring_wcsdup(buf);
+ albumList.Items[0].ifields[1] = numitems;
+ albumList.Items[0].ifields[0] = numitems2;
+ albumList.Size++;
+ numGroups = numalbumstotal;
+}
+
+bool AlbumArtFilter::MakeFilterQuery(int x, GayStringW *query)
+{
+ queryListItem * l = &albumList.Items[x+1];
+ // this mess appends this query:
+ // (album="l->album" && ((albumartist isempty && artist="l->artist") || (albumartist isnotempty && albumartist="l->artist")))
+ query->Append(L"(album=\"");
+ GayStringW escaped;
+ queryStrEscape(l->name, escaped);
+ query->Append(escaped.Get());
+ escaped.Set(L"");
+ query->Append(L"\" && ((albumartist isempty && artist LIKE \"");
+ queryStrEscape(l->artist, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\") || (albumartist isnotempty && albumartist LIKE \"");
+ query->Append(escaped.Get());
+ query->Append(L"\")))");
+ return true;
+}
+
+HMENU AlbumArtFilter::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu)
+{
+ HMENU menu = GetSubMenu(themenu, 6);
+
+ int checked=0;
+ switch (mode)
+ {
+ case 0: checked=ID_ARTHEADERWND_SMALLDETAILS; break;
+ case 1: checked=ID_ARTHEADERWND_MEDIUMDETAILS; break;
+ case 2: checked=ID_ARTHEADERWND_LARGEDETAILS; break;
+ case 3: checked=ID_ARTHEADERWND_SMALLICON; break;
+ case 4: checked=ID_ARTHEADERWND_MEDIUMICON; break;
+ case 5: checked=ID_ARTHEADERWND_LARGEICON; break;
+ case 6: checked=ID_ARTHEADERWND_EXTRALARGEICON; break;
+ }
+ CheckMenuItem(menu,checked,MF_CHECKED | MF_BYCOMMAND);
+ CheckMenuItem(menu,ID_ARTHEADERWND_SHOWTEXT,(icons_only ? MF_UNCHECKED : MF_CHECKED) | MF_BYCOMMAND);
+
+ if (isFilter)
+ {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i=0;
+ while (GetMenuItemInfo(menu,i,TRUE,&m))
+ {
+ m.wID |= (1+filterNum) << 16;
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ i++;
+ }
+ }
+ return menu;
+}
+
+void AlbumArtFilter::ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent)
+{
+ int mid = (r >> 16), newMode;
+ BOOL updateChache = FALSE;
+ BOOL iconsOnly = FALSE;
+ if (!isFilter && mid) return;
+ if (isFilter && mid-1 != filterNum) return;
+
+ switch (LOWORD(r))
+ {
+ case ID_ARTHEADERWND_SMALLDETAILS: newMode=0; break;
+ case ID_ARTHEADERWND_MEDIUMDETAILS: newMode=1; break;
+ case ID_ARTHEADERWND_LARGEDETAILS: newMode=2; break;
+ case ID_ARTHEADERWND_SMALLICON: newMode=3; break;
+ case ID_ARTHEADERWND_MEDIUMICON: newMode=4; break;
+ case ID_ARTHEADERWND_LARGEICON: newMode=5; break;
+ case ID_ARTHEADERWND_EXTRALARGEICON:newMode=6; break;
+ case ID_ARTHEADERWND_SHOWTEXT:
+ {
+ newMode = mode;
+ icons_only = !icons_only;
+ // only update view if in icon view
+ if (!isDetailsMode(mode)) {
+ iconsOnly = TRUE;
+ }
+ }
+ break;
+ default: return;
+ }
+
+ if (mode == newMode && iconsOnly == FALSE) return;
+
+ if (!iconsOnly) {
+ int w1, w2, h1, h2;
+ getImgSize(mode, w1, h1);
+ getImgSize(newMode, w2, h2);
+ updateChache = (w1 != w2 || h1 != h2);
+ mode = newMode;
+ }
+
+ c->WriteInt(L"albumartviewmode",mode);
+ c->WriteInt(L"albumarticonmode",icons_only);
+ SaveColumnWidths();
+ while (ListView_DeleteColumn(list->getwnd(), 0));
+ if (updateChache) FlushCache();
+
+ for (int i=0;i!=albumList.Size;i++)
+ {
+ if (albumList.Items[i].art)
+ {
+ albumList.Items[i].art->Reset();
+ albumList.Items[i].art->updateMsg.hwnd=0;
+ }
+ }
+
+ DialogProc(parent,WM_INITDIALOG,0,0);
+ if (updateChache) ResumeCache();
+ HWND hwndList = GetDlgItem(parent,dlgitem);
+ if (hwndList)
+ {
+ MLSkinnedWnd_SkinChanged(hwndList, TRUE, TRUE);
+ list->setwnd(hwndList);
+ AddColumns();
+ CalcuateItemHeight();
+ ListView_SetItemCount(hwndList, Size());
+ }
+}
+
+void AlbumArtFilter::MetaUpdate(int r, const wchar_t *metaItem, const wchar_t *value)
+{
+ if(_wcsicmp(metaItem, DB_FIELDNAME_rating )==0)
+ albumList.Items[r+1].rating = _wtoi(value) * albumList.Items[r+1].ifields[1];
+
+ SendMessage(GetDlgItem(hwndDlg,dlgitem),LVM_REDRAWITEMS,r,r);
+ UpdateWindow(GetDlgItem(hwndDlg,dlgitem));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtFilter.h b/Src/Plugins/Library/ml_local/AlbumArtFilter.h
new file mode 100644
index 00000000..b3a1439c
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtFilter.h
@@ -0,0 +1,64 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMART_FILTER_H_
+#define NULLSOFT_ML_LOCAL_ALBUMART_FILTER_H_
+
+#include "AlbumFilter.h"
+#include <tataki/bitmap/autobitmap.h>
+
+class AlbumArtFilter : public AlbumFilter
+{
+public:
+ AlbumArtFilter(HWND hwndDlg, int dlgitem, C_Config *c);
+ virtual ~AlbumArtFilter();
+ virtual char * GetConfigId(){return "av_art";}
+ virtual int Size() { return albumList.Size - 1; }
+ virtual const wchar_t *GetText(int row) { return AlbumFilter::GetText(row+1); }
+ virtual void CopyText(int row, size_t column, wchar_t *dest, int destCch) { return AlbumFilter::CopyText(row+1,column,dest,destCch); }
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch) { return AlbumFilter::CopyText2(row+1,column,dest,destCch); }
+
+ virtual INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ AlbumArtContainer * GetArt(int row) { return albumList.Items[row].art; }
+
+ BOOL DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive);
+ BOOL PrepareDetails(HDC hdc);
+ BOOL DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNaes, INT namesWidth);
+ void drawArt(AlbumArtContainer *art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex);
+
+ void AddColumns2();
+ void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2);
+ virtual bool HasTopItem() {return false;}
+ static int __fastcall MyBuildSortFunc(const void *elem1, const void *elem2, const void *context);
+ virtual bool MakeFilterQuery(int x, GayStringW *query);
+
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+
+ virtual void MetaUpdate(int r, const wchar_t *metaItem, const wchar_t *value);
+
+protected:
+ BOOL OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnKeyDown(NMLVKEYDOWN *plvkd);
+ BOOL CalcuateItemHeight(void);
+protected:
+ DWORD ratingHotItem;
+ int ratingHotValue;
+ int dlgitem;
+ HWND hwndDlg;
+ HWND hwndRealList;
+ int mode;
+ int icons_only;
+ AutoSkinBitmap notfound;
+ AutoSkinBitmap notfound60;
+ AutoSkinBitmap notfound90;
+ ARGB32 * classicnotfound[3];
+ int classicnotfoundW,classicnotfoundH;
+ INT ratingrow;
+ HBITMAP hbmpNames;
+ INT itemHeight;
+ INT textHeight;
+ INT ratingTop;
+ INT namesWidth;
+ HBRUSH bgBrush;
+};
+
+#endif // NULLSOFT_ML_LOCAL_ALBUMART_FILTER_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumFilter.cpp b/Src/Plugins/Library/ml_local/AlbumFilter.cpp
new file mode 100644
index 00000000..af414d2d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumFilter.cpp
@@ -0,0 +1,496 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "../nu/sort.h"
+#include "AlbumFilter.h"
+#include "resource.h"
+#include <shlwapi.h>
+
+static size_t m_sort_by, m_sort_dir, m_sort_which;
+
+void emptyQueryListObject(queryListObject *obj);
+int reallocQueryListObject(queryListObject *obj);
+void freeQueryListObject(queryListObject *obj);
+
+
+int AlbumFilter::AlbumSortFunc(const void *elem1, const void *elem2)
+{
+ const queryListItem *a = (const queryListItem*)elem1;
+ const queryListItem *b = (const queryListItem*)elem2;
+ int use_by = m_sort_by;
+ int use_dir = m_sort_dir;
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+ if (use_by == ALBUMFILTER_COLUMN_NAME)
+ {
+ int v = WCSCMP_NULLOK(a->name, b->name);
+ RETIFNZ(v)
+ v = b->ifields[2] - a->ifields[2];
+ RETIFNZ(v)
+ v = b->ifields[1] - a->ifields[1];
+ return v;
+ }
+ if (use_by == ALBUMFILTER_COLUMN_YEAR)
+ {
+ int v = a->ifields[2] - b->ifields[2];
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+ if (use_by == ALBUMFILTER_COLUMN_ALBUMS)
+ {
+ int v = a->ifields[0] - b->ifields[0];
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+ if (use_by == ALBUMFILTER_COLUMN_TRACKS)
+ {
+ int v = a->ifields[1] - b->ifields[1];
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if (use_by == ALBUMFILTER_COLUMN_REPLAYGAIN)
+ {
+ int v = FLOATWCMP_NULLOK(a->albumGain, b->albumGain);
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if(use_by == ALBUMFILTER_COLUMN_SIZE)
+ {
+ __int64 v = a->size - b->size;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if(use_by == ALBUMFILTER_COLUMN_LENGTH)
+ {
+ int v = a->length - b->length;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+ if(use_by == ALBUMFILTER_COLUMN_RATING) {
+ int ar = 0;
+ if(a->ifields[1]) ar = a->rating/a->ifields[1];
+ int br = 0;
+ if(b->ifields[1]) br = b->rating/b->ifields[1];
+ int v = ar - br;
+ RETIFNZ(v)
+ }
+ if(use_by == ALBUMFILTER_COLUMN_ARTIST || use_by == ALBUMFILTER_COLUMN_RATING) {
+ int v = WCSCMP_NULLOK(a->artist, b->artist);
+ RETIFNZ(v)
+ v = WCSCMP_NULLOK(a->name, b->name);
+ RETIFNZ(v)
+ }
+ else if(use_by == ALBUMFILTER_COLUMN_GENRE) {
+ int v = WCSCMP_NULLOK(a->genre, b->genre);
+ RETIFNZ(v)
+ v = WCSCMP_NULLOK(a->artist, b->artist);
+ RETIFNZ(v)
+ v = WCSCMP_NULLOK(a->name, b->name);
+ RETIFNZ(v)
+ }
+ if(use_by == ALBUMFILTER_COLUMN_LASTUPD)
+ {
+ int v = (int)(b->lastupd - a->lastupd);
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+#undef RETIFNZ
+ return 0;
+}
+
+void AlbumFilter::SortResults(C_Config *viewconf, int which, int isfirst) // sorts the results based on the current sort mode
+{
+ if (viewconf)
+ {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d", GetConfigId(), which);
+ m_sort_by = viewconf->ReadInt(buf, 0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_dir_%d", GetConfigId(), which);
+ m_sort_dir = viewconf->ReadInt(buf, 0);
+ m_sort_which = which;
+
+ if(showncolumns.size()>m_sort_by && m_sort_by>=0) m_sort_by = showncolumns[m_sort_by]->field;
+
+ if (m_sort_dir == 0 && m_sort_by == 0 && isfirst) return;
+
+ if (albumList.Size > 2) qsort(albumList.Items+1,albumList.Size-1,sizeof(queryListItem),AlbumSortFunc);
+ }
+}
+
+void AlbumFilter::Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)
+{
+ if (numitems > 1)
+ qsort_itemRecord(items,numitems, this, BuildSortFunc);
+
+ if (killswitch && *killswitch) return;
+
+ emptyQueryListObject(&albumList);
+ reallocQueryListObject(&albumList);
+
+ itemRecordW *p = items;
+ int n = numitems;
+ int numalbumstotal = 0;
+ wchar_t *lastname = 0;
+ const wchar_t *lastalb=0;
+ wchar_t albbuf[100] = {0}, albbuf2[100] = {0};
+ ZeroMemory(&albumList.Items[0],sizeof(queryListItem));
+ int isbl = 0;
+ while (n--)
+ {
+ if (killswitch && *killswitch) return;
+ if (!lastname || WCSCMP_NULLOK(lastname, p->album))
+ {
+ albumList.Size++;
+ if (reallocQueryListObject(&albumList)) break;
+ wchar_t *albumGain = p->replaygain_album_gain;
+ ndestring_retain(albumGain);
+ wchar_t *gracenoteFileId = getRecordExtendedItem_fast(p, extended_fields.GracenoteFileID);
+ ZeroMemory(&albumList.Items[albumList.Size],sizeof(queryListItem));
+ albumList.Items[albumList.Size].albumGain = albumGain;
+ albumList.Items[albumList.Size].gracenoteFileId = gracenoteFileId;
+ ndestring_retain(gracenoteFileId);
+
+ if (p->album)
+ {
+ ndestring_retain(p->album);
+ lastname = albumList.Items[albumList.Size].name = p->album;
+ }
+ else
+ lastname = albumList.Items[albumList.Size].name = emptyQueryListString;
+
+ lastalb=0;
+ SKIP_THE_AND_WHITESPACEW(lastname) // optimization technique
+ if(*lastname) numalbumstotal++;
+ }
+ if(p->year>0) {
+ int y = albumList.Items[albumList.Size].ifields[2];
+ if(y == 0) y = MAKELONG((short)p->year,(short)p->year);
+ else if(p->year > (short)LOWORD(y)) y = MAKELONG((short)p->year,(short)HIWORD(y));
+ else if(p->year < (short)HIWORD(y)) y = MAKELONG((short)LOWORD(y),(short)p->year);
+ albumList.Items[albumList.Size].ifields[2] = y;
+ }
+
+ if (!p->album || !*p->album) isbl++;
+ if (albumList.Size) {
+ albumList.Items[albumList.Size].ifields[1]++;
+ if(p->length>0) albumList.Items[albumList.Size].length += p->length;
+ if(p->filesize>0) albumList.Items[albumList.Size].size += p->filesize;
+ }
+ if (nextFilter && (!lastalb || WCSCMP_NULLOK(lastalb,nextFilter->GroupText(p,albbuf2,100))))
+ {
+ lastalb = nextFilter->GroupText(p,albbuf,100);
+ if(lastalb && *lastalb) albumList.Items[albumList.Size].ifields[0]++;
+ if (lastalb) SKIP_THE_AND_WHITESPACEW(lastalb) // optimization technique
+ }
+ p++;
+ }
+ wchar_t buf[64] = {0}, sStr[16] = {0}, langBuf[64] = {0};
+ if (isbl)
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM, langBuf, 64), albumList.Size - 1, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 2 ? IDS_ALBUM : IDS_ALBUMS,sStr,16), isbl);
+ }
+ else
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS, langBuf, 64), albumList.Size, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 1 ? IDS_ALBUM : IDS_ALBUMS,sStr,16));
+ }
+
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(buf);
+
+ albumList.Items[0].name = ndestring_wcsdup(buf);
+ albumList.Items[0].ifields[1] = numitems;
+ albumList.Items[0].ifields[0] = numitems2;
+ albumList.Size++;
+ numGroups = numalbumstotal;
+}
+
+int AlbumFilter::Size()
+{
+ return albumList.Size;
+}
+
+const wchar_t *AlbumFilter::GetText(int row) // gets main text (first column)
+{
+ return (row < albumList.Size) ? albumList.Items[row].name : NULL;
+}
+
+void AlbumFilter::Empty()
+{
+ freeQueryListObject(&albumList);
+}
+
+
+void AlbumFilter::CopyText(int row, size_t column, wchar_t *dest, int destCch)
+{
+ if(column >= showncolumns.size()) return;
+ column = showncolumns[column]->field;
+ if (row>=albumList.Size)
+ return ;
+ switch(column)
+ {
+ case ALBUMFILTER_COLUMN_NAME: // name
+ if (albumList.Items[row].name && *albumList.Items[row].name)
+ lstrcpynW(dest,albumList.Items[row].name,destCch);
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ALBUM,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_YEAR: // year
+ if (HIWORD(albumList.Items[row].ifields[2]) < 1)
+ dest[0] = 0;
+ else {
+ int y = albumList.Items[row].ifields[2];
+ if(HIWORD(y) == LOWORD(y)) StringCchPrintfW(dest, destCch, L"%d", HIWORD(y));
+ else if(HIWORD(y)/100 == LOWORD(y)/100) StringCchPrintfW(dest, destCch, L"%d-%02d", HIWORD(y),LOWORD(y)%100);
+ else StringCchPrintfW(dest, destCch, L"%d-%d", HIWORD(y),LOWORD(y));
+ }
+ break;
+ case ALBUMFILTER_COLUMN_ALBUMS: // albums
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[0]);
+ break;
+ case ALBUMFILTER_COLUMN_TRACKS: // tracks
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[1]);
+ break;
+ case ALBUMFILTER_COLUMN_REPLAYGAIN: // album replay gain
+ if (albumList.Items[row].albumGain)
+ lstrcpynW(dest, albumList.Items[row].albumGain, destCch);
+ else
+ dest[0] = 0;
+ break;
+ case ALBUMFILTER_COLUMN_SIZE: // size
+ if(row && albumList.Items[row].size) {
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, albumList.Items[row].size);
+ } else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_LENGTH: // length
+ if(row && albumList.Items[row].length)
+ StringCchPrintfW(dest, destCch,L"%d:%02d", albumList.Items[row].length / 60, albumList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_ARTIST: // artist
+ if(row && albumList.Items[row].artist)
+ lstrcpynW(dest,albumList.Items[row].artist,destCch);
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ARTIST,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_GENRE: // genre
+ if(row && albumList.Items[row].genre)
+ lstrcpynW(dest,albumList.Items[row].genre,destCch);
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_GENRE,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_RATING: // rating
+ dest[0]=0;
+ if(row && albumList.Items[row].rating > 0 && albumList.Items[row].ifields[1]) {
+ int r = albumList.Items[row].rating / albumList.Items[row].ifields[1];
+ if(r >=0 && r<=5) {
+ wchar_t ra[]=L"*****";
+ ra[r]=0;
+ StringCchPrintfW(dest, destCch,L"%d",ra);
+ }
+ }
+ break;
+ case ALBUMFILTER_COLUMN_LASTUPD: // last updated
+ dest[0]=0;
+ if(row && albumList.Items[row].lastupd)
+ {
+ StringCchPrintfW(dest, destCch,L"%d", albumList.Items[row].lastupd);
+ }
+ else dest[0]=0;
+ break;
+ }
+}
+const wchar_t *AlbumFilter::CopyText2(int row, size_t column, wchar_t *dest, int destCch)
+{
+ const wchar_t *cdest=dest;
+ if(column >= showncolumns.size()) return NULL;
+ column = showncolumns[column]->field;
+ if (row>=albumList.Size)
+ return NULL;
+ switch(column)
+ {
+ case ALBUMFILTER_COLUMN_NAME: // name
+ if (albumList.Items[row].name && *albumList.Items[row].name)
+ cdest = albumList.Items[row].name;
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ALBUM,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_YEAR: // year
+ if (HIWORD(albumList.Items[row].ifields[2]) < 1)
+ dest[0] = 0;
+ else {
+ int y = albumList.Items[row].ifields[2];
+ if(HIWORD(y) == LOWORD(y)) StringCchPrintfW(dest, destCch, L"%d", HIWORD(y));
+ else if(HIWORD(y)/100 == LOWORD(y)/100) StringCchPrintfW(dest, destCch, L"%d-%02d", HIWORD(y),LOWORD(y)%100);
+ else StringCchPrintfW(dest, destCch, L"%d-%d", HIWORD(y),LOWORD(y));
+ }
+ break;
+ case ALBUMFILTER_COLUMN_ALBUMS: // albums
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[0]);
+ break;
+ case ALBUMFILTER_COLUMN_TRACKS: // tracks
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[1]);
+ break;
+ case ALBUMFILTER_COLUMN_REPLAYGAIN: // album replay gain
+ if (albumList.Items[row].albumGain)
+ cdest = albumList.Items[row].albumGain;
+ else
+ dest[0] = 0;
+ break;
+ case ALBUMFILTER_COLUMN_SIZE: // size
+ if(row && albumList.Items[row].size) {
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, albumList.Items[row].size);
+ } else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_LENGTH: // length
+ if(row && albumList.Items[row].length)
+ StringCchPrintfW(dest, destCch,L"%d:%02d", albumList.Items[row].length / 60, albumList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_ARTIST: // artist
+ if(row && albumList.Items[row].artist)
+ cdest = albumList.Items[row].artist;
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ARTIST,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_GENRE: // genre
+ if(row && albumList.Items[row].genre)
+ cdest = albumList.Items[row].genre;
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_GENRE,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_RATING: // rating
+ dest[0]=0;
+ if(row && albumList.Items[row].rating > 0 && albumList.Items[row].ifields[1]) {
+ double d = double(albumList.Items[row].rating) / double(albumList.Items[row].ifields[1]);
+ int r = int(d);
+ if(d - double(r) >= 0.5 && r<5) r++;
+ if(r >=0 && r<=5) {
+ wchar_t ra[]=L"*****";
+ ra[r]=0;
+ StringCchPrintfW(dest, destCch,L"%s",ra);
+ }
+ }
+ break;
+ case ALBUMFILTER_COLUMN_LASTUPD: // last updated
+ dest[0]=0;
+ if(row && albumList.Items[row].lastupd)
+ {
+ StringCchPrintfW(dest, destCch,L"%d", albumList.Items[row].lastupd);
+ }
+ else dest[0]=0;
+ break;
+ }
+ return cdest;
+}
+const wchar_t *AlbumFilter::GetField()
+{
+ return DB_FIELDNAME_album;
+}
+
+const wchar_t *AlbumFilter::GetName()
+{
+ return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUMS,name,64);
+}
+
+const wchar_t *AlbumFilter::GetNameSingular()
+{
+ return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM,sing_name,64);
+}
+
+const wchar_t *AlbumFilter::GetNameSingularAlt(int mode)
+{
+ return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ALBUM_ALT:(mode==1?2064:2080)),sing_name_alt,64);
+}
+
+char * AlbumFilter::getColConfig(int i) {
+ switch (i) {
+ case 0: return "album";
+ case 1: return "year";
+ case 2: return "nf";
+ case 3: return "tracks";
+ case 4: return "albumgain";
+ case 5: return "size";
+ case 6: return "length";
+ case 7: return "artist";
+ case 8: return "genre";
+ case 9: return "rating";
+ case 10: return "lastupd";
+ }
+ return "";
+}
+
+static void readConf(C_Config * c, int col, int * width, int * pos, bool * hidden, int defwidth, int defpos, bool defhidden, char * confid) {
+ char * name = AlbumFilter::getColConfig(col);
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs", confid, name);
+ *width = c->ReadInt(buf, defwidth);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_pos", confid, name);
+ *pos = c->ReadInt(buf, defpos);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_hidden", confid, name);
+ *hidden = !!c->ReadInt(buf, defhidden);
+}
+
+static void saveConf(C_Config * c, int col, int defwidth, int defpos, bool defhidden, bool exists, char * confid) {
+ char * name = AlbumFilter::getColConfig(col);
+ wchar_t buf[100] = {0};
+ if(exists) {
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs", confid, name);
+ c->WriteInt(buf, defwidth);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_pos", confid, name);
+ c->WriteInt(buf, defpos);
+ }
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_hidden", confid, name);
+ c->WriteInt(buf, defhidden);
+}
+
+void AlbumFilter::AddColumns2() {
+ int width, pos;
+ bool hidden;
+ char * config = GetConfigId();
+ readConf(g_view_metaconf,0,&width,&pos,&hidden,155,0,false,config);
+ showncolumns.push_back(new ListField(0,width,IDS_ALBUM,g_view_metaconf,"",hidden,false,false,pos));
+ readConf(g_view_metaconf,1,&width,&pos,&hidden,47,1,false,config);
+ showncolumns.push_back(new ListField(1,width,IDS_YEAR,g_view_metaconf,"",hidden,false,false,pos));
+ readConf(g_view_metaconf,2,&width,&pos,&hidden,48,2,false,config);
+ if(nextFilter)
+ showncolumns.push_back(new ListField(2,width,nextFilter->GetName(),g_view_metaconf,"",hidden,false,false,pos));
+ readConf(g_view_metaconf,3,&width,&pos,&hidden,47,3,false,config);
+ showncolumns.push_back(new ListField(3,width,IDS_TRACKS_MENU,g_view_metaconf,"",hidden,false,false,pos));
+
+ readConf(g_view_metaconf,4,&width,&pos,&hidden,77,4,!g_config->ReadInt(L"show_albumgain", 0),config);
+ showncolumns.push_back(new ListField(4,width,IDS_ALBUM_GAIN,g_view_metaconf,"",hidden,true,false,pos));
+
+ readConf(g_view_metaconf,5,&width,&pos,&hidden,45,5,true,config);
+ showncolumns.push_back(new ListField(5,width,IDS_SIZE,g_view_metaconf,"",hidden,true,false,pos));
+ readConf(g_view_metaconf,6,&width,&pos,&hidden,45,6,true,config);
+ showncolumns.push_back(new ListField(6,width,IDS_LENGTH,g_view_metaconf,"",hidden,true,false,pos));
+}
+
+
+void AlbumFilter::SaveColumnWidths()
+{
+ char *config = GetConfigId();
+ for ( size_t i = 0; i < showncolumns.size(); i++ )
+ {
+ int field = showncolumns[ i ]->field;
+ saveConf( g_view_metaconf, field, list->GetColumnWidth( i ), i, 0, list->ColumnExists( i ), config );
+ }
+ for ( size_t i = 0; i < hiddencolumns.size(); i++ )
+ {
+ int field = hiddencolumns[ i ]->field;
+ saveConf( g_view_metaconf, field, 0, 0, true, false, config );
+ }
+}
+
+const wchar_t * AlbumFilter::GroupText(itemRecordW * item, wchar_t * buf, int len) {
+ return item->album;
+}
+
+void AlbumFilter::CustomizeColumns(HWND parent, BOOL showmenu) {
+ ViewFilter::CustomizeColumns(parent,showmenu);
+ int v = 0;
+ for (size_t i = 0;i < showncolumns.size();i++) if(showncolumns[i]->field == 4) { v=1; break; }
+ g_config->WriteInt(L"show_albumgain", v);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumFilter.h b/Src/Plugins/Library/ml_local/AlbumFilter.h
new file mode 100644
index 00000000..371ee111
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumFilter.h
@@ -0,0 +1,49 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMFILTER_H
+#define NULLSOFT_ML_LOCAL_ALBUMFILTER_H
+
+#include "ViewFilter.h"
+
+class AlbumFilter : public ViewFilter
+{
+public:
+ static int AlbumSortFunc(const void *elem1, const void *elem2);
+ void SortResults(C_Config *viewconf, int which=0, int isfirst=0);
+ void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2);
+ virtual int Size();
+ virtual const wchar_t *GetText(int row);
+ virtual void CopyText(int row, size_t column, wchar_t *dest, int destCch);
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch);
+ void Empty();
+ const wchar_t *GetField();
+ const wchar_t *GetName();
+ const wchar_t *GetNameSingular();
+ const wchar_t *GetNameSingularAlt(int mode);
+ void AddColumns2();
+ virtual const wchar_t * GroupText(itemRecordW * item, wchar_t * buf, int len);
+ virtual void SaveColumnWidths();
+ virtual void CustomizeColumns(HWND parent, BOOL showmenu);
+ virtual char * GetConfigId(){return "av2";}
+ static char * getColConfig(int i);
+ wchar_t name[64];
+ wchar_t sing_name[64];
+ wchar_t sing_name_alt[64];
+protected:
+ queryListObject albumList;
+
+ enum
+ {
+ ALBUMFILTER_COLUMN_NAME = 0,
+ ALBUMFILTER_COLUMN_YEAR = 1,
+ ALBUMFILTER_COLUMN_ALBUMS = 2,
+ ALBUMFILTER_COLUMN_TRACKS = 3,
+ ALBUMFILTER_COLUMN_REPLAYGAIN = 4,
+ ALBUMFILTER_COLUMN_SIZE = 5,
+ ALBUMFILTER_COLUMN_LENGTH = 6,
+ ALBUMFILTER_COLUMN_ARTIST = 7,
+ ALBUMFILTER_COLUMN_GENRE = 8,
+ ALBUMFILTER_COLUMN_RATING = 9,
+ ALBUMFILTER_COLUMN_LASTUPD = 10,
+ };
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/DBitemrecord.cpp b/Src/Plugins/Library/ml_local/DBitemrecord.cpp
new file mode 100644
index 00000000..d301ec14
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/DBitemrecord.cpp
@@ -0,0 +1,173 @@
+#include "main.h"
+#include "ml_local.h"
+
+#ifdef _M_IX86
+const size_t convert_max_characters = 16; // it's actually 11, but this probably causes less memory fragmentation
+#elif defined(_M_X64)
+const size_t convert_max_characters = 20;
+#endif
+
+bool compat_mode = false;
+
+typedef void (__fastcall *FieldFunc)(itemRecordW *obj, nde_field_t f);
+#define FIELD_FUNC(field) field ## Func
+#define STRINGFIELD_FUNC(item) static void __fastcall FIELD_FUNC(item)(itemRecordW *obj, nde_field_t f) { obj-> ## item = NDE_StringField_GetString(f); ndestring_retain(obj-> ## item); }
+#define INTFIELD_FUNC(item) static void __fastcall FIELD_FUNC(item)(itemRecordW *obj, nde_field_t f) { obj-> ## item = NDE_IntegerField_GetValue(f); }
+#define EXT_STRINGFIELD_FUNC(field) static void __fastcall FIELD_FUNC(field)(itemRecordW *obj, nde_field_t f) { setRecordExtendedItem_fast(obj, extended_fields.field, NDE_StringField_GetString(f)); }
+#define EXT_INTFIELD_FUNC(field) static void __fastcall FIELD_FUNC(field)(itemRecordW *obj, nde_field_t f) { wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t)); unsigned int v = NDE_IntegerField_GetValue(f); wsprintfW(temp, L"%u", v); setRecordExtendedItem_fast(obj, extended_fields.field, temp); ndestring_release(temp); }
+#define REALSIZE_INTFIELD_FUNC(field) static void __fastcall FIELD_FUNC(field)(itemRecordW *obj, nde_field_t f) {\
+ wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t));\
+ __int64 v = NDE_Int64Field_GetValue(f);\
+ obj-> ## field = (v > 0 ? ((int)(compat_mode ? v >> 10 : v)) : 0);\
+ wsprintfW(temp, L"%ld", v);\
+ setRecordExtendedItem_fast(obj, extended_fields.realsize, temp);\
+ ndestring_release(temp);\
+}
+STRINGFIELD_FUNC(title);
+STRINGFIELD_FUNC(artist);
+STRINGFIELD_FUNC(album);
+INTFIELD_FUNC(year);
+STRINGFIELD_FUNC(genre);
+STRINGFIELD_FUNC(comment);
+INTFIELD_FUNC(track);
+INTFIELD_FUNC(length);
+INTFIELD_FUNC(type);
+INTFIELD_FUNC(lastupd);
+INTFIELD_FUNC(lastplay);
+INTFIELD_FUNC(rating);
+INTFIELD_FUNC(playcount);
+INTFIELD_FUNC(filetime);
+// use a custom version to set 'filesize' and 'realsize' to ensure compatibility
+REALSIZE_INTFIELD_FUNC(filesize);
+INTFIELD_FUNC(bitrate);
+INTFIELD_FUNC(disc);
+STRINGFIELD_FUNC(albumartist);
+STRINGFIELD_FUNC(replaygain_album_gain);
+STRINGFIELD_FUNC(replaygain_track_gain);
+STRINGFIELD_FUNC(publisher);
+STRINGFIELD_FUNC(composer);
+INTFIELD_FUNC(bpm);
+INTFIELD_FUNC(discs);
+INTFIELD_FUNC(tracks);
+EXT_INTFIELD_FUNC(ispodcast);
+EXT_STRINGFIELD_FUNC(podcastchannel);
+EXT_INTFIELD_FUNC(podcastpubdate);
+EXT_STRINGFIELD_FUNC(GracenoteFileID);
+EXT_STRINGFIELD_FUNC(GracenoteExtData);
+EXT_INTFIELD_FUNC(lossless);
+STRINGFIELD_FUNC(category);
+EXT_STRINGFIELD_FUNC(codec);
+EXT_STRINGFIELD_FUNC(director);
+EXT_STRINGFIELD_FUNC(producer);
+EXT_INTFIELD_FUNC(width);
+EXT_INTFIELD_FUNC(height);
+EXT_STRINGFIELD_FUNC(mimetype);
+EXT_INTFIELD_FUNC(dateadded);
+
+static void __fastcall NullFieldFunction(itemRecordW *obj, nde_field_t f) {}
+static FieldFunc field_functions[] =
+{
+ NullFieldFunction, // filename
+ FIELD_FUNC(title),
+ FIELD_FUNC(artist),
+ FIELD_FUNC(album),
+ FIELD_FUNC(year),
+ FIELD_FUNC(genre),
+ FIELD_FUNC(comment),
+ FIELD_FUNC(track),
+ FIELD_FUNC(length),
+ FIELD_FUNC(type),
+ FIELD_FUNC(lastupd),
+ FIELD_FUNC(lastplay),
+ FIELD_FUNC(rating),
+ NullFieldFunction, // skip lucky number 13
+ NullFieldFunction, // gracenote ID
+ FIELD_FUNC(playcount),
+ FIELD_FUNC(filetime),
+ FIELD_FUNC(filesize),
+ FIELD_FUNC(bitrate),
+ FIELD_FUNC(disc),
+ FIELD_FUNC(albumartist),
+ FIELD_FUNC(replaygain_album_gain),
+ FIELD_FUNC(replaygain_track_gain),
+ FIELD_FUNC(publisher),
+ FIELD_FUNC(composer),
+ FIELD_FUNC(bpm),
+ FIELD_FUNC(discs),
+ FIELD_FUNC(tracks),
+ FIELD_FUNC(ispodcast),
+ FIELD_FUNC(podcastchannel),
+ FIELD_FUNC(podcastpubdate),
+ FIELD_FUNC(GracenoteFileID),
+ FIELD_FUNC(GracenoteExtData),
+ FIELD_FUNC(lossless),
+ FIELD_FUNC(category),
+ FIELD_FUNC(codec),
+ FIELD_FUNC(director),
+ FIELD_FUNC(producer),
+ FIELD_FUNC(width),
+ FIELD_FUNC(height),
+ FIELD_FUNC(mimetype),
+ FIELD_FUNC(dateadded),
+};
+
+static int StoreField(void *record, nde_field_t field, void *context)
+{
+ unsigned char id = NDE_Field_GetID(field);
+ if (id < sizeof(field_functions)/sizeof(*field_functions))
+ {
+ FieldFunc field_function = field_functions[id];
+ field_function((itemRecordW *)context, field);
+ }
+ return 1;
+}
+
+static void initRecord(itemRecordW *p)
+{
+ if (p)
+ {
+ p->title=0;
+ p->album=0;
+ p->artist=0;
+ p->comment=0;
+ p->genre=0;
+ p->albumartist=0;
+ p->replaygain_album_gain=0;
+ p->replaygain_track_gain=0;
+ p->publisher=0;
+ p->composer=0;
+ p->year=-1;
+ p->track=-1;
+ p->tracks=-1;
+ p->length=-1;
+ p->rating=-1;
+ p->playcount=-1;
+ p->lastplay=-1;
+ p->lastupd=-1;
+ p->filetime=-1;
+ p->filesize=-1;
+ p->bitrate=-1;
+ p->type=-1;
+ p->disc=-1;
+ p->discs=-1;
+ p->bpm=-1;
+ p->extended_info=0;
+ p->category=0;
+ }
+}
+
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordW *obj, bool compat)
+{
+ initRecord(obj);
+ compat_mode = compat;
+ NDE_Scanner_WalkFields(s, StoreField, obj);
+ return obj->filesize;
+}
+
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordListW *obj, bool compat)
+{
+ compat_mode = compat;
+ __int64 retval = ScannerRefToObjCacheNFNW(s, &obj->Items[obj->Size], compat);
+ obj->Size++;
+ return retval;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/FolderBrowseEx.cpp b/Src/Plugins/Library/ml_local/FolderBrowseEx.cpp
new file mode 100644
index 00000000..2c1b0de1
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/FolderBrowseEx.cpp
@@ -0,0 +1,239 @@
+#include "main.h"
+#include "./folderbrowseex.h"
+#include "../nu/CGlobalAtom.h"
+#include <shobjidl.h>
+#include "../replicant/nu/AutoWide.h"
+
+static CGlobalAtom CLSPROP(L"FBEXDLG");
+
+#ifdef _WIN64
+#define LONGPTR_CAST LONG_PTR
+#else
+#define LONGPTR_CAST LONG
+#endif
+
+#define PATHTYPE_PIDL FALSE
+#define PATHTYPE_STRING TRUE
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassName(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpi(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int WINAPI BrowseCallback_Helper(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ FolderBrowseEx *lpfbex = reinterpret_cast<FolderBrowseEx*>(lpData);
+ if (!lpfbex) return 0;
+
+ switch (uMsg)
+ {
+ case BFFM_INITIALIZED:
+ lpfbex->hwnd = hwnd;
+ if(SetPropW(hwnd, CLSPROP, (void*)lpData))
+ {
+ lpfbex->oldProc = (LONG_PTR) ((IsWindowUnicode(hwnd)) ? SetWindowLongPtrW(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)WindowProc_Helper) : SetWindowLongPtrA(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)WindowProc_Helper));
+ if (NULL == lpfbex->oldProc) RemovePropW(hwnd, CLSPROP);
+ }
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ break;
+ }
+ return lpfbex->BrowseCallback(uMsg, lParam);
+}
+
+static LRESULT WINAPI WindowProc_Helper(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ FolderBrowseEx *lpfbex = static_cast<FolderBrowseEx*>(GetPropW(hwnd, CLSPROP));
+ if (!lpfbex) return 0;
+
+ switch(uMsg)
+ {
+ case WM_NCDESTROY:
+ lpfbex->DialogProc(uMsg, wParam, lParam);
+ if (IsWindowUnicode(hwnd)) SetWindowLongPtrW(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)lpfbex->oldProc);
+ else SetWindowLongPtrA(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)lpfbex->oldProc);
+ RemovePropW(hwnd, CLSPROP);
+ lpfbex->oldProc = NULL;
+ lpfbex->hwnd = NULL;
+ return 0;
+ }
+ return lpfbex->DialogProc(uMsg, wParam, lParam);
+}
+
+static void Initialize(FolderBrowseEx *lpfbx, LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle)
+{
+ lpfbx->pidlRoot = pidlRoot;
+ lpfbx->ulFlags = ulFlags;
+
+ lpfbx->lpszCaption = NULL;
+ lpfbx->lpszTitle = NULL;
+ lpfbx->image = -1;
+ lpfbx->pidl = NULL;
+ lpfbx->hwnd = NULL;
+ lpfbx->oldProc = NULL;
+
+ CoInitialize(NULL);
+
+ lpfbx->pathExpanded.empty = TRUE;
+ lpfbx->pathSelection.empty = TRUE;
+
+ if (lpszCaption) lpfbx->SetCaption(lpszCaption);
+ if (lpszTitle) lpfbx->SetTitle(lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle)
+{
+ Initialize(this, pidlRoot, ulFlags, lpszCaption, lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle)
+{
+ Initialize(this, NULL, ulFlags, lpszCaption, lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(UINT ulFlags, LPCWSTR lpszTitle)
+{
+ Initialize(this, NULL, ulFlags, L"", lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(void)
+{
+ Initialize(this, NULL, 0, L"", L"");
+}
+
+FolderBrowseEx::~FolderBrowseEx(void)
+{
+ if (pidl) CoTaskMemFree(pidl);
+ if (lpszTitle) free(lpszTitle);
+ if (lpszCaption) free(lpszCaption);
+ CoUninitialize();
+}
+
+HRESULT FolderBrowseEx::ParseDisplayName(LPCWSTR lpszPath, IBindCtx *pbc, LPITEMIDLIST *ppidl, SFGAOF sfgaoIn, SFGAOF *psfgaoOut)
+{
+ IShellFolder *isf = NULL;
+ HRESULT result;
+ SFGAOF attrib;
+
+ if (NOERROR != (result = SHGetDesktopFolder(&isf))) return result;
+ attrib = sfgaoIn;
+ result = isf->ParseDisplayName(NULL, pbc, (LPWSTR)lpszPath, NULL, ppidl, &attrib);
+ isf->Release();
+
+ if (S_OK != result) *ppidl = NULL;
+ else if (psfgaoOut) *psfgaoOut = attrib;
+ return result;
+}
+
+void FolderBrowseEx::SetExpanded(LPCITEMIDLIST pidlExpand)
+{
+ pathExpanded.empty = FALSE;
+ pathExpanded.type = PATHTYPE_PIDL;
+ pathExpanded.value = (void*)pidlExpand;
+ if (hwnd) SendMessage(BFFM_SETEXPANDED, pathExpanded.type, (LPARAM)pathExpanded.value);
+}
+
+void FolderBrowseEx::SetExpanded(LPCWSTR lpszExpand)
+{
+ pathExpanded.empty = FALSE;
+ pathExpanded.type = PATHTYPE_STRING;
+ pathExpanded.value = (void*)lpszExpand;
+ if (hwnd) SendMessage(BFFM_SETEXPANDED, pathExpanded.type, (LPARAM)pathExpanded.value);
+}
+
+void FolderBrowseEx::SetSelection(LPCITEMIDLIST pidlSelect)
+{
+ pathSelection.empty = FALSE;
+ pathSelection.type = PATHTYPE_PIDL;
+ pathSelection.value = (void*)pidlSelect;
+ if (hwnd) SendMessage(BFFM_SETSELECTIONW, pathSelection.type, (LPARAM)pathSelection.value);
+}
+
+void FolderBrowseEx::SetSelection(LPCWSTR lpszSelect)
+{
+ pathSelection.empty = FALSE;
+ pathSelection.type = PATHTYPE_STRING;
+ pathSelection.value = (void*)lpszSelect;
+ if (hwnd) SendMessage(BFFM_SETSELECTIONW, pathSelection.type, (LPARAM)pathSelection.value);
+}
+
+void FolderBrowseEx::SetCaption(LPCWSTR lpszCaption)
+{
+ if (this->lpszCaption) free(this->lpszCaption);
+ this->lpszCaption = _wcsdup((lpszCaption) ? lpszCaption : L"");
+ if (hwnd) SetWindowText(lpszCaption);
+}
+
+void FolderBrowseEx::SetTitle(LPCWSTR lpszTitle)
+{
+ if (this->lpszTitle) free(this->lpszTitle);
+ this->lpszTitle = _wcsdup((lpszTitle) ? lpszTitle : L"");
+}
+
+LPITEMIDLIST FolderBrowseEx::Browse(HWND hwndOwner)
+{
+ BROWSEINFOW bi = {0};
+ bi.hwndOwner = hwndOwner;
+ bi.pidlRoot = pidlRoot;
+ bi.pszDisplayName = pszDisplayName;
+ bi.lpszTitle = lpszTitle;
+ bi.ulFlags = ulFlags;
+ bi.lpfn = BrowseCallback_Helper;
+ bi.lParam = (LPARAM)this;
+
+ pidl = SHBrowseForFolderW(&bi);
+ if (pidl) OnSelectionDone(pidl);
+
+ image = (pidl) ? bi.iImage : -1;
+
+ return pidl;
+}
+
+INT FolderBrowseEx::BrowseCallback(UINT uMsg, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case BFFM_INITIALIZED: OnInitialized(); break;
+ case BFFM_IUNKNOWN: OnIUnknown((IUnknown*)lParam); break;
+ case BFFM_SELCHANGED: OnSelectionChanged((LPCITEMIDLIST)lParam); break;
+ case BFFM_VALIDATEFAILEDA: return OnValidateFailed(AutoWide((LPCSTR)lParam));
+ case BFFM_VALIDATEFAILEDW: return OnValidateFailed((LPCWSTR)lParam);
+ }
+ return 0;
+}
+
+void FolderBrowseEx::OnInitialized(void)
+{
+ if (!pathSelection.empty) SendMessage(BFFM_SETSELECTIONW, pathSelection.type, (LPARAM)pathSelection.value);
+ if (!pathExpanded.empty) SendMessage(BFFM_SETEXPANDED, pathExpanded.type, (LPARAM)pathExpanded.value);
+ SetWindowText(lpszCaption);
+}
+
+void FolderBrowseEx::OnSelectionChanged(LPCITEMIDLIST pidl)
+{
+}
+
+INT_PTR FolderBrowseEx::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return CallWindowProc(uMsg, wParam, lParam);
+}
+
+INT_PTR FolderBrowseEx::CallWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return (IsWindowUnicode(hwnd)) ? ::CallWindowProcW((WNDPROC)oldProc, hwnd, uMsg, wParam, lParam)
+ : ::CallWindowProcA((WNDPROC)oldProc, hwnd, uMsg, wParam, lParam);
+}
+
+void FolderBrowseEx::SetDialogResult(LRESULT result)
+{
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, (LONG)(LONG_PTR)result);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/FolderBrowseEx.h b/Src/Plugins/Library/ml_local/FolderBrowseEx.h
new file mode 100644
index 00000000..746d186d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/FolderBrowseEx.h
@@ -0,0 +1,97 @@
+#ifndef NULLSOFT_FOLDERBROWSE_EXTENDED_DIALOG_HEADER
+#define NULLSOFT_FOLDERBROWSE_EXTENDED_DIALOG_HEADER
+
+#include <windows.h>
+#include <shlobj.h>
+
+/// Standart controls
+#define IDC_TV_FOLDERS 0x3741
+#define IDC_SB_GRIPPER 0x3747
+#define IDC_LBL_FOLDER 0x3748
+#define IDC_LBL_CAPTION 0x3742
+#define IDC_EDT_PATH 0x3744
+
+
+typedef struct _BFPATH
+{
+ int empty;
+ int type;
+ void *value;
+}BFPATH;
+
+class FolderBrowseEx
+{
+public:
+ FolderBrowseEx(LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle);
+ FolderBrowseEx(UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle);
+ FolderBrowseEx(UINT ulFlags, LPCWSTR lpszTitle);
+ FolderBrowseEx(void);
+ virtual ~FolderBrowseEx(void);
+
+public:
+ virtual LPITEMIDLIST Browse(HWND hwndOwner);
+
+ LPITEMIDLIST GetPIDL(void) { return pidl; }
+ INT GetImage(void) { return image; }
+ LPCWSTR GetDislpayName(void) { return pszDisplayName; }
+ HRESULT ParseDisplayName(LPCWSTR lpszPath, IBindCtx *pbc, LPITEMIDLIST *ppidl, SFGAOF sfgaoIn, SFGAOF *psfgaoOut);
+ HWND GetDlgItem(int nIDDlgItem) { return ::GetDlgItem(hwnd, nIDDlgItem); }
+
+ void SetRoot(LPCITEMIDLIST pidlRoot) { this->pidlRoot = pidlRoot; }
+ void SetFlags(UINT ulFlags) { this->ulFlags = ulFlags; }
+ void SetSelection(LPCITEMIDLIST pidlSelect);
+ void SetSelection(LPCWSTR lpszSelect);
+ void SetExpanded(LPCITEMIDLIST pidlExpand);
+ void SetExpanded(LPCWSTR lpszExpand);
+ void SetCaption(LPCWSTR lpszCaption);
+ void SetTitle(LPCWSTR lpszTitle);
+
+
+protected:
+ HWND GetHandle(void) { return hwnd; }
+ INT_PTR CallWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+ void SetDialogResult(LRESULT result);
+ LRESULT SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { return ::SendMessageW(hwnd, uMsg, wParam, lParam); }
+ void SetWindowText(LPCWSTR lpText) { ::SetWindowTextW(hwnd, lpText); }
+
+ void EnableOK(BOOL enable) { SendMessage(BFFM_ENABLEOK, 0, (LPARAM)enable); }
+ void SetOKText(LPCWSTR lpText) { SendMessage(BFFM_SETOKTEXT, 0, (LPARAM)lpText); }
+ void SetStatusText(LPCWSTR lpText) { SendMessage(BFFM_SETSTATUSTEXTW, 0, (LPARAM)lpText); }
+
+ virtual void OnInitialized(void);
+ virtual void OnIUnknown(IUnknown *lpiu) {}
+ virtual void OnSelectionChanged(LPCITEMIDLIST pidl);
+ virtual BOOL OnValidateFailed(LPCWSTR lpName) { return FALSE; }
+ virtual void OnSelectionDone(LPCITEMIDLIST pidl) { }
+
+ virtual INT BrowseCallback(UINT uMsg, LPARAM lParam);
+ virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+protected:
+ LPCITEMIDLIST pidlRoot;
+
+ BFPATH pathSelection;
+ BFPATH pathExpanded;
+
+ LPWSTR lpszCaption;
+ LPWSTR lpszTitle;
+ UINT ulFlags;
+ INT image;
+ WCHAR pszDisplayName[MAX_PATH];
+ BOOL expand;
+ LPITEMIDLIST pidl;
+
+
+private:
+ HWND hwnd;
+ LONG_PTR oldProc;
+
+
+
+ friend static int WINAPI BrowseCallback_Helper(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);
+ friend static LRESULT WINAPI WindowProc_Helper(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ friend static void Initialize(FolderBrowseEx *lpfbx, LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle);
+
+};
+
+#endif //NULLSOFT_FOLDERBROWSE_EXTENDED_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/LocalMediaCOM.cpp b/Src/Plugins/Library/ml_local/LocalMediaCOM.cpp
new file mode 100644
index 00000000..3600b349
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/LocalMediaCOM.cpp
@@ -0,0 +1,307 @@
+#include "main.h"
+#include "LocalMediaCOM.h"
+
+
+void sortResults(int column, int dir, itemRecordListW *obj);
+
+static void saveQueryToList(nde_scanner_t s, itemRecordListW *obj, int sortColumn, int sortDir)
+{
+ emptyRecordList(obj);
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_First(s);
+
+ int r;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(obj->Items[obj->Size].filename);
+ ScannerRefToObjCacheNFNW(s, obj, true);
+ }
+
+ r = NDE_Scanner_Next(s);
+ }
+ while (r && !NDE_Scanner_EOF(s));
+
+ if (((Table *)g_table)->HasErrors()) // TODO: don't use C++ NDE API
+ {
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Compact(g_table);
+ if (m_media_scanner)
+ {
+ m_media_scanner = NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ compactRecordList(obj);
+
+ sortResults(sortColumn, sortDir, obj);
+}
+
+void WriteEscaped(FILE *fp, const wchar_t *str)
+{
+ // TODO: for speed optimization,
+ // we should wait until we hit a special character
+ // and write out everything else so before it,
+ // like how ASX loader does it
+ while (str && *str)
+ {
+ switch(*str)
+ {
+ case L'&':
+ fputws(L"&amp;", fp);
+ break;
+ case L'>':
+ fputws(L"&gt;", fp);
+ break;
+ case L'<':
+ fputws(L"&lt;", fp);
+ break;
+ case L'\'':
+ fputws(L"&apos;", fp);
+ break;
+ case L'\"':
+ fputws(L"&quot;", fp);
+ break;
+ default:
+ fputwc(*str, fp);
+ break;
+ }
+ // write out the whole character
+ wchar_t *next = CharNextW(str);
+ while (++str != next)
+ fputwc(*str, fp);
+
+ }
+}
+
+bool SaveListToXML(const itemRecordListW *const obj, const wchar_t *filename, int limit)
+{
+ int i=0;
+ FILE *fp = _wfopen(filename, L"wb");
+ if (!fp)
+ return false;
+ wchar_t BOM = 0xFEFF;
+ fwrite(&BOM, sizeof(BOM), 1, fp);
+ fwprintf(fp, L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>");
+ fputws(L"<itemlist>\r\n", fp);
+
+ while (i < obj->Size)
+ {
+ fputws(L"<item ", fp);
+
+ if (obj->Items[i].filename)
+ {
+ fputws(L"filename=\"", fp);
+ WriteEscaped(fp, obj->Items[i].filename);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].title)
+ {
+ fputws(L"title=\"", fp);
+ WriteEscaped(fp, obj->Items[i].title);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].album)
+ {
+ fputws(L"album=\"", fp);
+ WriteEscaped(fp, obj->Items[i].album);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].artist)
+ {
+ fputws(L"artist=\"", fp);
+ WriteEscaped(fp, obj->Items[i].artist);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].comment)
+ {
+ fputws(L"comment=\"", fp);
+ WriteEscaped(fp, obj->Items[i].comment);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].genre)
+ {
+ fputws(L"genre=\"", fp);
+ WriteEscaped(fp, obj->Items[i].genre);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].year > 0)
+ fwprintf(fp, L"year=\"%d\" ",obj->Items[i].year);
+
+ if (obj->Items[i].track > 0)
+ fwprintf(fp, L"track=\"%d\" ",obj->Items[i].track);
+
+ if (obj->Items[i].length > 0)
+ fwprintf(fp, L"length=\"%d\" ",obj->Items[i].length);
+
+ // TODO: extended info fields
+ fputws(L"/>", fp);
+ if (++i == limit)
+ break;
+ }
+ fputws(L"</itemlist>", fp);
+ fclose(fp);
+
+ return true;
+}
+
+enum
+{
+ DISP_LOCALMEDIA_XMLQUERY = 777,
+
+};
+
+#define CHECK_ID(str, id) if (_wcsicmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
+HRESULT LocalMediaCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ CHECK_ID("XMLQuery", DISP_LOCALMEDIA_XMLQUERY)
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT LocalMediaCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT LocalMediaCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+void Bookmark_WriteAsXML(const wchar_t *filename, int max);
+
+HRESULT LocalMediaCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case DISP_LOCALMEDIA_XMLQUERY:
+ if (pdispparams->cArgs == 4)
+ {
+ openDb();
+ int max,dir,column;
+
+ // Strict-ish type checking
+ if ( pdispparams->rgvarg[0].vt == VT_BSTR )
+ max = _wtoi(pdispparams->rgvarg[0].bstrVal);
+ else
+ max = pdispparams->rgvarg[0].uiVal;
+
+ if ( pdispparams->rgvarg[1].vt == VT_BSTR )
+ dir = _wtoi(pdispparams->rgvarg[2].bstrVal);
+ else
+ dir = pdispparams->rgvarg[1].uiVal;
+
+ if ( pdispparams->rgvarg[2].vt == VT_BSTR )
+ column = _wtoi(pdispparams->rgvarg[2].bstrVal);
+ else
+ column = pdispparams->rgvarg[2].uiVal;
+
+ // run query
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, pdispparams->rgvarg[3].bstrVal);
+
+// create itemRecordList (necessary because NDE doesn't sort)
+ itemRecordListW obj;
+ obj.Alloc = 0;
+ obj.Items = NULL;
+ obj.Size = 0;
+ saveQueryToList(s, &obj, column, dir);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ // write to a temporary XML file
+ wchar_t tempPath[MAX_PATH] = {0};
+ GetTempPathW(MAX_PATH, tempPath);
+ wchar_t tempFile[MAX_PATH] = {0};
+ GetTempFileNameW(tempPath, L"lmx", 0, tempFile);
+ SaveListToXML(&obj, tempFile, max);
+ freeRecordList(&obj);
+
+ // open the resultant file to read into a buffer
+ // (we're basically using the filesystem as an automatically growing buffer)
+ HANDLE plFile = CreateFileW(tempFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
+ size_t flen = SetFilePointer(plFile, 0, NULL, FILE_END);
+ SetFilePointer(plFile, 0, NULL, FILE_BEGIN);
+
+ SAFEARRAY *bufferArray=SafeArrayCreateVector(VT_UI1, 0, flen);
+ void *data;
+ SafeArrayAccessData(bufferArray, &data);
+ DWORD bytesRead = 0;
+ ReadFile(plFile, data, flen, &bytesRead, 0);
+ SafeArrayUnaccessData(bufferArray);
+ CloseHandle(plFile);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_ARRAY|VT_UI1;
+ V_ARRAY(pvarResult) = bufferArray;
+ DeleteFileW(tempFile);
+ }
+ return S_OK;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+
+STDMETHODIMP LocalMediaCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG LocalMediaCOM::AddRef(void)
+{
+ return 0;
+}
+
+
+ULONG LocalMediaCOM::Release(void)
+{
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_local/LocalMediaCOM.h b/Src/Plugins/Library/ml_local/LocalMediaCOM.h
new file mode 100644
index 00000000..02138a06
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/LocalMediaCOM.h
@@ -0,0 +1,21 @@
+#ifndef NULLSOFT_BOOKMARKSCOM_H
+#define NULLSOFT_BOOKMARKSCOM_H
+
+#include <ocidl.h>
+
+class LocalMediaCOM : public IDispatch
+{
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MD5.cpp b/Src/Plugins/Library/ml_local/MD5.cpp
new file mode 100644
index 00000000..4ce5396d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MD5.cpp
@@ -0,0 +1,294 @@
+#include "MD5.h"
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+
+
+/* Constants for MD5Transform routine.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform(uint32_t [4], unsigned char [64]);
+static void Encode(unsigned char *, uint32_t *, unsigned int);
+static void Decode(uint32_t *, unsigned char *, unsigned int);
+
+static unsigned char PADDING[64] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void MD5Init(MD5_CTX *context)
+{
+ context->count[0] = context->count[1] = 0;
+
+ /* Load magic initialization constants.
+ */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen)
+{
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((uint32_t)inputLen << 3))
+ < ((uint32_t)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((uint32_t)inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible.
+ */
+ if (inputLen >= partLen)
+ {
+ memcpy
+ ((void *)&context->buffer[index], (void *)input, partLen);
+ MD5Transform(context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform(context->state, &input[i]);
+
+ index = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ memcpy
+ ((void *)&context->buffer[index], (void *)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+void MD5Final(uint8_t digest[16], MD5_CTX *context)
+{
+ uint8_t bits[8] = {0};
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode(bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+ */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ MD5Update(context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update(context, bits, 8);
+
+ /* Store state in digest */
+ Encode(digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+ */
+ memset((void *)context, 0, sizeof(*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform(uint32_t state[4], uint8_t block[64])
+{
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16] = {0};
+
+ Decode(x, block, 64);
+
+ /* Round 1 */
+ FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+ */
+ memset((void *)x, 0, sizeof(x));
+}
+
+/* Encodes input (uint32_t) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode(unsigned char *output, uint32_t *input, unsigned int len)
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (uint32_t). Assumes len is
+ a multiple of 4.
+ */
+static void Decode(uint32_t *output, unsigned char *input, unsigned int len)
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((uint32_t)input[j]) | (((uint32_t)input[j+1]) << 8) |
+ (((uint32_t)input[j+2]) << 16) | (((uint32_t)input[j+3]) << 24);
+}
diff --git a/Src/Plugins/Library/ml_local/MD5.h b/Src/Plugins/Library/ml_local/MD5.h
new file mode 100644
index 00000000..74f8f675
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MD5.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_ML_LOCAL_MD5_H
+#define NULLSOFT_ML_LOCAL_MD5_H
+
+#include <bfc/platform/types.h>
+/* MD5 context. */
+typedef struct
+{
+ uint32_t state[4]; /* state (ABCD) */
+ uint32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ uint8_t buffer[64]; /* input buffer */
+} MD5_CTX;
+
+void MD5Init(MD5_CTX *);
+void MD5Update(MD5_CTX *, uint8_t *, unsigned int);
+void MD5Final(uint8_t [16], MD5_CTX *);
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MLDBCallback.h b/Src/Plugins/Library/ml_local/MLDBCallback.h
new file mode 100644
index 00000000..4fe48286
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MLDBCallback.h
@@ -0,0 +1,76 @@
+#pragma once
+#include <api/syscb/callbacks/svccb.h>
+#include <api/syscb/api_syscb.h>
+#include "../ml_local/api_mldb.h"
+
+class MLDBCallback : public SysCallback
+{
+private:
+ FOURCC GetEventType()
+ {
+ return api_mldb::SYSCALLBACK;
+ }
+
+ int notify(int msg, intptr_t param1, intptr_t param2)
+ {
+ const wchar_t *filename = (const wchar_t *)param1;
+
+ switch (msg)
+ {
+ case api_mldb::MLDB_FILE_ADDED:
+ OnFileAdded(filename);
+ return 1;
+
+ case api_mldb::MLDB_FILE_REMOVED_PRE:
+ OnFileRemove_Pre(filename);
+ return 1;
+
+ case api_mldb::MLDB_FILE_REMOVED_POST:
+ OnFileRemove_Post(filename);
+ return 1;
+
+ case api_mldb::MLDB_FILE_UPDATED:
+ case api_mldb::MLDB_FILE_UPDATED_EXTERNAL:
+ OnFileUpdated(filename, (msg == api_mldb::MLDB_FILE_UPDATED));
+ return 1;
+
+ case api_mldb::MLDB_FILE_PLAYED:
+ OnFilePlayed(filename,
+ ((api_mldb::played_info *)param2)->played,
+ ((api_mldb::played_info *)param2)->count);
+ return 1;
+
+ case api_mldb::MLDB_CLEARED:
+ OnCleared((const wchar_t **)param1, param2);
+ return 1;
+
+ case api_mldb::MLDB_FILE_GET_CLOUD_STATUS:
+ {
+ OnGetCloudStatus((const wchar_t *)param1, (HMENU *)param2);
+ }
+ return 1;
+
+ case api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS:
+ OnProcessCloudStatus(param1, (int *)param2);
+ return 1;
+
+ default:
+ return 0;
+ }
+ }
+ virtual void OnFileAdded(const wchar_t *filename) {}
+ virtual void OnFileRemove_Pre(const wchar_t *filename) {}
+ virtual void OnFileRemove_Post(const wchar_t *filename) {}
+ virtual void OnFileUpdated(const wchar_t *filename, bool from_library) {}
+ virtual void OnFilePlayed(const wchar_t *filename, time_t played, int count) {}
+ virtual void OnCleared(const wchar_t **filenames, int count) {}
+ virtual void OnGetCloudStatus(const wchar_t *filename, HMENU *menu) {}
+ virtual void OnProcessCloudStatus(int menu_item, int *result) {}
+
+#define CBCLASS MLDBCallback
+ START_DISPATCH_INLINE;
+ CB(SYSCALLBACK_GETEVENTTYPE, GetEventType);
+ CB(SYSCALLBACK_NOTIFY, notify);
+ END_DISPATCH;
+#undef CBCLASS
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MLString.cpp b/Src/Plugins/Library/ml_local/MLString.cpp
new file mode 100644
index 00000000..32b8e229
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MLString.cpp
@@ -0,0 +1,116 @@
+#include "main.h"
+#include ".\MLString.h"
+#include <strsafe.h>
+
+#define ALLOCATION_STEP 16
+
+void* MLString::heap = GetProcessHeap();
+
+MLString::MLString(void) : buffer(NULL), allocated(0), cchLen(0)
+{
+}
+
+MLString::MLString(const wchar_t* string) : buffer(NULL), allocated(0), cchLen(0)
+{
+ if (string) Append(string, lstrlenW(string));
+}
+MLString::MLString(unsigned int cchBuffer) : buffer(NULL), allocated(0), cchLen(0)
+{
+ Allocate(cchBuffer);
+}
+
+MLString::MLString(const MLString &copy) : buffer(NULL), allocated(0)
+{
+ cchLen = copy.cchLen;
+ Allocate(cchLen + 1);
+ CopyMemory(buffer, copy.buffer, cchLen*sizeof(wchar_t));
+}
+
+MLString::~MLString(void)
+{
+ if (buffer)
+ {
+ HeapFree(heap, NULL, buffer);
+ allocated = 0;
+ cchLen = 0;
+ buffer = NULL;
+ }
+}
+
+HRESULT MLString::Append(const wchar_t* string, unsigned int cchLength)
+{
+ if (allocated <= cchLen + cchLength)
+ {
+ do { allocated += ALLOCATION_STEP; } while(allocated < cchLen + cchLength + 1);
+ buffer = (wchar_t*) ((!buffer) ? HeapAlloc(heap, NULL, allocated*sizeof(wchar_t)) :
+ HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t)));
+ }
+ if (!buffer) return ERROR_OUTOFMEMORY;
+ CopyMemory(buffer + cchLen, string, cchLength*sizeof(wchar_t));
+ cchLen += cchLength;
+ buffer[cchLen] = 0x0000;
+ return S_OK;
+}
+
+HRESULT MLString::Set(const wchar_t* string, unsigned int cchLength)
+{
+ cchLen = 0;
+ return Append(string, cchLength);
+}
+
+HRESULT MLString::Allocate(unsigned int cchNewSize)
+{
+ if (cchNewSize <= cchLen) return ERROR_BAD_LENGTH;
+ if (allocated >= cchNewSize) return S_OK;
+ allocated = cchNewSize;
+ buffer = (wchar_t*) ((!buffer) ? HeapAlloc(heap, NULL, allocated*sizeof(wchar_t)) :
+ HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t)));
+ return (buffer) ? S_OK : ERROR_OUTOFMEMORY;
+}
+
+void MLString::Compact(void)
+{
+ if (!buffer) return;
+ if (0 == cchLen)
+ {
+ HeapFree(heap, NULL, buffer);
+ allocated = 0;
+ cchLen = 0;
+ buffer = NULL;
+ }
+ else
+ {
+ allocated = cchLen + 1;
+ buffer = (wchar_t*)HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t));
+ }
+}
+
+HRESULT MLString::Format(const wchar_t *format, ...)
+{
+ va_list argList;
+ va_start(argList, format);
+ HRESULT retCode;
+ size_t remaining = 0;
+ retCode = (allocated) ? StringCchVPrintfExW(buffer, allocated, NULL, &remaining, STRSAFE_NULL_ON_FAILURE, format, argList) : STRSAFE_E_INSUFFICIENT_BUFFER;
+ while (STRSAFE_E_INSUFFICIENT_BUFFER == retCode)
+ {
+ int attempt = 1;
+ allocated += ALLOCATION_STEP*attempt;
+ attempt++;
+ buffer = (wchar_t*) ((!buffer) ? HeapAlloc(heap, NULL, allocated*sizeof(wchar_t)) :
+ HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t)));
+ retCode = StringCchVPrintfExW(buffer, allocated, NULL, &remaining, STRSAFE_NULL_ON_FAILURE, format, argList);
+ }
+ va_end(argList);
+ cchLen = (S_OK == retCode) ? allocated - (unsigned int)remaining : 0;
+ return retCode;
+}
+
+HRESULT MLString::CopyTo(MLString *destination)
+{
+ HRESULT retCode = destination->Allocate(allocated);
+ if (S_OK != retCode) return retCode;
+ destination->cchLen = cchLen;
+ CopyMemory(destination->buffer, buffer, cchLen*sizeof(wchar_t));
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MLString.h b/Src/Plugins/Library/ml_local/MLString.h
new file mode 100644
index 00000000..ddf98e58
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MLString.h
@@ -0,0 +1,54 @@
+#ifndef NULLOSFT_MLSTRING_HEADER
+#define NULLOSFT_MLSTRING_HEADER
+
+#include <windows.h>
+
+class MLString
+{
+public:
+ MLString(void);
+ ~MLString(void);
+ MLString(const wchar_t* string);
+ MLString(unsigned int cchBuffer);
+protected:
+ MLString(const MLString &copy);
+
+
+public:
+ HRESULT Set(const wchar_t* string, unsigned int cchLength);
+ HRESULT Append(const wchar_t* string, unsigned int cchLength);
+ const wchar_t* Get(void) { return (cchLen) ? buffer : NULL; }
+ void Clear(void) { cchLen = 0; }
+ unsigned int GetLength(void) { return cchLen; }
+ HRESULT Format(const wchar_t *format, ...);
+ HRESULT CopyTo(MLString *destination);
+
+ HRESULT Set(const wchar_t* string) { return Set(string, lstrlenW(string)); }
+ HRESULT Append(const wchar_t* string) { return Append(string, lstrlenW(string)); }
+
+ // buffer
+ HRESULT Allocate(unsigned int cchNewSize);
+ void Compact(void);
+ wchar_t* GetBuffer(void) { return buffer; }
+ unsigned int GetBufferLength(void) { return allocated; }
+ void UpdateBuffer(void) { cchLen = lstrlenW(buffer); }
+
+ operator const wchar_t *() { return buffer; }
+ operator wchar_t *() { return buffer; }
+ wchar_t& operator [](unsigned int index) { return buffer[index]; }
+ MLString& operator = (const wchar_t *source) { (source) ? Set(source, lstrlenW(source)) : Clear(); return *this;}
+ MLString& operator + (const wchar_t *source) { if (source) Append(source, lstrlenW(source)); return *this;}
+ MLString& operator += (const wchar_t *source) { if (source) Append(source, lstrlenW(source)); return *this;}
+ MLString& operator = (MLString *source) { (source) ? source->CopyTo(this) : Clear(); return *this;}
+ MLString& operator + (MLString *source) { if (source) Append(source->GetBuffer(), source->GetLength()); return *this;}
+ MLString& operator += (MLString *source) { if (source) Append(source->GetBuffer(), source->GetLength()); return *this;}
+
+protected:
+ wchar_t *buffer;
+ unsigned int cchLen;
+ unsigned int allocated;
+
+ static void *heap;
+};
+
+#endif // NULLOSFT_STRING_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/Main.cpp b/Src/Plugins/Library/ml_local/Main.cpp
new file mode 100644
index 00000000..a8aefced
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/Main.cpp
@@ -0,0 +1,586 @@
+#define PLUGIN_VERSION L"3.36"
+#include "main.h"
+#include "resource.h"
+#include "api__ml_local.h"
+#include "..\..\General\gen_ml/config.h"
+#include <commctrl.h>
+#include ".\ml_local.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../replicant/nu/AutoChar.h"
+#include <api/service/waServiceFactory.h>
+#include "../playlist/api_playlistmanager.h"
+#include "mldbApiFactory.h"
+#include "../nu/ServiceWatcher.h"
+#include "LocalMediaCOM.h"
+#include <tataki/export.h>
+#include <strsafe.h>
+#if 0
+// disabled since not building cloud dlls
+#include "../ml_cloud/CloudCallback.h"
+#endif
+
+static ServiceWatcher serviceWatcher;
+static LocalMediaCOM localMediaCOM;
+MLDBAPIFactory mldbApiFactory;
+mlAddTreeItemStruct newTree;
+
+#if 0
+// disabled since not building cloud dlls
+static class CloudCallbacks : public CloudCallback
+{
+ void OnCloudUploadStart(const wchar_t *filename) {
+ SendMessage(m_curview_hwnd, WM_APP + 5, 0, (LPARAM)filename);
+ }
+
+ void OnCloudUploadDone(const wchar_t *filename, int code) {
+ SendMessage(m_curview_hwnd, WM_APP + 5, MAKEWPARAM(code, 1), (LPARAM)filename);
+ }
+} cloudCallback;
+#endif
+
+LRESULT ML_IPC_MENUFUCKER_BUILD = -1, ML_IPC_MENUFUCKER_RESULT = -1;
+int IPC_CLOUD_ENABLED = -1;
+
+int CreateView(int treeItem, HWND parent);
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+void SaveAll();
+
+prefsDlgRecW preferences;
+
+static wchar_t preferencesName[64] = {0};
+
+int winampVersion = 0;
+int substantives = 0;
+int play_enq_rnd_alt = 0;
+
+// Delay load library control << begin >>
+#include <delayimp.h>
+#pragma comment(lib, "delayimp")
+
+bool nde_error = false;
+
+FARPROC WINAPI FailHook(unsigned dliNotify, DelayLoadInfo *dli)
+{
+ nde_error = true;
+ return 0;
+}
+
+/*
+extern "C"
+{
+ PfnDliHook __pfnDliFailureHook2 = FailHook;
+}
+// Delay load library control << end >>
+*/
+
+#define CBCLASS PLCallBackW
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+extern WORD waMenuID;
+DEFINE_EXTERNAL_SERVICE(api_application, WASABI_API_APP);
+DEFINE_EXTERNAL_SERVICE(api_explorerfindfile, WASABI_API_EXPLORERFINDFILE);
+DEFINE_EXTERNAL_SERVICE(api_language, WASABI_API_LNG);
+DEFINE_EXTERNAL_SERVICE(api_syscb, WASABI_API_SYSCB);
+DEFINE_EXTERNAL_SERVICE(api_memmgr, WASABI_API_MEMMGR);
+DEFINE_EXTERNAL_SERVICE(api_albumart, AGAVE_API_ALBUMART);
+DEFINE_EXTERNAL_SERVICE(api_metadata, AGAVE_API_METADATA);
+DEFINE_EXTERNAL_SERVICE(api_playlistmanager, AGAVE_API_PLAYLISTMANAGER);
+DEFINE_EXTERNAL_SERVICE(api_itunes_importer, AGAVE_API_ITUNES_IMPORTER);
+DEFINE_EXTERNAL_SERVICE(api_playlist_generator, AGAVE_API_PLAYLIST_GENERATOR);
+DEFINE_EXTERNAL_SERVICE(api_threadpool, WASABI_API_THREADPOOL);
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+int sse_flag;
+int Init()
+{
+#ifdef _M_IX86
+ int flags_edx;
+ _asm
+ {
+ mov eax, 1
+ cpuid
+ mov flags_edx, edx
+ }
+
+ sse_flag = flags_edx & 0x02000000;
+#else
+ sse_flag=1; // always supported on amd64
+#endif
+ InitializeCriticalSection(&g_db_cs);
+
+ waMenuID = (WORD)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_REGISTER_LOWORD_COMMAND);
+
+ Tataki::Init(plugin.service);
+
+ plugin.service->service_register(&mldbApiFactory);
+
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ ServiceBuild(WASABI_API_EXPLORERFINDFILE, ExplorerFindFileApiGUID);
+ ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceBuild(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid);
+ ServiceBuild(WASABI_API_SYSCB, syscbApiServiceGuid);
+ ServiceBuild(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID);
+ //ServiceBuild(AGAVE_API_PLAYLIST_GENERATOR, api_playlist_generator::getServiceGuid());
+
+ serviceWatcher.WatchWith(plugin.service);
+ serviceWatcher.WatchFor(&AGAVE_API_ITUNES_IMPORTER, api_itunes_importer::getServiceGuid());
+ serviceWatcher.WatchFor(&AGAVE_API_PLAYLIST_GENERATOR, api_playlist_generator::getServiceGuid());
+ WASABI_API_SYSCB->syscb_registerCallback(&serviceWatcher);
+ #if 0
+ // disabled since not building cloud dlls
+ WASABI_API_SYSCB->syscb_registerCallback(&cloudCallback);
+ #endif
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlLocalLangGUID);
+
+ wchar_t buf[2] = {0};
+ if(LoadString(WASABI_API_LNG_HINST,IDS_SUBSTANTIVES,buf,2)){
+ substantives = 1;
+ }
+
+ // this is used to load alternative play/enqueue random strings where
+ // the default implementation will cause pluralisation issue eg de-de
+ buf[0] = 0;
+ if(LoadString(WASABI_API_LNG_HINST,IDS_PLAY_ENQ_RND_ALTERNATIVE,buf,2)){
+ play_enq_rnd_alt = 1;
+ }
+
+ static wchar_t szDescription[256];
+ StringCchPrintfW(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_LOCAL_MEDIA), PLUGIN_VERSION);
+ plugin.description = (char*)szDescription;
+
+ ML_IPC_MENUFUCKER_BUILD = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_build", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ ML_IPC_MENUFUCKER_RESULT = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_result", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_CLOUD_ENABLED = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudEnabled", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+ winampVersion = mediaLibrary.GetWinampVersion();
+
+ mediaLibrary.AddDispatch(L"LocalMedia", &localMediaCOM);
+
+ // this may look unused, but we want to get this here since mediaLibrary will cache the inidir
+ // and then we don't run into weird multithreaded SendMessage issues
+ mediaLibrary.GetIniDirectory();
+ mediaLibrary.GetIniDirectoryW();
+
+ preferences.hInst = WASABI_API_LNG_HINST;
+ preferences.dlgID = IDD_PREFSFR;
+ preferences.proc = (void *)PrefsProc;
+ preferences.name = WASABI_API_LNGSTRINGW_BUF(IDS_LOCAL_MEDIA,preferencesName,64);
+ preferences.where = 6; // 0;
+ mediaLibrary.AddPreferences(preferences);
+
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_AUDIO, TREE_IMAGE_LOCAL_AUDIO, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_MOSTPLAYED, TREE_IMAGE_LOCAL_MOSTPLAYED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_NEVERPLAYED, TREE_IMAGE_LOCAL_NEVERPLAYED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_RECENTLYPLAYED, TREE_IMAGE_LOCAL_RECENTLYPLAYED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_RECENTLYADDED, TREE_IMAGE_LOCAL_RECENTLYADDED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_TOPRATED, TREE_IMAGE_LOCAL_TOPRATED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_VIDEO, TREE_IMAGE_LOCAL_VIDEO, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_PODCASTS, TREE_IMAGE_LOCAL_PODCASTS, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_RECENTLYMODIFIED, TREE_IMAGE_LOCAL_RECENTLYMODIFIED, (BMPFILTERPROC)FILTER_DEFAULT1);
+
+ int ret = init();
+ if (ret) return ret;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW(IDS_LOCAL_MEDIA);
+ nis.item.pszInvariant = L"Local Media";
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.id = 1000; // benski> use the old ID for backwards compatability
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_ITEMID;
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = {sizeof(NAVITEM),0,NIMF_ITEMID,};
+ nvItem.hItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ m_query_tree = nvItem.id;
+
+ loadQueryTree();
+
+ m_query_mode = 0;
+ m_query_metafile = L"default.vmd";
+
+ return ret;
+}
+
+void Quit()
+{
+ serviceWatcher.StopWatching();
+ serviceWatcher.Clear();
+
+ // deregister this first, otherwise people might try to use it after we shut down the database!
+ plugin.service->service_deregister(&mldbApiFactory);
+
+ UnhookPlaylistEditor();
+
+ Scan_Kill();
+
+ closeDb();
+
+ delete(g_view_metaconf);
+ g_view_metaconf = 0;
+
+ delete g_config;
+ g_config = NULL;
+
+ KillArtThread();
+
+ for (QueryList::iterator i = m_query_list.begin();i != m_query_list.end();i++)
+ {
+ queryItem *item = i->second;
+ if (item)
+ {
+ free(item->metafn);
+ free(item->name);
+ free(item->query);
+ }
+ free(item);
+ }
+ m_query_list.clear();
+ DeleteCriticalSection(&g_db_cs);
+
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid);
+ ServiceRelease(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceRelease(WASABI_API_SYSCB, syscbApiServiceGuid);
+ ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_ITUNES_IMPORTER, api_itunes_importer::getServiceGuid());
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+ ServiceRelease(AGAVE_API_PLAYLIST_GENERATOR, api_playlist_generator::getServiceGuid());
+
+ Tataki::Quit();
+}
+
+INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ switch (message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW: // param1 = param of tree item, param2 is HWND of parent. return HWND if it is us
+ if (param1 == m_query_tree || m_query_list[param1])
+ return (INT_PTR)onTreeViewSelectChange((HWND)param2);
+ else
+ return 0;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, m_query_tree);
+ if (hItem == myItem)
+ {
+ queriesContextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+ return 1;
+ }
+ else
+ {
+ NAVITEM nvItem = {sizeof(NAVITEM),hItem,NIMF_ITEMID,};
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ if (m_query_list[nvItem.id])
+ {
+ view_queryContextMenu(param1, (HWND)param2, MAKEPOINTS(param3), nvItem.id);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ case ML_MSG_TREE_ONCLICK:
+ if (param1 == m_query_tree)
+ return OnLocalMediaClick(param2, (HWND)param3);
+ else if (m_query_list[param1])
+ return OnLocalMediaItemClick(param2, param1, (HWND)param3);
+ else
+ return 0;
+
+ case ML_MSG_TREE_ONDRAG:
+ if (m_query_list[param1])
+ {
+ int *type = reinterpret_cast<int *>(param3);
+ *type = ML_TYPE_ITEMRECORDLIST;
+ return 1;
+ }
+ return 0;
+
+ case ML_MSG_TREE_ONDROP:
+ if (param3 != NULL && param1 != NULL) // here we go - finishing moving view
+ {
+ if (m_query_list[param1])
+ {
+ if (param3 == m_query_tree || m_query_list[param3])
+ {
+ QueryList::iterator src = m_query_list.find(param1);
+ mediaLibrary.RemoveTreeItem(src->first);
+ MLTREEITEMW srcItem = {sizeof(MLTREEITEMW), };
+
+ srcItem.title = src->second->name;
+ srcItem.hasChildren = 0;
+ srcItem.parentId = m_query_tree;
+ srcItem.id = param3;
+ srcItem.imageIndex = src->second->imgIndex;
+ mediaLibrary.InsertTreeItem(srcItem);
+
+ auto item = src->second;
+ m_query_list.erase(param1);
+ m_query_list.insert({ srcItem.id, item });
+
+ saveQueryTree();
+ mediaLibrary.SelectTreeItem(srcItem.id);
+ return 1;
+ }
+ }
+ }
+ else if (m_query_list[param1])
+ {
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_ITEMRECORDLISTW;
+ m.p = *(POINT *)param2;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR | ML_HANDLEDRAG_FLAG_NAME;
+
+ // build an itemRecordList
+ queryItem *item = m_query_list[param1];
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, item->query);
+ itemRecordListW obj = {0, };
+ saveQueryToListW(&viewconf, s, &obj, 0, 0, (resultsniff_funcW)-1);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ m.data = (void *) & obj;
+ AutoChar whatsThisNameUsedForAnyway(item->name);
+ m.name = whatsThisNameUsedForAnyway;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ if (m.result < 1)
+ {
+ m.result = 0;
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA={0,};
+ convertRecordList(&objA, &obj);
+ m.data = (void*)&objA;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ freeRecordList(&objA);
+ }
+ freeRecordList(&obj);
+ }
+ return 0;
+
+ case ML_MSG_CONFIG:
+ mediaLibrary.GoToPreferences(preferences._id);
+ return TRUE;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = param1;
+ groupBtn = param2;
+ PostMessage(m_curview_hwnd, WM_APP + 104, param1, param2);
+ return 0;
+
+ case ML_MSG_ONSENDTOBUILD:
+ if (param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_ITEMRECORDLIST ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW)
+ {
+ if (!myMenu) mediaLibrary.AddToSendTo(WASABI_API_LNGSTRINGW(IDS_ADD_TO_LOCAL_MEDIA), param2, (INT_PTR)MessageProc);
+ }
+ break;
+
+ case ML_MSG_ONSENDTOSELECT:
+ case ML_MSG_TREE_ONDROPTARGET: // return -1 if not allowed, 1 if allowed, or 0 if not our tree item
+ // set with droptarget defaults =)
+ INT_PTR type, data;
+
+ if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)MessageProc) return 0;
+
+ type = param1;
+ data = param2;
+ }
+ else
+ {
+ if (param1 != m_query_tree && !m_query_list[param1]) return 0;
+
+ type = param2;
+ data = param3;
+
+ if (!data)
+ {
+ return (type == ML_TYPE_ITEMRECORDLISTW || type == ML_TYPE_ITEMRECORDLIST ||
+ type == ML_TYPE_FILENAMES || type == ML_TYPE_FILENAMESW ||
+ type == ML_TYPE_PLAYLIST) ? 1 : -1;
+ }
+ }
+
+ if (data)
+ {
+ if (type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordList *p = (itemRecordList*)data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddToMediaLibrary(p->Items[x].filename);
+
+ return 1;
+ }
+ else if (type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *p = (itemRecordListW*)data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddToMediaLibrary(p->Items[x].filename);
+
+ return 1;
+ }
+ else if (type == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist * pl = (mlPlaylist *)data;
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(pl->filename, &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(pl->filename);
+ }
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMES)
+ {
+ const char *p = (const char*)data;
+ while (p && *p)
+ {
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(AutoWide(p), &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(p);
+ }
+ p += strlen(p) + 1;
+ }
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMESW)
+ {
+ const wchar_t *p = (const wchar_t*)data;
+ while (p && *p)
+ {
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(p, &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(p);
+ }
+ p += wcslen(p) + 1;
+ }
+ return 1;
+ }
+ }
+ break;
+
+ case ML_MSG_TREE_ONKEYDOWN:
+ {
+ NMTVKEYDOWN *p = (NMTVKEYDOWN*)param2;
+ int ctrl = (GetAsyncKeyState(VK_CONTROL)&0x8000);
+ int shift = (GetAsyncKeyState(VK_SHIFT)&0x8000);
+
+ if (p->wVKey == VK_INSERT && !shift)
+ {
+ if (!ctrl)
+ addNewQuery(plugin.hwndLibraryParent);
+ else
+ if (!g_bgscan_scanning) SendMessage(plugin.hwndLibraryParent, WM_USER + 575, 0xffff00dd, 0);
+
+ PostMessageW(plugin.hwndLibraryParent, WM_NEXTDLGCTL, (WPARAM)param3, (LPARAM)TRUE);
+ return 1;
+ }
+ else if (m_query_list[param1])
+ {
+ if (p->wVKey == VK_F2 && !shift && !ctrl)
+ {
+ queryEditItem(param1);
+ }
+ else if (p->wVKey == VK_DELETE && !shift && !ctrl)
+ {
+ queryDeleteItem(plugin.hwndLibraryParent, param1);
+ }
+ else
+ break;
+
+ PostMessageW(plugin.hwndLibraryParent, WM_NEXTDLGCTL, (WPARAM)param3, (LPARAM)TRUE);
+ return 1;
+ }
+ return 0;
+ }
+
+ case ML_IPC_HOOKEXTINFO:
+ if (IPC_HookExtInfo(param1)) return 1;
+ break;
+
+ case ML_IPC_HOOKEXTINFOW:
+ if (IPC_HookExtInfoW(param1)) return 1;
+ break;
+
+ case ML_IPC_HOOKTITLEW:
+ if (IPC_HookTitleInfo(param1)) return 1;
+ break;
+
+ case ML_MSG_PLAYING_FILE:
+ if (param1) onStartPlayFileTrack((const wchar_t *)param1, false);
+ break;
+ }
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_local.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/Main.h b/Src/Plugins/Library/ml_local/Main.h
new file mode 100644
index 00000000..bde0bc52
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/Main.h
@@ -0,0 +1,71 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include <windows.h>
+#include <windowsx.h>
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/DialogSkinner.h"
+#include "../winamp/wa_ipc.h"
+#include "ml_local.h"
+#include <shlobj.h>
+#include "../nde/nde.h"
+#include "../nde/nde_c.h"
+#include <time.h>
+#include "../Agave/Language/api_language.h"
+#include "..\..\General\gen_ml/config.h"
+#include "api__ml_local.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../replicant/nu/AutoChar.h"
+#include "AlbumArtCache.h"
+#include "./local_menu.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+
+#define WM_QUERYFILEINFO (WM_USER + 65)
+#define WM_SHOWFILEINFO (WM_USER + 64) // wParam - bForceUpdate, lParam - pszFileName
+
+extern winampMediaLibraryPlugin plugin;
+extern int winampVersion;
+extern int substantives;
+extern int play_enq_rnd_alt;
+extern bool nde_error;
+extern HMENU wa_playlists_cmdmenu;
+extern prefsDlgRecW preferences;
+extern HWND hwndSearchGlobal;
+
+void EatKeyboard();
+void HookPlaylistEditor();
+void UnhookPlaylistEditor();
+extern bool skipTitleInfo;
+extern wchar_t *recent_fn;
+
+extern int IPC_GET_CLOUD_HINST, IPC_GET_CLOUD_ACTIVE, IPC_CLOUD_ENABLED;
+extern LRESULT ML_IPC_MENUFUCKER_BUILD;
+extern LRESULT ML_IPC_MENUFUCKER_RESULT;
+
+extern int groupBtn, enqueuedef;
+extern LARGE_INTEGER freq;
+
+class PLCallBackW : public ifc_playlistloadercallback
+{
+public:
+ PLCallBackW(void){};
+ ~PLCallBackW(void){};
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ mediaLibrary.AddToMediaLibrary(filename);
+ }
+ RECVS_DISPATCH;
+};
+
+extern "C" void qsort_itemRecord(void *base, size_t num, const void *context,
+int (__fastcall *comp)(const void *, const void *, const void *));
+
+void MigrateArtCache();
+void setCloudValue(itemRecordW *item, const wchar_t* value);
+
+void onStartPlayFileTrack(const wchar_t *filename, bool resume);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ReIndexUI.cpp b/Src/Plugins/Library/ml_local/ReIndexUI.cpp
new file mode 100644
index 00000000..83e0c968
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ReIndexUI.cpp
@@ -0,0 +1,333 @@
+#include "Main.h"
+#include "ml_local.h"
+#include "resource.h"
+
+static INT_PTR CALLBACK CompactWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, 400));
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, 0, 0);
+
+ int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (progress[1] == -102)
+ {
+ progress[1] = -101;
+ SetWindowTextW(hwndDlg, WASABI_API_LNGSTRINGW(IDS_REFRESH_FILESIZE_DATEADDED));
+ }
+
+ SetTimer(hwndDlg, 666, 500, 0);
+ break;
+ }
+ case WM_TIMER:
+ if (wParam == 666)
+ {
+ int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (progress[0] == 666)
+ {
+ KillTimer(hwndDlg, 666);
+ EndDialog(hwndDlg, 0);
+ }
+ else if (progress[1] != -101)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, progress[1] + 300, 0);
+ }
+ else
+ {
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, progress[0] + 100, 0);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static DWORD CALLBACK CompactThread(LPVOID param)
+{
+ WASABI_API_DIALOGBOXPARAMW(IDD_REINDEX, NULL, CompactWndProc, (LPARAM)param);
+ return 0;
+}
+
+static int sortFunc(const void *a, const void *b)
+{
+ const wchar_t **fn1 = (const wchar_t **)a;
+ const wchar_t **fn2 = (const wchar_t **)b;
+ return _wcsicmp(*fn1, *fn2);
+}
+
+void RetypeFilename(nde_table_t table)
+{
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+
+ int progress[2] = {-100, -101};
+ DWORD threadId = 0;
+ HANDLE compactThread = 0;
+
+ nde_scanner_t pruneScanner = NDE_Table_CreateScanner(table);
+ if (pruneScanner)
+ {
+ bool first=true;
+ int recordNum = 0;
+ NDE_Scanner_First(pruneScanner);
+ while (!NDE_Scanner_EOF(pruneScanner))
+ {
+ progress[0] = MulDiv(recordNum, 100, totalRecords)-100;
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, MAINTABLE_ID_FILENAME);
+ if (f && NDE_Field_GetType(f) == FIELD_STRING)
+ {
+ wchar_t *s = NDE_StringField_GetString(f);
+ ndestring_retain(s);
+
+ NDE_Scanner_DeleteField(pruneScanner, f);
+
+ nde_field_t new_f = NDE_Scanner_NewFieldByID(pruneScanner, MAINTABLE_ID_FILENAME);
+ NDE_StringField_SetString(new_f, s);
+
+ ndestring_release(s);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ else if (f)
+ {
+ first = false; // skips creating the thread
+ break;
+ }
+
+ recordNum++;
+ NDE_Scanner_Next(pruneScanner);
+ if (first)
+ {
+ compactThread = CreateThread(0, 0, CompactThread, progress, 0, &threadId);
+ DumpArtCache(); // go ahead and dump the album art cache if we had to rebuild this table. ideally we could perform the same logic but this is easier and it's 1 in the morning and i don't feel like doing it :)
+ }
+ first=false;
+ }
+
+ NDE_Table_DestroyScanner(table, pruneScanner);
+ if (compactThread)
+ NDE_Table_Sync(table);
+ }
+ progress[0] = 666;
+ if (compactThread)
+ {
+ WaitForSingleObject(compactThread, INFINITE);
+ CloseHandle(compactThread);
+ }
+}
+
+void RefreshFileSizeAndDateAddedTable(nde_table_t table)
+{
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+
+ int progress[2] = {-100, -102};
+ DWORD threadId = 0;
+ HANDLE compactThread = 0;
+
+ nde_scanner_t scanner = NDE_Table_CreateScanner(table);
+ if (scanner)
+ {
+ bool first=true;
+ int recordNum = 0;
+ NDE_Scanner_First(scanner);
+ while (!NDE_Scanner_EOF(scanner))
+ {
+ progress[0] = MulDiv(recordNum, 400, totalRecords);
+
+ // converts filesize from a int and kb scaled value to the actual filesize as a int64
+ nde_field_t f = NDE_Scanner_GetFieldByID(scanner, MAINTABLE_ID_FILESIZE);
+ if (f && NDE_Field_GetType(f) == FIELD_INTEGER)
+ {
+ __int64 size = NDE_IntegerField_GetValue(f);
+ if (size) size *= 1024;
+
+ NDE_Scanner_DeleteField(scanner, f);
+
+ nde_field_t new_f = NDE_Scanner_NewFieldByType(scanner, FIELD_INT64, MAINTABLE_ID_FILESIZE);
+ NDE_Int64Field_SetValue(new_f, size);
+
+ NDE_Scanner_Post(scanner);
+ }
+
+ // takes the lastupd value and applies it to dateadded so we've got something to use
+ f = NDE_Scanner_GetFieldByID(scanner, MAINTABLE_ID_LASTUPDTIME);
+ if (f && NDE_Field_GetType(f) == FIELD_DATETIME)
+ {
+ int lastupd = NDE_IntegerField_GetValue(f);
+ nde_field_t new_f = NDE_Scanner_NewFieldByID(scanner, MAINTABLE_ID_DATEADDED);
+ NDE_IntegerField_SetValue(new_f, lastupd);
+
+ NDE_Scanner_Post(scanner);
+ }
+
+ NDE_Scanner_Next(scanner);
+ recordNum++;
+ if (first)
+ {
+ compactThread = CreateThread(0, 0, CompactThread, progress, 0, &threadId);
+ }
+ first=false;
+ }
+
+ NDE_Table_DestroyScanner(table, scanner);
+ if (compactThread)
+ NDE_Table_Sync(table);
+ }
+ progress[0] = 666;
+ if (compactThread)
+ {
+ WaitForSingleObject(compactThread, INFINITE);
+ CloseHandle(compactThread);
+ }
+}
+
+void ReindexTable(nde_table_t table)
+{
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+
+ int progress[2] = {-100, -101};
+ DWORD threadId;
+ HANDLE compactThread = CreateThread(0, 0, CompactThread, progress, 0, &threadId);
+
+ nde_scanner_t pruneScanner = NDE_Table_CreateScanner(table);
+ if (pruneScanner)
+ {
+ int recordNum = 0;
+ NDE_Scanner_First(pruneScanner);
+ while (!NDE_Scanner_EOF(pruneScanner))
+ {
+ int total = MulDiv(recordNum, 100, totalRecords);
+ progress[0] = total - 100;
+ #ifndef elementsof
+ #define elementsof(x) (sizeof(x)/sizeof(*x))
+ #endif
+ unsigned char STR_IDS[] = {MAINTABLE_ID_TITLE, MAINTABLE_ID_ARTIST, MAINTABLE_ID_ALBUM, MAINTABLE_ID_GENRE,
+ MAINTABLE_ID_COMMENT, MAINTABLE_ID_GRACENOTE_ID, MAINTABLE_ID_ALBUMARTIST,
+ MAINTABLE_ID_TRACKGAIN, MAINTABLE_ID_PUBLISHER, MAINTABLE_ID_COMPOSER,
+ MAINTABLE_ID_PODCASTCHANNEL, MAINTABLE_ID_GRACENOTEFILEID, MAINTABLE_ID_GRACENOTEEXTDATA,
+ MAINTABLE_ID_CATEGORY, MAINTABLE_ID_CODEC, MAINTABLE_ID_DIRECTOR, MAINTABLE_ID_PRODUCER
+ };
+ for (size_t i = 0;i != elementsof(STR_IDS);i++)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, STR_IDS[i]);
+ if (f)
+ {
+ const wchar_t *s = NDE_StringField_GetString(f);
+ if (!s || !*s)
+ {
+ NDE_Scanner_DeleteField(pruneScanner, f);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ }
+ }
+
+ unsigned char INT_IDS_ZEROOK[] = {MAINTABLE_ID_LENGTH, MAINTABLE_ID_PLAYCOUNT, MAINTABLE_ID_FILESIZE,
+ MAINTABLE_ID_TYPE, MAINTABLE_ID_ISPODCAST, MAINTABLE_ID_LOSSLESS
+ };
+ for (size_t i = 0;i != elementsof(INT_IDS_ZEROOK);i++)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, INT_IDS_ZEROOK[i]);
+ if (f)
+ {
+ int s = NDE_IntegerField_GetValue(f);
+ if (s < 0)
+ {
+ NDE_Scanner_DeleteField(pruneScanner, f);
+ NDE_Scanner_Post(pruneScanner);
+
+ }
+ }
+ }
+
+ unsigned char INT_IDS[] = {MAINTABLE_ID_TRACKNB, MAINTABLE_ID_LASTUPDTIME, MAINTABLE_ID_LASTPLAY, MAINTABLE_ID_RATING,
+ MAINTABLE_ID_FILETIME, MAINTABLE_ID_BITRATE, MAINTABLE_ID_DISC, MAINTABLE_ID_BPM, MAINTABLE_ID_DISCS,
+ MAINTABLE_ID_TRACKS, MAINTABLE_ID_PODCASTPUBDATE, MAINTABLE_ID_FILESIZE, MAINTABLE_ID_DATEADDED
+ };
+ for (size_t i = 0;i != elementsof(INT_IDS);i++)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, INT_IDS[i]);
+ if (f)
+ {
+ int s = NDE_IntegerField_GetValue(f);
+ if (s <= 0)
+ {
+ NDE_Scanner_DeleteField(pruneScanner, f);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ }
+ }
+ NDE_Scanner_Next(pruneScanner);
+ recordNum++;
+ }
+
+ NDE_Table_DestroyScanner(table, pruneScanner);
+ NDE_Table_Sync(table);
+ }
+
+ NDE_Table_Compact(table, &progress[0]);
+ assert(((Table *)table)->CheckIndexing());
+ // now remove duplicates
+ nde_scanner_t dupscanner = NDE_Table_CreateScanner(table);
+ if (dupscanner)
+ {
+ int count = NDE_Scanner_GetRecordsCount(dupscanner);
+ wchar_t **filenames = new wchar_t *[count];
+ int i = 0;
+ for (NDE_Scanner_First(dupscanner);!NDE_Scanner_EOF(dupscanner);NDE_Scanner_Next(dupscanner))
+ {
+ nde_field_t fileName = NDE_Scanner_GetFieldByID(dupscanner, MAINTABLE_ID_FILENAME);
+ if (fileName)
+ {
+ filenames[i] = NDE_StringField_GetString(fileName);
+ ndestring_retain(filenames[i]);
+ i++;
+ }
+ }
+ count = i;
+ if (count)
+ {
+ qsort(filenames, count, sizeof(wchar_t *), sortFunc);
+ for (int x = 0;x < (count - 1);x++)
+ {
+ int total = MulDiv(x, 100, count);
+ progress[1] = total - 100;
+
+ if (!_wcsicmp(filenames[x], filenames[x+1]))
+ {
+ wchar_t query[1024] = {0};
+ wnsprintfW(query, 1024, L"filename == \"%s\"", filenames[x]);
+
+ nde_scanner_t scanner = NDE_Table_CreateScanner(table);
+ NDE_Scanner_Query(scanner, query);
+
+ NDE_Scanner_First(scanner);
+ NDE_Scanner_Next(scanner);
+ while (!NDE_Scanner_EOF(scanner))
+ {
+ NDE_Scanner_Delete(scanner);
+ NDE_Scanner_Post(scanner);
+ NDE_Scanner_Next(scanner);
+ }
+ NDE_Table_DestroyScanner(table, scanner);
+ }
+ ndestring_release(filenames[x]);
+ filenames[x]=0;
+ }
+ }
+ delete[] filenames;
+ }
+ NDE_Table_DestroyScanner(table, dupscanner);
+ NDE_Table_Sync(table);
+ NDE_Table_Compact(table, &progress[1]);
+
+ progress[0] = 666;
+ WaitForSingleObject(compactThread, INFINITE);
+ CloseHandle(compactThread);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/SaveQuery.cpp b/Src/Plugins/Library/ml_local/SaveQuery.cpp
new file mode 100644
index 00000000..35cac30c
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/SaveQuery.cpp
@@ -0,0 +1,406 @@
+#include "main.h"
+#include "ml_local.h"
+#include <shlwapi.h>
+#include <assert.h>
+#include "../nu/sort.h"
+
+/*
+#ifdef _M_IX86
+#undef min
+static inline int min(int x, int y)
+{
+ return y+((x-y)>>31)&(x-y);
+}
+#undef max
+static inline int max(int x, int y)
+{
+ return x-(((x-y)>>(31))&(x-y));
+}
+
+#elif defined(_WIN64)
+#undef min
+static inline int min(int x, int y)
+{
+ return y+((x-y)>>63)&(x-y);
+}
+#undef max
+static inline int max(int x, int y)
+{
+ return x-(((x-y)>>(63))&(x-y));
+}
+#endif
+*/
+
+#ifdef _M_IX86
+const size_t convert_max_characters = 16; // it's actually 11, but this probably causes less memory fragmentation
+#elif defined(_M_X64)
+const size_t convert_max_characters = 20;
+#endif
+
+static inline int Compare_Int_NegativeIsNull(int v1, int v2)
+{
+ v1 = max(v1,0);
+ v2 = max(v2,0);
+ return(v1 - v2);
+}
+
+typedef int (__fastcall *SortFunction)(const itemRecordW *a, const itemRecordW *b);
+#define SORT(field) SortBy ## field
+#define STRING_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return WCSCMP_NULLOK(a->field, b->field); }
+#define EXT_STRING_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.field); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.field); return WCSCMP_NULLOK(a_field, b_field);}
+#define TIME_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { time_t v1 = (time_t)a->field; time_t v2 = (time_t)b->field; if (v1 == -1) v1 = 0; if (v2 == -1) v2 = 0; return (int)(v1 - v2);}
+#define EXT_TIME_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.dateadded); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.dateadded); time_t v1 = a_field?_wtoi(a_field):0; time_t v2 = b_field?_wtoi(b_field):0; if (v1 == -1) v1 = 0; if (v2 == -1) v2 = 0; return (int)(v1 - v2);}
+#define INT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return Compare_Int_NegativeIsNull(a->field, b->field); }
+#define EXT_INT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.field); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.field); int v1 = a_field?_wtoi(a_field):0; int v2 = b_field?_wtoi(b_field):0; return (v1-v2);}
+#define FLOAT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return FLOATWCMP_NULLOK(a->field, b->field); }
+STRING_SORT(artist);
+STRING_SORT(title);
+STRING_SORT(album);
+INT_SORT(length);
+INT_SORT(track);
+STRING_SORT(genre);
+INT_SORT(year);
+static int __fastcall SORT(filespec)(const itemRecordW *a, const itemRecordW *b)
+{
+ // remove path before compare...
+ wchar_t * af = L"";
+ if (a->filename)
+ af = PathFindFileNameW(a->filename);
+
+ wchar_t * bf = L"";
+ if (b->filename)
+ bf = PathFindFileNameW(b->filename);
+
+ return _wcsicmp(af, bf);
+}
+
+INT_SORT(rating);
+INT_SORT(playcount);
+TIME_SORT(lastplay);
+TIME_SORT(lastupd);
+TIME_SORT(filetime);
+STRING_SORT(comment);
+INT_SORT(filesize);
+INT_SORT(bitrate);
+INT_SORT(type);
+INT_SORT(disc);
+STRING_SORT(albumartist);
+STRING_SORT(filename);
+FLOAT_SORT(replaygain_album_gain);
+FLOAT_SORT(replaygain_track_gain);
+STRING_SORT(publisher);
+STRING_SORT(composer);
+static int __fastcall SORT(extension)(const itemRecordW *a, const itemRecordW *b)
+{
+ wchar_t *extA = PathFindExtensionW(a->filename);
+ wchar_t *extB = PathFindExtensionW(b->filename);
+ if (extA && *extA)
+ extA++;
+ if (extB && *extB)
+ extB++;
+ return WCSCMP_NULLOK(extA, extB);
+}
+EXT_INT_SORT(ispodcast);
+EXT_STRING_SORT(podcastchannel);
+EXT_STRING_SORT(podcastpubdate);
+INT_SORT(bpm);
+STRING_SORT(category);
+EXT_STRING_SORT(director);
+EXT_STRING_SORT(producer);
+EXT_TIME_SORT(dateadded);
+EXT_STRING_SORT(cloud);
+
+static int __fastcall SORT(dimension)(const itemRecordW *a, const itemRecordW *b)
+{
+ wchar_t *a_width = getRecordExtendedItem_fast(a, extended_fields.width);
+ wchar_t *b_width = getRecordExtendedItem_fast(b, extended_fields.width);
+ wchar_t *a_height = getRecordExtendedItem_fast(a, extended_fields.height);
+ wchar_t *b_height = getRecordExtendedItem_fast(b, extended_fields.height);
+ int w1 = a_width?_wtoi(a_width):0;
+ int w2 = b_width?_wtoi(b_width):0;
+ int h1 = a_height?_wtoi(a_height):0;
+ int h2 = b_height?_wtoi(b_height):0;
+ if (w1 != w2)
+ return (w1-w2);
+ else
+ return (h1-h2);
+}
+// DISABLED FOR 5.62 RELEASE - DRO
+//EXT_STRING_SORT(codec);
+
+#define FORCE_ASCENDING ((SortFunction)-2)
+// this is where we define sort orders!
+static const SortFunction sortOrder[MEDIAVIEW_COL_NUMS][MEDIAVIEW_COL_NUMS+2] =
+{
+ {SORT(artist), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), SORT(title), 0}, // Artist
+ {SORT(title), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(album), FORCE_ASCENDING, SORT(albumartist), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(length), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(track), FORCE_ASCENDING, SORT(title), SORT(artist), SORT(album), SORT(disc), 0},
+ {SORT(genre), FORCE_ASCENDING, SORT(albumartist), SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(year), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filespec), SORT(filename),0},
+ {SORT(rating), SORT(playcount), SORT(lastplay), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(playcount), SORT(lastplay), FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(lastplay), FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(lastupd), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filetime), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(comment), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filesize),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(bitrate), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(type), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(disc), FORCE_ASCENDING, SORT(track), SORT(title), SORT(artist), SORT(album), SORT(disc), 0},
+ {SORT(albumartist), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filename), 0},
+ {SORT(replaygain_album_gain), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(replaygain_track_gain), FORCE_ASCENDING, SORT(title), 0},
+ {SORT(publisher),FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(composer), FORCE_ASCENDING,SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(extension), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(ispodcast), FORCE_ASCENDING, SORT(podcastchannel), SORT(title), 0},
+ {SORT(podcastchannel), SORT(title), 0},
+ {SORT(podcastpubdate), 0},
+ {SORT(bpm), 0}, // TODO
+ {SORT(category),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(director),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(producer),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(dimension), 0},
+ // DISABLED FOR 5.62 RELEASE - DRO
+ //{SORT(codec), 0},
+ {SORT(dateadded), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(cloud), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+};
+
+/* ---- */
+struct SortRules
+{
+ int by;
+ int dir;
+};
+static int __fastcall sortFuncW(const void *elem1, const void *elem2, const void *context)
+{
+ assert(sizeof(sortOrder) / sizeof(*sortOrder) == MEDIAVIEW_COL_NUMS);
+
+ const itemRecordW *a = (const itemRecordW*)elem1;
+ const itemRecordW *b = (const itemRecordW*)elem2;
+
+ const SortRules *rules = (SortRules *)context;
+ int use_by = rules->by;
+ int use_dir = !!rules->dir;
+
+ int dir_values[] = {-1, 1};
+
+#define RETIFNZ(v) if ((v)<0) return dir_values[use_dir]; if ((v)>0) return -dir_values[use_dir];
+
+ const SortFunction *order =sortOrder[use_by];
+ int i=0;
+ while (order[i])
+ {
+ if (order[i]==FORCE_ASCENDING)
+ {
+ use_dir=0;
+ i++;
+ continue;
+ }
+ int v = order[i](a, b);
+ RETIFNZ(v);
+ i++;
+ }
+ return 0;
+}
+
+void sortResults(int column, int dir, itemRecordListW *obj) // sorts the results based on the current sort mode
+{
+ if (obj->Size > 1)
+ {
+ SortRules rules = {column, dir};
+ qsort_itemRecord(obj->Items, obj->Size, &rules, sortFuncW);
+ }
+}
+
+void sortResults(C_Config *viewconf, itemRecordListW *obj) // sorts the results based on the current sort mode
+{
+ if (viewconf)
+ {
+ SortRules rules = {viewconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST), viewconf->ReadInt(L"mv_sort_dir", 0)};
+ if (obj->Size > 1)
+ qsort_itemRecord(obj->Items, obj->Size, &rules, sortFuncW);
+ }
+}
+
+void setCloudValue(itemRecordW *item, const wchar_t* value)
+{
+ wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t));
+ lstrcpynW(temp, value, convert_max_characters);
+ setRecordExtendedItem(item, extended_fields.cloud, temp);
+ ndestring_release(temp);
+}
+
+int saveQueryToListW(C_Config *viewconf, nde_scanner_t s, itemRecordListW *obj,
+ CloudFiles *uploaded, CloudFiles *uploading,
+ resultsniff_funcW cb, int user32, int *killswitch, __int64 *total_bytes)
+{
+ __int64 total_kb = 0;
+
+ emptyRecordList(obj);
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_First(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+
+ int r = 0;
+ unsigned int total_length_s = 0;
+ unsigned int uncert_cnt = 0;
+ unsigned int cert_cnt = 0;
+ int recordCount = NDE_Scanner_GetRecordsCount(s);
+ allocRecordList(obj, recordCount);
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(obj->Items[obj->Size].filename);
+ total_kb += ScannerRefToObjCacheNFNW(s, obj, (cb == (resultsniff_funcW)-1 ? true : false));
+
+ // look for any files known to the cloud so the icon can be set accordingly
+ // TODO possibly need to do more here to better cope with transient states??
+ if (uploaded && uploaded->size() > 0)
+ {
+ bool found = false;
+ for(CloudFiles::iterator iter = uploaded->begin(); iter != uploaded->end(); iter++)
+ {
+ if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter)) == 0)
+ {
+ bool pending = false;
+ found = true;
+
+ // catches files being uploaded but not fully known to be in the cloud, etc
+ if (uploading && uploading->size() > 0)
+ {
+ for(CloudFiles::iterator iter2 = uploading->begin(); iter2 != uploading->end(); iter2++)
+ {
+ if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter2)) == 0)
+ {
+ pending = true;
+ setCloudValue(&obj->Items[obj->Size - 1], L"5");
+ break;
+ }
+ }
+ }
+
+ if (!pending)
+ {
+ setCloudValue(&obj->Items[obj->Size - 1], L"0");
+ }
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ // catches files being uploaded but not fully known to be in the cloud, etc
+ if (uploading && uploading->size() > 0)
+ {
+ for(CloudFiles::iterator iter = uploading->begin(); iter != uploading->end(); iter++)
+ {
+ if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter)) == 0)
+ {
+ found = true;
+ setCloudValue(&obj->Items[obj->Size - 1], L"5");
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ setCloudValue(&obj->Items[obj->Size - 1], L"4");
+ }
+ }
+ }
+
+ int thisl = obj->Items[obj->Size - 1].length;
+
+ if (thisl > 0)
+ {
+ total_length_s += thisl;
+ cert_cnt++;
+ }
+ else
+ {
+ uncert_cnt++;
+ }
+ }
+
+ r = NDE_Scanner_Next(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ }
+ while (r && !NDE_Scanner_EOF(s));
+
+ if (((Table *)g_table)->HasErrors()) // TODO: don't use C++ NDE API
+ {
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Compact(g_table);
+ if (m_media_scanner)
+ {
+ m_media_scanner = NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (total_bytes)
+ {
+ // maintain compatibility as needed
+ if (cb == (resultsniff_funcW)-1)
+ *total_bytes = total_kb * 1024;
+ else
+ *total_bytes = total_kb;
+ }
+ compactRecordList(obj);
+
+ if (cb && cb != (resultsniff_funcW)-1)
+ {
+ cb(obj->Items, obj->Size, user32, killswitch);
+ }
+
+ if (killswitch && *killswitch) return 0;
+
+ sortResults(viewconf, obj);
+
+ if (killswitch && *killswitch) return 0;
+
+ if (uncert_cnt)
+ {
+ if (cert_cnt)
+ {
+ __int64 avg_len = (__int64)total_length_s / cert_cnt;
+ total_length_s += (DWORD)(avg_len * uncert_cnt);
+ }
+ total_length_s |= (1 << 31);
+ }
+
+ return total_length_s;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp b/Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp
new file mode 100644
index 00000000..ae49d0a0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp
@@ -0,0 +1,475 @@
+#include "main.h"
+#include "./scanfolderbrowser.h"
+#include "resource.h"
+
+/// New controls
+#define IDC_CHK_BGSCAN 0x1980
+#define IDC_TLB_BOOKMARKS 0x1978
+#define TOOLBAR_BTN_STARTID 0x1000
+
+#define PATHTYPE_CSIDL 0
+#define PATHTYPE_STRING 1
+#define PATHTYPE_PIDL 2
+
+typedef struct __FOLDER
+{
+ int pathtype;
+ INT_PTR path;
+ LPCWSTR caption; // if NULL display name will be used
+ LPCWSTR tooltip; // if NULL display name will be used
+} FOLDER;
+
+typedef struct _FBUTTON
+{
+ LPITEMIDLIST pidl;
+ LPWSTR caption;
+ LPWSTR tooltip;
+ INT iImage;
+} FBUTTON;
+
+static const FOLDER BOOKMARKS[] =
+{
+ {PATHTYPE_STRING, (INT_PTR)L"C:\\Program Files\\Winamp", NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_MYMUSIC, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_MYVIDEO, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_PERSONAL, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_DESKTOP, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_DRIVES, NULL, NULL},
+ {PATHTYPE_CSIDL, CSIDL_NETWORK, NULL, NULL },
+};
+
+static HIMAGELIST hSysImageList;
+
+//// XP THEME - BEGIN
+typedef HANDLE HTHEME;
+
+typedef HRESULT (WINAPI *XPT_CLOSETHEMEDATA)(HTHEME);
+typedef BOOL (WINAPI *XPT_ISAPPTHEMED)(void);
+typedef HTHEME (WINAPI *XPT_OPENTHEMEDATA)(HWND, LPCWSTR);
+typedef HRESULT (WINAPI *XPT_GETTHEMECOLOR)(HTHEME, INT, INT, INT, COLORREF*);
+
+#define GP_BORDER 1
+#define BSS_FLAT 1
+#define TMT_BORDERCOLOR 3801
+
+static XPT_CLOSETHEMEDATA xpCloseThemeData;
+static XPT_ISAPPTHEMED xpIsAppThemed;
+static XPT_OPENTHEMEDATA xpOpenThemeData;
+static XPT_GETTHEMECOLOR xpGetThemeColor;
+
+static HINSTANCE xpThemeDll = NULL;
+
+static BOOL LoadXPTheme(void);
+static void UnloadXPTheme(void);
+//// XP THEME - END
+
+static HBRUSH GetBorderBrush(HWND hwnd);
+
+static void Initialize(ScanFolderBrowser *browser, BOOL showBckScan, BOOL checkBckScan)
+{
+ browser->buttonsCount = 0;
+ browser->buttons = NULL;
+ browser->bkScanChecked = checkBckScan;
+ browser->bkScanShow = showBckScan;
+ browser->pac = NULL;
+ browser->pacl2 = NULL;
+
+ HRESULT result = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete, (LPVOID*)&browser->pac);
+ if (S_OK == result)
+ {
+ IAutoComplete2 *pac2;
+ if (SUCCEEDED(browser->pac->QueryInterface(IID_IAutoComplete2, (LPVOID*)&pac2)))
+ {
+ pac2->SetOptions(ACO_AUTOSUGGEST | ACO_AUTOAPPEND | ACO_USETAB | 0x00000020/*ACF_UPDOWNKEYDROPSLIST*/);
+ }
+ result = CoCreateInstance(CLSID_ACListISF, NULL, CLSCTX_INPROC_SERVER, IID_IACList2, (LPVOID*)&browser->pacl2);
+ if (S_OK == result) browser->pacl2->SetOptions(ACLO_FILESYSDIRS);
+ else { browser->pac->Release(); browser->pac = NULL; }
+ }
+
+ lstrcpynW(browser->selectionPath, WASABI_API_APP->path_getWorkingPath(), MAX_PATH);
+
+ browser->SetCaption(WASABI_API_LNGSTRINGW(IDS_ADD_MEDIA_TO_LIBRARY_));
+ browser->SetTitle(WASABI_API_LNGSTRINGW(IDS_SELECT_FOLDER_TO_ADD_TO_WINAMP_MEDIA_LIBRARY));
+ browser->SetSelection(browser->selectionPath);
+ browser->SetFlags(BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_VALIDATE | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON | BIF_NEWDIALOGSTYLE);
+ browser->LoadBookmarks();
+}
+
+ScanFolderBrowser::ScanFolderBrowser(BOOL showBckScanOption)
+{
+ Initialize(this, showBckScanOption, FALSE);
+}
+
+ScanFolderBrowser::ScanFolderBrowser(void)
+{
+ Initialize(this, TRUE, FALSE);
+}
+
+ScanFolderBrowser::~ScanFolderBrowser(void)
+{
+ if (pacl2) pacl2->Release();
+ if (pac) pac->Release();
+ FreeBookmarks();
+}
+
+void ScanFolderBrowser::LoadBookmarks(void)
+{
+ FreeBookmarks();
+ INT count = sizeof(BOOKMARKS)/sizeof(FOLDER);
+ if (!count) return;
+
+ buttons = (FBUTTON*)calloc(count, sizeof(FBUTTON));
+ if (!buttons) return;
+ buttonsCount = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ const FOLDER *pfolder = &BOOKMARKS[i];
+ FBUTTON *pfb = &buttons[buttonsCount];
+ switch(BOOKMARKS[i].pathtype)
+ {
+ case PATHTYPE_PIDL:
+ pfb->pidl = ILClone((LPITEMIDLIST)pfolder->path); // need to copy it
+ break;
+ case PATHTYPE_CSIDL:
+ if (S_OK != SHGetSpecialFolderLocation(NULL, (INT)pfolder->path, &pfb->pidl)) pfb->pidl = NULL;
+ break;
+ case PATHTYPE_STRING:
+ if (S_OK != ParseDisplayName((LPCWSTR)pfolder->path, NULL, &pfb->pidl, 0, NULL))pfb->pidl = NULL;
+ break;
+ }
+ if (!buttons[buttonsCount].pidl)
+ {
+ continue;
+ }
+
+ SHFILEINFOW shfi = {0};
+ hSysImageList = (HIMAGELIST)SHGetFileInfoW((LPCWSTR)pfb->pidl, 0, &shfi, sizeof(SHFILEINFO), SHGFI_DISPLAYNAME | SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SYSICONINDEX | SHGFI_PIDL);
+ pfb->caption = _wcsdup((pfolder->caption) ? pfolder->caption : shfi.szDisplayName);
+ pfb->tooltip = _wcsdup((pfolder->tooltip) ? pfolder->tooltip : shfi.szDisplayName);
+ pfb->iImage = shfi.iIcon;
+ buttonsCount++;
+ }
+}
+
+void ScanFolderBrowser::FreeBookmarks(void)
+{
+ if (buttons)
+ {
+ FBUTTON *pbtn = buttons;
+ while(buttonsCount--)
+ {
+ if (pbtn->caption) { free(pbtn->caption); pbtn->caption = NULL; }
+ if (pbtn->tooltip) { free(pbtn->tooltip); pbtn->tooltip = NULL; }
+ if (pbtn->pidl) { ILFree(pbtn->pidl); pbtn->pidl = NULL; }
+ pbtn++;
+ }
+ free(buttons);
+ }
+ buttonsCount = 0;
+}
+
+void ScanFolderBrowser::OnInitialized(void)
+{
+ HWND ctrlWnd;
+
+ if (!FindWindowExW(GetHandle(), NULL,L"SHBrowseForFolder ShellNameSpace Control",NULL))
+ {
+ RECT rw;
+ int cx;
+ GetWindowRect(GetHandle(), &rw);
+ cx = rw.right - rw.left;
+ SetWindowPos(GetHandle(), NULL, 0, 0, cx, rw.bottom - rw.top, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
+ GetWindowRect(GetHandle(), &rw);
+ ShrinkWindows((rw.right - rw.left) - cx);
+ }
+ SetOKText(WASABI_API_LNGSTRINGW(IDS_ADD));
+
+ ShiftWindows(88);
+
+ ctrlWnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY , TOOLBARCLASSNAMEW, NULL,
+ WS_CHILD | WS_TABSTOP |
+ CCS_TOP | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE |
+ TBSTYLE_FLAT | TBSTYLE_TRANSPARENT | TBSTYLE_WRAPABLE | TBSTYLE_CUSTOMERASE | TBSTYLE_TOOLTIPS,
+ 12, 10, 84, 272, GetHandle(), (HMENU)IDC_TLB_BOOKMARKS, NULL, NULL);
+ if (ctrlWnd)
+ {
+ ::SendMessage(ctrlWnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0L);
+ ::SendMessage(ctrlWnd, TB_SETBUTTONSIZE, (WPARAM)0, MAKELPARAM(84, 50));
+ ::SendMessage(ctrlWnd, TB_SETBITMAPSIZE, (WPARAM)0, MAKELPARAM(32, 32));
+ ::SendMessage(ctrlWnd, TB_SETPADDING, (WPARAM)0, MAKELPARAM(8, 8));
+ ::SendMessage(ctrlWnd, TB_SETBUTTONWIDTH, (WPARAM)0, MAKELPARAM(84, 84));
+ ::SendMessage(ctrlWnd, TB_SETEXTENDEDSTYLE, (WPARAM)0, 0x0000080 /*TBSTYLE_EX_DOUBLEBUFFER*/);
+ ::SendMessage(ctrlWnd, TB_SETMAXTEXTROWS, (WPARAM)2, 0L);
+ ::SendMessage(ctrlWnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)hSysImageList);
+
+ LPTBBUTTON ptbb = (LPTBBUTTON)calloc(buttonsCount, sizeof(TBBUTTON));
+ for (int i = 0; i < buttonsCount; i++)
+ {
+ ptbb[i].fsState = TBSTATE_ENABLED | TBSTATE_WRAP;
+ ptbb[i].idCommand = TOOLBAR_BTN_STARTID + i;
+ ptbb[i].fsStyle = TBSTYLE_CHECKGROUP;
+ ptbb[i].iString = (INT_PTR)buttons[i].caption;
+ ptbb[i].iBitmap = (INT_PTR) buttons[i].iImage;
+ }
+ ::SendMessage(ctrlWnd, TB_ADDBUTTONSW, (WPARAM)buttonsCount,(LPARAM)ptbb);
+ }
+
+ ctrlWnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY, L"BUTTON",
+ WASABI_API_LNGSTRINGW(IDS_SCAN_FOLDER_IN_BACKGROUND),
+ BS_AUTOCHECKBOX | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_TABSTOP,
+ 0, 0, 100, 16, GetHandle(), (HMENU)IDC_CHK_BGSCAN, NULL, NULL);
+ if (ctrlWnd)
+ {
+ ::SendMessage(ctrlWnd, WM_SETFONT, (WPARAM)SendMessage(WM_GETFONT, 0, 0), FALSE);
+ ::SendMessage(ctrlWnd, BM_SETCHECK, (WPARAM) (bkScanChecked) ? BST_CHECKED : BST_UNCHECKED, 0L);
+ }
+
+ hbBorder = GetBorderBrush(GetHandle());
+ RepositionWindows();
+
+ ShowWindow(GetDlgItem(IDC_TLB_BOOKMARKS), SW_SHOWNORMAL);
+ if (bkScanShow) ShowWindow(GetDlgItem(IDC_CHK_BGSCAN), SW_SHOWNORMAL);
+
+ // edit box autocomplete chnages
+ if(pac) pac->Init(GetDlgItem(IDC_EDT_PATH), pacl2, NULL, NULL);
+
+ FolderBrowseEx::OnInitialized();
+}
+
+void ScanFolderBrowser::OnSelectionChanged(LPCITEMIDLIST pidl)
+{
+ for(int i =0; i < buttonsCount; i++)
+ {
+ ::SendMessage(GetDlgItem(IDC_TLB_BOOKMARKS), TB_CHECKBUTTON, (WPARAM)TOOLBAR_BTN_STARTID + i, ILIsEqual(pidl, buttons[i].pidl));
+ }
+
+ FolderBrowseEx::OnSelectionChanged(pidl);
+}
+
+BOOL ScanFolderBrowser::OnValidateFailed(LPCWSTR lpName)
+{
+ wchar_t buffer[2048] = {0}, titleStr[64] = {0};
+ FolderBrowseEx::OnValidateFailed(lpName);
+ wsprintfW(buffer, WASABI_API_LNGSTRINGW(IDS_FOLDER_NAME_IS_INCORRECT), lpName);
+ MessageBoxW(GetHandle(), buffer, WASABI_API_LNGSTRINGW_BUF(IDS_INCORRECT_FOLDER_NAME,titleStr,64), MB_OK | MB_ICONERROR | MB_SETFOREGROUND);
+ return TRUE;
+}
+
+void ScanFolderBrowser::OnSelectionDone(LPCITEMIDLIST pidl)
+{
+ WCHAR pszPath[MAX_PATH] = {0};
+ SHGetPathFromIDListW(pidl, pszPath);
+ WASABI_API_APP->path_setWorkingPath(pszPath);
+}
+
+void ScanFolderBrowser::ShiftWindows(int cx)
+{
+ HWND hwnd = GetWindow(GetHandle(), GW_CHILD);
+ while(hwnd)
+ {
+ RECT rw;
+ UINT ctrlID = GetDlgCtrlID(hwnd);
+ if (ctrlID != IDOK && ctrlID != IDCANCEL && ctrlID != IDC_SB_GRIPPER)
+ {
+ GetWindowRect(hwnd, &rw);
+ MapWindowPoints(HWND_DESKTOP, GetHandle(), (LPPOINT)&rw, 2);
+ SetWindowPos(hwnd, NULL, rw.left + cx, rw.top, (rw.right - (rw.left + cx)), rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOZORDER | ((ctrlID == IDC_LBL_FOLDER) ? SWP_NOSIZE : 0));
+ }
+ hwnd = GetWindow(hwnd, GW_HWNDNEXT);
+ }
+}
+
+void ScanFolderBrowser::ShrinkWindows(int cx)
+{
+ HWND hwnd = GetWindow(GetHandle(), GW_CHILD);
+ while(hwnd)
+ {
+ UINT ctrlID = GetDlgCtrlID(hwnd);
+ if (ctrlID != IDC_LBL_FOLDER)
+ {
+ RECT rw;
+ GetWindowRect(hwnd, &rw);
+ MapWindowPoints(HWND_DESKTOP, GetHandle(), (LPPOINT)&rw, 2);
+ SetWindowPos(hwnd, NULL,
+ rw.left + cx, rw.top, (rw.right - rw.left) + cx, rw.bottom - rw.top,
+ SWP_NOACTIVATE | SWP_NOZORDER | ((ctrlID == IDOK || ctrlID == IDCANCEL) ? SWP_NOSIZE : SWP_NOMOVE));
+ }
+ hwnd = GetWindow(hwnd, GW_HWNDNEXT);
+ }
+}
+
+void ScanFolderBrowser::RepositionWindows(void)
+{
+ RECT rwBtn, rwLV, rwCtrl;
+ HWND pwnd, ctrlWnd;
+
+ GetWindowRect(GetDlgItem(IDOK), &rwBtn);
+ pwnd = FindWindowExW(GetHandle(), NULL,L"SHBrowseForFolder ShellNameSpace Control",NULL);
+
+ GetWindowRect((pwnd) ? pwnd : GetDlgItem(IDC_TV_FOLDERS), &rwLV);
+ ctrlWnd = GetDlgItem(IDC_CHK_BGSCAN);
+ GetWindowRect(ctrlWnd, &rwCtrl);
+ SetRect(&rwCtrl, rwLV.left, rwBtn.bottom - (rwCtrl.bottom - rwCtrl.top), rwBtn.left - 4 - rwLV.left, rwCtrl.bottom - rwCtrl.top);
+ MapWindowPoints(HWND_DESKTOP, GetHandle(), (LPPOINT)&rwCtrl, 1);
+ SetWindowPos(ctrlWnd, NULL, rwCtrl.left, rwCtrl.top, rwCtrl.right, rwCtrl.bottom, SWP_NOACTIVATE | SWP_NOZORDER);
+
+ ctrlWnd = GetDlgItem(IDC_TLB_BOOKMARKS);
+ GetWindowRect(ctrlWnd, &rwCtrl);
+ GetWindowRect(GetDlgItem(IDC_LBL_CAPTION), &rwLV);
+ SetWindowPos(ctrlWnd, NULL, 0, 0, rwCtrl.right - rwCtrl.left, rwBtn.bottom - rwLV.top, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
+}
+
+void ScanFolderBrowser::OnSize(UINT nType, int cx, int cy)
+{
+ FolderBrowseEx::DialogProc(WM_SIZE, nType, MAKELPARAM(cx, cy));
+
+ RepositionWindows();
+}
+
+void ScanFolderBrowser::OnWindowPosChanging(WINDOWPOS *lpwp)
+{
+ if (lpwp->cx < 346 + 88) lpwp->cx = 346 + 88;
+}
+
+BOOL ScanFolderBrowser::OnNotify(UINT idCtrl, LPNMHDR pnmh, LRESULT *result)
+{
+ switch(pnmh->code)
+ {
+ case NM_CUSTOMDRAW:
+ switch(pnmh->idFrom)
+ {
+ case IDC_TLB_BOOKMARKS:
+ *result = OnToolBarCustomDraw((LPNMTBCUSTOMDRAW)pnmh);
+ return TRUE;
+ }
+ break;
+ case TTN_GETDISPINFOW:
+ OnToolTipGetDispInfo((LPNMTTDISPINFOW)pnmh);
+ break;
+ }
+ return FALSE;
+}
+
+BOOL ScanFolderBrowser::OnCommand(UINT idCtrl, UINT idEvnt, HWND hwndCtrl)
+{
+ int tbid;
+ tbid = idCtrl - TOOLBAR_BTN_STARTID;
+ if (tbid >= 0 && tbid < buttonsCount)
+ {
+ LPITEMIDLIST pidl1 = buttons[tbid].pidl;
+ SetExpanded(pidl1);
+ SetSelection(pidl1);
+
+ wchar_t test_path[MAX_PATH] = {0};
+ EnableOK(SHGetPathFromIDListW(pidl1, test_path));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+LRESULT ScanFolderBrowser::OnToolBarCustomDraw(LPNMTBCUSTOMDRAW pnmcd)
+{
+ switch(pnmcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ FrameRect(pnmcd->nmcd.hdc, &pnmcd->nmcd.rc, hbBorder);
+ InflateRect(&pnmcd->nmcd.rc, -1, -1);
+ IntersectClipRect(pnmcd->nmcd.hdc, pnmcd->nmcd.rc.left, pnmcd->nmcd.rc.top, pnmcd->nmcd.rc.right, pnmcd->nmcd.rc.bottom);
+ return CDRF_NOTIFYITEMDRAW;
+ case CDDS_ITEMPREPAINT:
+ InflateRect(&pnmcd->nmcd.rc, -1, 0);
+ if (0 == pnmcd->nmcd.rc.top) pnmcd->nmcd.rc.top++;
+ return CDRF_DODEFAULT;
+ }
+ return CDRF_DODEFAULT;
+}
+
+void ScanFolderBrowser::OnToolTipGetDispInfo(LPNMTTDISPINFOW lpnmtdi)
+{
+ int tbid;
+ tbid = lpnmtdi->hdr.idFrom - TOOLBAR_BTN_STARTID;
+ lpnmtdi->lpszText = (tbid >= 0 && tbid < buttonsCount) ? buttons[tbid].tooltip : L"";
+}
+
+INT_PTR ScanFolderBrowser::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT result;
+ switch(uMsg)
+ {
+ case WM_WINDOWPOSCHANGING: OnWindowPosChanging((WINDOWPOS*)lParam);
+ case WM_SIZE: OnSize(wParam, LOWORD(lParam), HIWORD(lParam)); return 0;
+ case WM_NOTIFY:
+ if (OnNotify(wParam, (LPNMHDR)lParam, &result))
+ {
+ SetDialogResult(result);
+ return TRUE;
+ }
+ break;
+ case WM_COMMAND:
+ if (OnCommand(LOWORD(wParam), HIWORD(wParam), (HWND)lParam)) return 0;
+ break;
+ case WM_DESTROY:
+ if (hbBorder) DeleteObject(hbBorder);
+ hbBorder = NULL;
+ bkScanChecked = (BST_CHECKED == IsDlgButtonChecked(GetHandle(), IDC_CHK_BGSCAN));
+ break;
+ }
+ return FolderBrowseEx::DialogProc(uMsg, wParam, lParam);
+}
+
+static BOOL LoadXPTheme(void)
+{
+ xpThemeDll = LoadLibraryW(L"UxTheme.dll");
+ if (!xpThemeDll) return FALSE;
+
+ xpCloseThemeData = (XPT_CLOSETHEMEDATA) GetProcAddress(xpThemeDll, "CloseThemeData");
+ xpIsAppThemed = (XPT_ISAPPTHEMED) GetProcAddress(xpThemeDll, "IsAppThemed");
+ xpOpenThemeData = (XPT_OPENTHEMEDATA) GetProcAddress(xpThemeDll, "OpenThemeData");
+ xpGetThemeColor = (XPT_GETTHEMECOLOR) GetProcAddress(xpThemeDll, "GetThemeColor");
+
+ if (!xpCloseThemeData || !xpIsAppThemed || !xpOpenThemeData || !xpGetThemeColor) UnloadXPTheme();
+ return (NULL != xpThemeDll);
+}
+
+static void UnloadXPTheme(void)
+{
+ xpCloseThemeData = NULL;
+ xpIsAppThemed = NULL;
+ xpOpenThemeData = NULL;
+ xpGetThemeColor = NULL;
+ if (xpThemeDll)
+ {
+ FreeLibrary(xpThemeDll);
+ xpThemeDll = NULL;
+ }
+}
+
+static HBRUSH GetBorderBrush(HWND hwnd)
+{
+ HBRUSH hb;
+ COLORREF clr;
+ HRESULT result;
+
+ clr = 0x00000;
+ result = S_FALSE;
+ if(LoadXPTheme())
+ {
+ if(xpIsAppThemed())
+ {
+ HTHEME ht;
+ ht = xpOpenThemeData(GetDlgItem(hwnd, IDC_EDT_PATH), L"Edit");
+ if (ht)
+ {
+ result = xpGetThemeColor(ht, GP_BORDER, BSS_FLAT, TMT_BORDERCOLOR, &clr);
+ xpCloseThemeData(ht);
+ }
+ }
+ UnloadXPTheme();
+ }
+
+ hb = CreateSolidBrush((S_OK == result) ? clr : GetSysColor(COLOR_WINDOWFRAME));
+
+ return hb;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ScanFolderBrowser.h b/Src/Plugins/Library/ml_local/ScanFolderBrowser.h
new file mode 100644
index 00000000..0849779b
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ScanFolderBrowser.h
@@ -0,0 +1,53 @@
+#ifndef NULLSOFT_FOLDERBROWSE_SCANFILES_DIALOG_HEADER
+#define NULLSOFT_FOLDERBROWSE_SCANFILES_DIALOG_HEADER
+
+#include "./folderbrowseex.h"
+
+typedef struct _FBUTTON FBUTTON;
+class ScanFolderBrowser : public FolderBrowseEx
+{
+public:
+ ScanFolderBrowser(void);
+ ScanFolderBrowser(BOOL showBckScanOption);
+ virtual ~ScanFolderBrowser(void);
+
+ void ShowBckScanOption(BOOL show) { bkScanShow = show; }
+ void SetBckScanChecked(BOOL checked) { bkScanChecked = checked; }
+ BOOL GetBckScanChecked(void) { return bkScanChecked; }
+protected:
+ virtual void OnInitialized(void);
+ virtual void OnSelectionChanged(LPCITEMIDLIST pidl);
+ virtual BOOL OnValidateFailed(LPCWSTR lpName);
+ virtual void OnSelectionDone(LPCITEMIDLIST pidl);
+ virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+ void OnWindowPosChanging(WINDOWPOS *lpwp);
+ void OnSize(UINT nType, int cx, int cy);
+ BOOL OnNotify(UINT idCtrl, LPNMHDR pnmh, LRESULT *result);
+ BOOL OnCommand(UINT idCtrl, UINT idEvnt, HWND hwndCtrl);
+ LRESULT OnToolBarCustomDraw(LPNMTBCUSTOMDRAW pnmcd);
+ void OnToolTipGetDispInfo(LPNMTTDISPINFOW lpnmtdi);
+
+private:
+ void LoadBookmarks(void);
+ void FreeBookmarks(void);
+ void ShiftWindows(int cx);
+ void ShrinkWindows(int cx);
+ void RepositionWindows(void);
+
+private:
+ FBUTTON *buttons;
+ int buttonsCount;
+ HBRUSH hbBorder;
+ BOOL bkScanChecked;
+ BOOL bkScanShow;
+
+ IAutoComplete *pac;
+ IACList2 *pacl2;
+
+ wchar_t selectionPath[MAX_PATH]; // this is here only because i'm lazy
+
+ friend static void Initialize(ScanFolderBrowser *browser, BOOL showBckScan, BOOL checkBckScan);
+};
+
+#endif //NULLSOFT_FOLDERBROWSE_SCANFILES_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/SimpleFilter.cpp b/Src/Plugins/Library/ml_local/SimpleFilter.cpp
new file mode 100644
index 00000000..64dd1819
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/SimpleFilter.cpp
@@ -0,0 +1,278 @@
+#include "main.h"
+#include "SimpleFilter.h"
+#include "../nu/sort.h"
+#include "resource.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+static size_t m_sort_by, m_sort_dir, m_sort_which;
+
+void emptyQueryListObject(queryListObject *obj);
+int reallocQueryListObject(queryListObject *obj);
+void freeQueryListObject(queryListObject *obj);
+
+
+int SimpleFilter::SimpleSortFunc(const void *elem1, const void *elem2)
+{
+ queryListItem *a=(queryListItem*)elem1;
+ queryListItem *b=(queryListItem*)elem2;
+ int use_by=m_sort_by;
+ int use_dir=m_sort_dir;
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+ if (use_by == 0)
+ {
+ int v=WCSCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ v=b->ifields[0]-a->ifields[0];
+ RETIFNZ(v)
+ v=b->ifields[1]-a->ifields[1];
+ return v;
+ }
+ if (use_by == 1)
+ {
+ int v=a->ifields[0]-b->ifields[0];
+ RETIFNZ(v)
+
+ if (m_sort_which == 0)
+ {
+ v=b->ifields[1]-a->ifields[1];
+ RETIFNZ(v)
+ }
+
+ return WCSCMP_NULLOK(a->name,b->name);
+ }
+ if (use_by == 2)
+ {
+ int v=a->ifields[1]-b->ifields[1];
+ RETIFNZ(v)
+ if (m_sort_which == 0)
+ {
+ v=b->ifields[0]-a->ifields[0];
+ RETIFNZ(v)
+ }
+ return WCSCMP_NULLOK(a->name,b->name);
+ }
+
+ if (use_by == 5)
+ {
+ __int64 v = a->size - b->size;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if (use_by == 6)
+ {
+ int v = a->length - b->length;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+#undef RETIFNZ
+ return 0;
+}
+void SimpleFilter::SortResults(C_Config *viewconf, int which, int isfirst) // sorts the results based on the current sort mode
+{
+ if (viewconf)
+ {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d", GetConfigId(), which);
+ m_sort_by = viewconf->ReadInt(buf, 0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_dir_%d", GetConfigId(), which);
+ m_sort_dir = viewconf->ReadInt(buf, 0);
+ m_sort_which = which;
+
+ if (showncolumns.size()>m_sort_by && m_sort_by>=0) m_sort_by = showncolumns[m_sort_by]->field;
+
+ if (m_sort_dir == 0 && m_sort_by == 0 && isfirst) return;
+
+ if (artistList.Size > 2) qsort(artistList.Items+1,artistList.Size-1,sizeof(queryListItem),SimpleSortFunc);
+ }
+}
+
+void SimpleFilter::Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)
+{
+ if (numitems > 1)
+ qsort_itemRecord(items,numitems, this, BuildSortFunc);
+
+ if (killswitch && *killswitch) return;
+
+ emptyQueryListObject(&artistList);
+ reallocQueryListObject(&artistList);
+ ZeroMemory(&artistList.Items[0],sizeof(queryListItem));
+
+ wchar_t *lastname=0;
+ const wchar_t *lastalb=0;
+ wchar_t albbuf[100] = {0}, albbuf2[100] = {0}, albbuf3[100] = {0};
+
+ itemRecordW *p=items;
+ int n=numitems;
+
+ int isbl=0;
+ numGroups = 0;
+ bool do_wcsdup = !this->uses_nde_strings();
+ while (n--)
+ {
+ if (killswitch && *killswitch) return;
+ const wchar_t *name = this->GroupText(p,albbuf3,100);
+ if (!lastname || WCSCMP_NULLOK(lastname, name))
+ {
+ artistList.Size++;
+ if (reallocQueryListObject(&artistList)) break;
+ ZeroMemory(&artistList.Items[artistList.Size],sizeof(queryListItem));
+ lastalb=0;
+ if (name == albbuf3)
+ lastname = artistList.Items[artistList.Size].name = ndestring_wcsdup(name);
+ else if (name)
+ {
+ if (do_wcsdup)
+ {
+ lastname = artistList.Items[artistList.Size].name = ndestring_wcsdup(name);
+ }
+ else
+ {
+ wchar_t *ndename = (wchar_t *)name;
+ ndestring_retain(ndename);
+ lastname = artistList.Items[artistList.Size].name = ndename;
+ }
+ }
+ else
+ {
+ lastname = artistList.Items[artistList.Size].name = emptyQueryListString;
+ }
+
+ SKIP_THE_AND_WHITESPACEW(lastname) // optimization technique
+ if (*lastname) numGroups++;
+ }
+ if (!name || !*name) isbl++;
+ if (artistList.Size)
+ {
+ artistList.Items[artistList.Size].ifields[1]++;
+ if (p->length>0) artistList.Items[artistList.Size].length += p->length;
+ if (p->filesize>0) artistList.Items[artistList.Size].size += p->filesize;
+ }
+ if (nextFilter && (!lastalb || WCSCMP_NULLOK(lastalb,nextFilter->GroupText(p,albbuf2,100))))
+ {
+ lastalb = nextFilter->GroupText(p,albbuf,100);
+ if (lastalb && *lastalb) artistList.Items[artistList.Size].ifields[0]++;
+ if (lastalb) SKIP_THE_AND_WHITESPACEW(lastalb) // optimization technique
+ }
+ p++;
+ }
+
+ if (killswitch && *killswitch) return;
+
+ wchar_t langbuf[2048] = {0}, buf[64] = {0};
+ if (isbl)
+ {
+ StringCchPrintfW(buf,64,WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_S, langbuf, 2048),artistList.Size-1,artistList.Size==2?GetNameSingular():GetName());
+ }
+ else
+ {
+ StringCchPrintfW(buf,64,WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_S, langbuf, 2048),artistList.Size,artistList.Size==1?GetNameSingular():GetName());
+ }
+
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(buf);
+
+ artistList.Items[0].name=ndestring_wcsdup(buf);
+ artistList.Items[0].ifields[0]=numitems2;
+ artistList.Items[0].ifields[1]=numitems;
+ artistList.Size++;
+}
+
+int SimpleFilter::Size()
+{
+ return artistList.Size;
+}
+
+const wchar_t *SimpleFilter::GetText(int row) // gets main text (first column)
+{
+ return artistList.Items[row].name;
+}
+
+void SimpleFilter::Empty()
+{
+ freeQueryListObject(&artistList);
+}
+
+void SimpleFilter::CopyText(int row, size_t column, wchar_t *dest, int destCch)
+{
+ if (column >= showncolumns.size()) return;
+ column = showncolumns[column]->field;
+ if (row>=artistList.Size)
+ return ;
+ switch (column)
+ {
+ case 0: // artist name
+ if (artistList.Items[row].name && *artistList.Items[row].name)
+ lstrcpynW(dest,artistList.Items[row].name,destCch);
+ else
+ {
+ StringCchPrintfW(dest, destCch, WASABI_API_LNGSTRINGW(IDS_NO_S), GetNameSingular());
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(dest);
+ }
+ break;
+ case 1: // albums
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[0]);
+ break;
+ case 2: // tracks
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[1]);
+ break;
+ case 5:
+ if (row && artistList.Items[row].size)
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, artistList.Items[row].size);
+ else dest[0]=0;
+ break;
+ case 6:
+ if (row && artistList.Items[row].length)
+ StringCchPrintfW(dest,destCch,L"%d:%02d", artistList.Items[row].length / 60, artistList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ }
+}
+
+const wchar_t * SimpleFilter::CopyText2(int row, size_t column, wchar_t *dest, int destCch)
+{
+ if (column >= showncolumns.size()) return NULL;
+ column = showncolumns[column]->field;
+ if (row>=artistList.Size)
+ return NULL;
+ switch (column)
+ {
+ case 0: // artist name
+ if (artistList.Items[row].name && *artistList.Items[row].name)
+ dest = artistList.Items[row].name;
+ else
+ {
+ StringCchPrintfW(dest, destCch, WASABI_API_LNGSTRINGW(IDS_NO_S), GetNameSingular());
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(dest);
+ }
+ break;
+ case 1: // albums
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[0]);
+ break;
+ case 2: // tracks
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[1]);
+ break;
+ case 5:
+ if (row && artistList.Items[row].size)
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, artistList.Items[row].size);
+ else dest[0]=0;
+ break;
+ case 6:
+ if (row && artistList.Items[row].length)
+ StringCchPrintfW(dest,destCch,L"%d:%02d", artistList.Items[row].length / 60, artistList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ }
+ return dest;
+}
+void SimpleFilter::AddColumns2()
+{
+ showncolumns.push_back(new ListField(0,155,GetNameSingular(),g_view_metaconf,GetConfigId()));
+ if (nextFilter) showncolumns.push_back(new ListField(1,48,nextFilter->GetName(),g_view_metaconf,GetConfigId()));
+ showncolumns.push_back(new ListField(2,45,IDS_TRACKS,g_view_metaconf,GetConfigId()));
+ showncolumns.push_back(new ListField(5,45,IDS_SIZE,g_view_metaconf,GetConfigId(),true,true));
+ showncolumns.push_back(new ListField(6,45,IDS_LENGTH,g_view_metaconf,GetConfigId(),true,true));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/SimpleFilter.h b/Src/Plugins/Library/ml_local/SimpleFilter.h
new file mode 100644
index 00000000..aaad01a9
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/SimpleFilter.h
@@ -0,0 +1,169 @@
+#ifndef NULLSOFT_ML_LOCAL_SIMPLEFILTER_H
+#define NULLSOFT_ML_LOCAL_SIMPLEFILTER_H
+
+#include "ViewFilter.h"
+#include "resource.h"
+
+class SimpleFilter : public ViewFilter
+{
+public:
+ static int SimpleSortFunc(const void *elem1, const void *elem2);
+ void SortResults(C_Config *viewconf, int which=0, int isfirst=0);
+ void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2);
+ int Size();
+ const wchar_t *GetText(int row);
+ void CopyText(int row, size_t column, wchar_t *dest, int destCch);
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch);
+ void Empty();
+ void AddColumns2();
+ // override these 4 to make a simple filter
+ virtual const wchar_t *GetField()=0;
+ virtual const wchar_t *GetName()=0;
+ virtual const wchar_t *GetNameSingular()=0;
+ virtual const wchar_t *GetNameSingularAlt(int mode)=0;
+ virtual const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len)=0;
+ wchar_t name[64];
+ wchar_t sname[64];
+ wchar_t saname[64];
+ virtual bool uses_nde_strings() { return true; }
+private:
+ queryListObject artistList;
+};
+
+class AlbumArtistFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_albumartist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTISTS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTIST,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ALBUM_ARTIST_ALT:(mode==1?2065:2081)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->albumartist;}
+ char * GetConfigId(){return "av_aa";}
+};
+
+class ArtistFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_artist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST_S,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ARTIST_ALT:(mode==1?2066:2082)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->artist;}
+ char * GetConfigId(){return "av_ar";}
+};
+
+class ComposerFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_composer;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_COMPOSERS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_COMPOSER,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_COMPOSER_ALT:(mode==1?2067:2083)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->composer;}
+ char * GetConfigId(){return "av_c";}
+};
+
+class GenreFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_genre;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_GENRES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_GENRE,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_GENRE_ALT:(mode==1?2068:2084)),saname,64);}
+ const wchar_t * GroupText(itemRecordW * item, wchar_t * buf, int len){return item->genre;}
+ char * GetConfigId(){return "av_g";}
+};
+
+class PublisherFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_publisher;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_PUBLISHERS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_PUBLISHER,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_PUBLISHER_ALT:(mode==1?2069:2085)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->publisher;}
+ char * GetConfigId(){return "av_p";}
+};
+
+class YearFilter : public SimpleFilter
+{
+ const wchar_t *GetComparisonOperator() {return L"==";}
+ const wchar_t *GetField(){return DB_FIELDNAME_year;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_YEARS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_YEAR,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_YEAR_ALT:(mode==1?2070:2086)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){if(item->year>0) wsprintfW(buf,L"%d",item->year); else buf[0]=0; return buf;}
+ char * GetConfigId(){return "av_y";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+static wchar_t getIndex(const wchar_t* str) {
+ if(!str) return 0;
+ SKIP_THE_AND_WHITESPACEW(str);
+ return towupper(*str);
+}
+
+class AlbumArtistIndexFilter : public SimpleFilter
+{
+ const wchar_t *GetComparisonOperator() {return L"BEGINSLIKE";}
+ const wchar_t *GetField(){return DB_FIELDNAME_albumartist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTIST_INDEXES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTIST_INDEX,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ALBUM_ARTIST_INDEX_ALT:(mode==1?2071:2087)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len) {buf[0]=getIndex(item->albumartist); buf[1]=0; return buf;}
+ char * GetConfigId(){return "av_aai";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class ArtistIndexFilter : public SimpleFilter
+{
+ const wchar_t *GetComparisonOperator() {return L"BEGINSLIKE";}
+ const wchar_t *GetField(){return DB_FIELDNAME_artist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST_INDEXES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST_INDEX,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ARTIST_INDEX_ALT:(mode==1?2072:2088)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len) {buf[0]=getIndex(item->artist); buf[1]=0; return buf;}
+ char * GetConfigId(){return "av_ai";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class PodcastChannelFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_podcastchannel;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_CHANNELS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_CHANNEL,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_PODCAST_CHANNEL_ALT:(mode==1?2073:2089)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return getRecordExtendedItem_fast(item,extended_fields.podcastchannel);}
+ char * GetConfigId(){return "av_pc";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class CategoryFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return L"category";}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_CATEGORIES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_CATEGORY,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_CATEGORY_ALT:(mode==1?2074:2090)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->category;}
+ char * GetConfigId(){return "av_cat";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class DirectorFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_director;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_DIRECTOR,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_DIRECTOR,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_DIRECTOR_ALT:(mode==1?2075:2091)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return getRecordExtendedItem_fast(item,extended_fields.director);}
+ char * GetConfigId(){return "av_drt";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class ProducerFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return L"producer";}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_PRODUCER,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_PRODUCER,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_PRODUCER_ALT:(mode==1?2076:2092)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return getRecordExtendedItem_fast(item,extended_fields.producer);}
+ char * GetConfigId(){return "av_pdc";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/TitleInfo.cpp b/Src/Plugins/Library/ml_local/TitleInfo.cpp
new file mode 100644
index 00000000..3d52a4c1
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/TitleInfo.cpp
@@ -0,0 +1,321 @@
+#include "main.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/ns_wc.h"
+#include <shlwapi.h>
+#include <malloc.h> // for alloca
+
+static wchar_t m_lastfn[2048];
+static wchar_t m_lastartist[256], m_lasttitle[256], m_lastalbum[256], m_gracenotefileid[128];
+static int m_lasttrack;
+static int m_playcount;
+static int m_ispodcast;
+static int m_rating;
+static int m_db_found;
+
+void ClearTitleHookCache()
+{
+ memset(m_lastfn, 0, sizeof(m_lastfn));
+}
+
+BOOL IPC_HookExtInfoW(INT_PTR param)
+{
+ if (skipTitleInfo) // we're reading metadata and being asked to skip title hooking (so we can get a valid title for guessing, if need be)
+ return false;
+
+ extendedFileInfoStructW *p = (extendedFileInfoStructW *)param;
+ // fill in our own titles from db? not for now, just let it default to hitting the tags?
+
+ if (!g_config->ReadInt(L"newtitle", 1))
+ return FALSE;
+
+ if (NULL == p->filename || L'\0' == *p->filename ||
+ NULL == p->metadata || L'\0' == *p->metadata)
+ {
+ return FALSE;
+ }
+
+ int which = 0;
+ if (!_wcsicmp(p->metadata, DB_FIELDNAME_artist )) which = 1;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_title )) which = 2;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_album )) which = 3;
+ //else if (!_wcsicmp(p->metadata, L"tuid")) which = 4;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_track )) which = 5;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_rating )) which = 6;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_playcount )) which = 7;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_GracenoteFileID )) which=8;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_ispodcast )) which=9;
+
+ if (which)
+ {
+ if (_wcsicmp(m_lastfn, p->filename))
+ {
+ if (!g_table) openDb();
+ if (!g_table) return 0;
+
+ m_lastartist[0] = 0;
+ m_lasttitle[0] = 0;
+ m_lastalbum[0] = 0;
+ m_gracenotefileid[0]=0;
+ m_lasttrack = 0;
+ m_rating=-1;
+ m_playcount=-1;
+ m_ispodcast=-1;
+ lstrcpynW(m_lastfn, p->filename, sizeof(m_lastfn)/sizeof(wchar_t));
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found = FindFileInDatabase(s, MAINTABLE_ID_FILENAME, m_lastfn, filename2);
+
+ if (found == 2)
+ lstrcpynW(m_lastfn, filename2, sizeof(m_lastfn)/sizeof(m_lastfn));
+
+ if (found)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ARTIST);
+ if (f) lstrcpynW(m_lastartist, NDE_StringField_GetString(f), sizeof(m_lastartist)/sizeof(wchar_t));
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ALBUM);
+ if (f) lstrcpynW(m_lastalbum, NDE_StringField_GetString(f), sizeof(m_lastalbum)/sizeof(wchar_t));
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TITLE);
+ if (f) lstrcpynW(m_lasttitle, NDE_StringField_GetString(f), sizeof(m_lasttitle)/sizeof(wchar_t));
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TRACKNB);
+ if (f) m_lasttrack = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
+ if (f) m_playcount = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_RATING);
+ if (f) m_rating = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ISPODCAST);
+ if (f) m_ispodcast = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_GRACENOTEFILEID);
+ if (f) lstrcpynW(m_gracenotefileid, NDE_StringField_GetString(f), sizeof(m_gracenotefileid)/sizeof(wchar_t));
+ m_db_found = 1;
+ }
+ else m_db_found = 0;
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ }
+ if (m_db_found == 1)
+ {
+ switch(which)
+ {
+ case 1:
+ if (m_lastartist[0])
+ {
+ lstrcpynW(p->ret, m_lastartist, p->retlen);
+ return 1;
+ }
+ break;
+ case 2:
+ if (m_lasttitle[0])
+ {
+ lstrcpynW(p->ret, m_lasttitle, p->retlen);
+ return 1;
+ }
+ break;
+ case 3:
+ if (m_lastalbum[0])
+ {
+ lstrcpynW(p->ret, m_lastalbum, p->retlen);
+ return 1;
+ }
+ break;
+ case 5:
+ if (m_lasttrack && m_lasttrack != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_lasttrack);
+ return 1;
+ }
+ break;
+ case 6:
+ if (m_rating != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_rating);
+ return 1;
+ }
+ break;
+ case 7:
+ if (m_playcount != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_playcount);
+ return 1;
+ }
+ break;
+ case 8:
+ if (m_gracenotefileid[0])
+ {
+ lstrcpynW(p->ret, m_gracenotefileid, p->retlen);
+ return 1;
+ }
+ break;
+ case 9:
+ if (m_ispodcast != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_ispodcast);
+ return 1;
+ }
+ break;
+ }
+ }
+ }
+ return FALSE;
+}
+
+BOOL IPC_HookExtInfo(INT_PTR param)
+{
+ extendedFileInfoStruct *p = (extendedFileInfoStruct*)param;
+ // fill in our own titles from db? not for now, just let it default to hitting the tags?
+ if (!g_config->ReadInt(L"newtitle", 1))
+ return FALSE;
+
+ if (NULL == p->filename || '\0' == *p->filename ||
+ NULL == p->metadata || '\0' == *p->metadata)
+ {
+ return FALSE;
+ }
+
+ extendedFileInfoStructW pW = {0};
+ AutoWide wideFn(p->filename), wideMetadata(p->metadata);
+ pW.filename = wideFn;
+ pW.metadata = wideMetadata;
+ pW.retlen = p->retlen;
+ pW.ret = (wchar_t *)alloca(pW.retlen * sizeof(wchar_t));
+
+ if (IPC_HookExtInfoW((INT_PTR)&pW))
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, pW.ret, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ return 0;
+
+}
+
+BOOL IPC_HookTitleInfo(INT_PTR param)
+{
+ if (skipTitleInfo) // we're reading metadata and being asked to skip title hooking (so we can get a valid title for guessing, if need be)
+ return false;
+
+ waHookTitleStructW *hts = (waHookTitleStructW*)param;
+
+ if (NULL != hts->filename &&
+ !StrStrW(hts->filename, L"://") &&
+ g_config->ReadInt(L"newtitle", 1))
+ {
+ if (!g_table) openDb();
+ if (!g_table) return 0;
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, hts->filename, filename2);
+
+ if (found)
+ {
+ nde_field_t length = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LENGTH);
+ int l = -1;
+ if (length)
+ l = NDE_IntegerField_GetValue(length);
+ if (l > 0)
+ hts->length = l;
+ else hts->length = -1;
+
+ if (hts->title)
+ {
+ TAG_FMT_EXT(hts->filename, fieldTagFunc, ndeTagFuncFree, (void*)s, hts->title, 2048, 1);
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (found) return 1;
+ } // not http://
+ return FALSE;
+}
+
+DWORD doGuessProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ // call the old wndproc, and if it produces empty results or no results, try to guess
+ extendedFileInfoStruct *p = (extendedFileInfoStruct*)wParam;
+
+ LRESULT ret = CallWindowProc(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+ if (NULL != p &&
+ (!ret || !p->ret[0]) &&
+ g_config->ReadInt(L"guessmode", 0) != 2)
+ {
+ int which = 0;
+
+ if (NULL != p->metadata)
+ {
+ if (!_stricmp(p->metadata, "artist")) which = 1;
+ else if (!_stricmp(p->metadata, "title")) which = 2;
+ else if (!_stricmp(p->metadata, "album")) which = 3;
+ else if (!_stricmp(p->metadata, "track")) which = 5;
+ else which = 0;
+ }
+ else
+ which = 0;
+
+ if (which)
+ {
+ static char m_lastfn[2048];
+ if (NULL != p->filename && _strnicmp(m_lastfn, p->filename, sizeof(m_lastfn)))
+ {
+ m_db_found = 2;
+ m_lastartist[0] = 0;
+ m_lasttitle[0] = 0;
+ m_lastalbum[0] = 0;
+ m_lasttrack = 0;
+ StringCbCopyA(m_lastfn, sizeof(m_lastfn), p->filename);
+
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0;
+ wchar_t *guessbuf = guessTitles(AutoWide(m_lastfn), &tn, &artist, &album, &title);
+
+ if (guessbuf)
+ {
+ if (artist) StringCbCopyW(m_lastartist, sizeof(m_lastartist), artist);
+ if (album) StringCbCopyW(m_lastalbum, sizeof(m_lastalbum), album);
+ if (title) StringCbCopyW(m_lasttitle, sizeof(m_lasttitle), title);
+ m_lasttrack = tn;
+ free(guessbuf);
+ }
+ }
+ if (m_db_found == 2)
+ {
+ if (which == 1 && m_lastartist[0])
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, m_lastartist, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ if (which == 2 && m_lasttitle[0])
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, m_lasttitle, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ if (which == 3 && m_lastalbum[0])
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, m_lastalbum, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ if (which == 5 && m_lasttrack)
+ {
+ _snprintf(p->ret, p->retlen, "%d", m_lasttrack);
+ return 1;
+ }
+ if (which == 6 && m_rating != -1)
+ {
+ _snprintf(p->ret, p->retlen, "%d", m_rating);
+ return 1;
+ }
+ if (which == 7 && m_playcount != -1)
+ {
+ _snprintf(p->ret, p->retlen, "%d", m_playcount);
+ return 1;
+ }
+ }
+ }
+ }
+ return (int)ret;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ViewFilter.cpp b/Src/Plugins/Library/ml_local/ViewFilter.cpp
new file mode 100644
index 00000000..4a0def1d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ViewFilter.cpp
@@ -0,0 +1,431 @@
+#include "main.h"
+#include "ViewFilter.h"
+#include "resource.h"
+#include "AlbumArtContainer.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <algorithm>
+
+wchar_t *emptyQueryListString = L"";
+void emptyQueryListObject(queryListObject *obj)
+{
+ queryListItem *p = obj->Items;
+ while (obj->Size-- > 0)
+ {
+ if (p->name && p->name != emptyQueryListString)
+ ndestring_release(p->name);
+ ndestring_release(p->albumGain);
+ ndestring_release(p->gracenoteFileId);
+ ndestring_release(p->genre);
+ if (p->artist && p->artist != emptyQueryListString)
+ ndestring_release(p->artist);
+
+ if(p->art) { p->art->updateMsg.hwnd = 0; p->art->Release(); }
+ p++;
+ }
+ obj->Size = 0;
+}
+
+void freeQueryListObject(queryListObject *obj)
+{
+ emptyQueryListObject(obj);
+ free(obj->Items);
+ obj->Items = 0;
+ obj->Alloc = obj->Size = 0;
+}
+
+int reallocQueryListObject(queryListObject *obj) // 0 on success
+{
+ if (obj->Size >= obj->Alloc)
+ {
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc = obj->Size + 32;
+ queryListItem *data = (queryListItem*)realloc(obj->Items, sizeof(queryListItem) * obj->Alloc);
+ if (data)
+ {
+ obj->Items = data;
+ }
+ else
+ {
+ data = (queryListItem*)malloc(sizeof(queryListItem) * obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(queryListItem) * old_Alloc);
+ free(obj->Items);
+ obj->Items = data;
+ }
+ else obj->Alloc = old_Alloc;
+ }
+
+ if (!obj->Items)
+ {
+ obj->Alloc = 0;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int ViewFilter::BuildSortFunc(const void *elem1, const void *elem2, const void *context)
+{
+ ViewFilter *sortFilter = (ViewFilter *)context;
+ itemRecordW *a = (itemRecordW *)elem1;
+ itemRecordW *b = (itemRecordW *)elem2;
+ wchar_t ab[100] = {0}, bb[100] = {0};
+ int v=WCSCMP_NULLOK(sortFilter->GroupText(a,ab,100),sortFilter->GroupText(b,bb,100));
+ if (v)
+ return v;
+ if(sortFilter->nextFilter)
+ return WCSCMP_NULLOK(sortFilter->nextFilter->GroupText(a,ab,100),sortFilter->nextFilter->GroupText(b,bb,100));
+ return 0;
+}
+
+static int sortFunc_cols(const void *elem1, const void *elem2)
+{
+ ListField * a = (ListField *)elem1;
+ ListField * b = (ListField *)elem2;
+ return a->pos - b->pos;
+}
+static bool sortFunc_cols_V2(const void* elem1, const void* elem2)
+{
+ return sortFunc_cols(elem1, elem2) < 0;
+}
+void ViewFilter::AddColumns() {
+ ClearColumns();
+
+ AddColumns2();
+
+ for(unsigned int i=0; i<showncolumns.size(); i++){
+ ListField *l = showncolumns[i];
+ if(l->hidden) {
+ hiddencolumns.push_back(l);
+ showncolumns.erase(showncolumns.begin() + i);
+ i--;
+ }
+ }
+ //qsort(showncolumns.first(),showncolumns.size(),sizeof(void*),sortFunc_cols);
+ std::sort(showncolumns.begin(), showncolumns.end(), sortFunc_cols_V2);
+
+ for ( ListField *l_column : showncolumns )
+ list->AddCol( l_column->name, l_column->width );
+}
+
+void ViewFilter::ClearColumns()
+{
+ for ( ListField *l_shown_column : showncolumns )
+ delete l_shown_column;
+
+ showncolumns.clear();
+
+
+ for ( ListField *l_hidden_column : hiddencolumns )
+ delete l_hidden_column;
+
+ hiddencolumns.clear();
+}
+
+void ViewFilter::SaveColumnWidths()
+{
+ char *av = GetConfigId();
+ for ( size_t i = 0; i < showncolumns.size(); i++ )
+ {
+ wchar_t configname[ 256 ] = { 0 };
+ int field = showncolumns[ i ]->field;
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d", av, field );
+ g_view_metaconf->WriteInt( configname, list->GetColumnWidth( i ) );
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d_pos", av, field );
+ g_view_metaconf->WriteInt( configname, i );
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d_hidden", av, field );
+ g_view_metaconf->WriteInt( configname, 0 );
+ }
+
+ for ( ListField *l_hidden_column : hiddencolumns )
+ {
+ wchar_t configname[ 256 ] = { 0 };
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d_hidden", av, l_hidden_column->field );
+ g_view_metaconf->WriteInt( configname, 1 );
+ }
+}
+
+void ViewFilter::ResetColumns()
+{
+ for ( unsigned int i = 0; i < hiddencolumns.size(); i++ )
+ {
+ showncolumns.push_back( hiddencolumns[ i ] );
+ hiddencolumns.erase( hiddencolumns.begin() + i );
+ i--;
+ }
+
+ for ( unsigned int i = 0; i < showncolumns.size(); i++ )
+ {
+ ListField *l = showncolumns[ i ];
+ l->ResetPos();
+ if ( l->hidden )
+ {
+ hiddencolumns.push_back( l );
+ showncolumns.erase( showncolumns.begin() + i );
+ i--;
+ }
+ }
+ //qsort(showncolumns.first(),showncolumns.size(),sizeof(void*),sortFunc_cols);
+ std::sort( showncolumns.begin(), showncolumns.end(), sortFunc_cols_V2 );
+
+ for ( ListField *l_show_column : showncolumns )
+ list->AddCol( l_show_column->name, l_show_column->width );
+}
+
+ListField::ListField(int field, int width0, const wchar_t * name, C_Config * config,char * av, bool hidden0, bool hiddenDefault, bool readini,int pos):field(field),name(name),hiddenDefault(hiddenDefault),hidden(hidden0),pos(pos),width(width0) {
+ this->name = _wcsdup(name);
+ if(readini) {
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d", av, field);
+ this->width = config->ReadInt(buf, width0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_pos", av, field);
+ this->pos = config->ReadInt(buf, field);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_hidden", av, field);
+ this->hidden = config->ReadInt(buf, hidden0 ? 1: 0 ) != 0;
+ }
+}
+
+ListField::ListField(int field, int width0, int name0, C_Config * config,char * av, bool hidden0, bool hiddenDefault, bool readini,int pos):field(field),name(name),hiddenDefault(hiddenDefault),hidden(hidden0),pos(pos),width(width0) {
+ this->name = _wcsdup(WASABI_API_LNGSTRINGW(name0));
+ if(readini) {
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d", av, field);
+ this->width = config->ReadInt(buf,width0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_pos", av, field);
+ this->pos = config->ReadInt(buf,field);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_hidden", av, field);
+ this->hidden = config->ReadInt(buf,hidden0?1:0)!=0;
+ }
+}
+
+void ListField::ResetPos() { pos = field; hidden = hiddenDefault;}
+
+static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND m_curlistbox_hwnd, m_availlistbox_hwnd;
+ static ViewFilter *filter;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1);
+ m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2);
+ filter = (ViewFilter*)lParam;
+
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, TRUE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, TRUE);
+ }
+ case WM_USER + 32:
+ {
+ for ( ListField *l : filter->showncolumns )
+ {
+ int r = SendMessageW( m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name );
+ SendMessageW( m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l );
+ }
+
+ for ( ListField *l : filter->hiddencolumns )
+ {
+ int r = SendMessageW( m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name );
+ SendMessageW( m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l );
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_DEFS:
+ SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ filter->ResetColumns();
+ SendMessage(hwndDlg, WM_USER + 32, 0, 0);
+ break;
+ case IDC_LIST2:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON2:
+ //add column
+ {
+ for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i--, 0);
+ int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0);
+ }
+ break;
+ case IDC_LIST1:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON3:
+ //remove column
+ {
+ for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+ int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0);
+ }
+ break;
+ case IDC_BUTTON4:
+ //move column up
+ {
+ for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i - 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON5:
+ //move column down
+ {
+ int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (int i = l - 2;i >= 0;i--)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i + 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDOK:
+ // read and apply changes...
+ {
+ filter->showncolumns.clear();
+ filter->hiddencolumns.clear();
+ int i;
+ int l = (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ filter->showncolumns.push_back(c);
+ c->pos=i;
+ c->hidden=false;
+ }
+ l = (INT)SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ filter->hiddencolumns.push_back(c);
+ c->hidden=true;
+ }
+ filter->SaveColumnWidths();
+ }
+ EndDialog(hwndDlg, 1);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ }
+ break;
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, FALSE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, FALSE);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+
+void ViewFilter::CustomizeColumns( HWND parent, BOOL showmenu )
+{
+ if ( showmenu )
+ {
+ HMENU menu = GetSubMenu( g_context_menus, 4 );
+ POINT p;
+ GetCursorPos( &p );
+ int r = TrackPopupMenu( menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, 0, parent, NULL );
+ if ( r != ID_HEADERWND_CUSTOMIZECOLUMNS ) return;
+ }
+
+ bool r = !!WASABI_API_DIALOGBOXPARAMW( IDD_CUSTCOLUMNS, parent, custColumns_dialogProc, (LPARAM)this );
+ MSG msg;
+ while ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ); //eat return
+ if ( r )
+ {
+ while ( ListView_DeleteColumn( list->getwnd(), 0 ) );
+ for ( ListField *l_show_column : showncolumns )
+ list->AddCol( l_show_column->name, l_show_column->width );
+ }
+}
+
+HMENU ViewFilter::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu) {
+ HMENU menu = GetSubMenu(themenu, 5);
+ wchar_t scrollConf[] = L"av0_hscroll";
+ scrollConf[2] = (wchar_t)('0'+filterNum);
+ BOOL enablescroll = c->ReadInt(scrollConf, 0);
+ CheckMenuItem(menu,ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR,enablescroll?MF_CHECKED:MF_UNCHECKED);
+
+ if(isFilter) {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i=0;
+ while(GetMenuItemInfo(menu,i,TRUE,&m)) {
+ m.wID |= (1+filterNum) << 16;
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ i++;
+ }
+ }
+ return menu;
+}
+
+void ViewFilter::ProcessMenuResult(int r, bool isFilter, int editFilter, C_Config *c, HWND hwndDlg) {
+ int mid = (r >> 16);
+ if(!isFilter && mid) return;
+ if(isFilter && mid-1 != editFilter) return;
+ r &= 0xFFFF;
+
+ switch(r) {
+ case ID_HEADERWND_CUSTOMIZECOLUMNS:
+ CustomizeColumns(hwndDlg, FALSE);
+ break;
+ case ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR:
+ {
+ wchar_t scrollConf[] = L"av0_hscroll";
+ scrollConf[2]=(wchar_t)('0'+editFilter);
+ BOOL enablescroll = !c->ReadInt(scrollConf,0);
+ g_view_metaconf->WriteInt(scrollConf,enablescroll);
+ MLSkinnedScrollWnd_ShowHorzBar(list->getwnd(),enablescroll);
+ }
+ break;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ViewFilter.h b/Src/Plugins/Library/ml_local/ViewFilter.h
new file mode 100644
index 00000000..b3c6c610
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ViewFilter.h
@@ -0,0 +1,89 @@
+#ifndef NULLSOFT_ML_LOCAL_VIEWFILTER_H
+#define NULLSOFT_ML_LOCAL_VIEWFILTER_H
+
+#include "main.h"
+#include "../nu/listview.h"
+#include <vector>
+class AlbumArtContainer;
+
+extern wchar_t *emptyQueryListString;
+struct queryListItem
+{
+ queryListItem() : name(0), albumGain(0), length(0), art(0), artist(0), genre(0),
+ gracenoteFileId(0), rating(0), lastupd(0), size(0) {
+ ZeroMemory(&ifields,sizeof(ifields));
+ }
+ wchar_t *name;
+ wchar_t *albumGain;
+ int length, ifields[3];
+ AlbumArtContainer *art;
+ wchar_t *artist;
+ wchar_t *genre;
+ wchar_t *gracenoteFileId;
+ int rating;
+ __time64_t lastupd;
+ __int64 size;
+} ;
+
+struct queryListObject
+{
+ queryListObject() : Items(0), Size(0), Alloc(0){}
+ queryListItem *Items;
+ int Size, Alloc;
+} ;
+
+class ListField {
+public:
+ int field;
+ int width;
+ int pos;
+ const wchar_t * name;
+ bool hidden, hiddenDefault;
+ ListField(int field, int width, const wchar_t * name, C_Config * config, char * av, bool hidden=false, bool hiddenDefault=false, bool readini=true, int pos=0);
+ ListField(int field, int width, int name, C_Config * config, char * av, bool hidden=false, bool hiddenDefault=false, bool readini=true, int pos=0);
+ ~ListField() { free((void*)name); }
+ void ResetPos();
+};
+
+class ViewFilter
+{
+public:
+ virtual ~ViewFilter(){}
+ virtual void SortResults(C_Config *viewconf, int which=0, int isfirst=0)=0;
+ virtual void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)=0;
+ virtual int Size()=0;
+ virtual const wchar_t *GetText(int row)=0;
+ virtual void CopyText(int row, size_t column, wchar_t *dest, int destCch)=0;
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch)=0;
+ virtual void Empty()=0;
+ virtual const wchar_t *GetField()=0;
+ virtual const wchar_t *GetName()=0;
+ virtual const wchar_t *GetNameSingular()=0;
+ virtual const wchar_t *GetNameSingularAlt(int mode)=0;
+
+ virtual const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len)=0;
+ virtual void AddColumns2()=0;
+ void AddColumns();
+ virtual void SaveColumnWidths();
+ static int __fastcall BuildSortFunc(const void *elem1, const void *elem2, const void *context);
+ virtual void CustomizeColumns(HWND parent, BOOL showmenu);
+ void ResetColumns();
+ virtual const wchar_t *GetComparisonOperator() {return L"LIKE";}
+ virtual char * GetConfigId(){return "av";}
+ void ClearColumns();
+ virtual INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam){return 0;}
+ virtual bool HasTopItem() {return true;}
+ virtual bool MakeFilterQuery(int n, GayStringW *str){return false;}
+
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+ virtual void MetaUpdate(int r, const wchar_t *metaItem, const wchar_t *value) {}
+
+ std::vector<ListField*> showncolumns;
+ std::vector<ListField*> hiddencolumns;
+ ViewFilter *nextFilter;
+ W_ListView * list;
+ int numGroups;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/add.cpp b/Src/Plugins/Library/ml_local/add.cpp
new file mode 100644
index 00000000..d99c93b8
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/add.cpp
@@ -0,0 +1,478 @@
+#include "main.h"
+#include "ml_local.h"
+#include "api_mldb.h"
+#include <commctrl.h>
+#include "resource.h"
+#include "../replicant/nu/ns_wc.h"
+#include "../nde/nde.h"
+#include "../Agave/Language/api_language.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "time.h"
+#include "../winamp/in2.h"
+#include "../Winamp/strutil.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+bool skipTitleInfo=false;
+
+static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len)
+{
+ dest[0]=0;
+ return AGAVE_API_METADATA->GetExtendedFileInfo(filename, metadata, dest, len);
+}
+
+static time_t FileTimeToUnixTime(FILETIME *ft)
+{
+ ULARGE_INTEGER end;
+ memcpy(&end,ft,sizeof(end));
+ end.QuadPart -= 116444736000000000;
+ end.QuadPart /= 10000000; // 100ns -> seconds
+ return (time_t)end.QuadPart;
+}
+
+void makeFilename2W(const wchar_t *filename, wchar_t *filename2, int filename2_len)
+{
+ filename2[0]=0;
+ GetLongPathNameW(filename, filename2, filename2_len);
+ if (!_wcsicmp(filename,filename2)) filename2[0]=0;
+}
+
+void makeFilename2(const char *filename, char *filename2, int filename2_len)
+{
+ filename2[0]=0;
+ GetLongPathNameA(filename, filename2, filename2_len);
+ if (!stricmp(filename,filename2)) filename2[0]=0;
+}
+
+static __int64 FileSize64(HANDLE file)
+{
+ LARGE_INTEGER position;
+ position.QuadPart=0;
+ position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
+
+ if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
+ return INVALID_FILE_SIZE;
+ else
+ return position.QuadPart;
+}
+
+static void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time)
+{
+ WIN32_FILE_ATTRIBUTE_DATA file_data;
+ if (GetFileAttributesExW(filename, GetFileExInfoStandard, &file_data) == FALSE)
+ {
+ // GetFileAttributesEx failed. that sucks, let's try something else
+ HANDLE hFile=CreateFileW(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ FILETIME lt = {0};
+ if (GetFileTime(hFile,NULL,NULL,&lt))
+ {
+ *file_time=FileTimeToUnixTime(&lt);
+ }
+ *file_size=FileSize64(hFile);
+ CloseHandle(hFile);
+ }
+ }
+ else
+ {
+ // success
+ *file_time = FileTimeToUnixTime(&file_data.ftLastWriteTime);
+ LARGE_INTEGER size64;
+ size64.LowPart = file_data.nFileSizeLow;
+ size64.HighPart = file_data.nFileSizeHigh;
+ *file_size = size64.QuadPart;
+ }
+}
+
+int FindFileInDatabase(nde_scanner_t s, int fieldId, const wchar_t *filename, wchar_t alternate[MAX_PATH])
+{
+ alternate[0]=0;
+
+ makeFilename2W(filename,alternate,MAX_PATH);
+ if (alternate[0])
+ {
+ if (NDE_Scanner_LocateFilename(s, fieldId, FIRST_RECORD, alternate))
+ {
+ return 2;
+ }
+ }
+ if (NDE_Scanner_LocateFilename(s, fieldId, FIRST_RECORD, filename))
+ return 1;
+
+ return 0;
+}
+
+static inline void GetOptionalField(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
+{
+ wchar_t tmp[1024]={0};
+ if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ tmp[1023]=0; // just in case
+ db_setFieldStringW(s, field_id,tmp);
+ }
+ else
+ {
+ db_removeField(s, field_id);
+ }
+ }
+}
+
+static inline void GetOptionalFieldInt(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
+{
+ wchar_t tmp[128]={0};
+ if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ tmp[127]=0; // just in case
+ db_setFieldInt(s,field_id,_wtoi(tmp));
+ }
+ else
+ {
+ db_removeField(s, field_id);
+ }
+ }
+}
+
+static inline void GetNonBlankFieldInt(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
+{
+ wchar_t tmp[128]={0};
+ if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ tmp[127]=0; // just in case
+ db_setFieldInt(s,field_id,_wtoi(tmp));
+ }
+ }
+}
+
+
+// return values
+// 0 - error
+// 1 - success
+// -2 - record not found (in update only mode)
+int addFileToDb(const wchar_t *filename, int onlyupdate, int use_metadata, int guess_mode, int playcnt, int lastplay, bool force)
+{
+ if (!_wcsicmp(PathFindExtensionW(filename), L".cda"))
+ return 0;
+
+ __int64 file_size=INVALID_FILE_SIZE;
+ time_t file_time=0;
+ GetFileSizeAndTime(filename, &file_size, &file_time);
+ if (file_size == INVALID_FILE_SIZE || file_size == 0)
+ return 0;
+
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ int found = FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+
+ if (found) // For updating
+ {
+ // if an update wasn't forced, see if the file's timestamp or filesize have changed
+ if (!force && NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILESIZE))
+ {
+ nde_field_t f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILETIME);
+ if (f && file_time <= NDE_IntegerField_GetValue(f))
+ {
+ f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LASTUPDTIME);
+ if (f && file_time <= NDE_IntegerField_GetValue(f))
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+ }
+ }
+ NDE_Scanner_Edit(s);
+ if (found == 1 && filename2[0]) db_setFieldStringW(s,MAINTABLE_ID_FILENAME,filename2); // if we have a better filename, update it
+ nde_field_t f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
+ int cnt = f?NDE_IntegerField_GetValue(f):0;
+ if (!cnt)
+ db_setFieldInt(s,MAINTABLE_ID_PLAYCOUNT,0);
+ }
+ else // Adding an entry from scratch
+ {
+ if (onlyupdate)
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ // Issue a wasabi system callback after we have successfully updated a file in the ml database
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED_EXTERNAL, (size_t)filename, 0);
+ return -2;
+ }
+ // new file
+ NDE_Scanner_New(s);
+ db_setFieldStringW(s,MAINTABLE_ID_FILENAME,filename2[0]?filename2:filename);
+ db_setFieldInt(s,MAINTABLE_ID_PLAYCOUNT,playcnt);
+ if (lastplay)
+ db_setFieldInt(s,MAINTABLE_ID_LASTPLAY,lastplay);
+ db_setFieldInt(s,MAINTABLE_ID_DATEADDED, (int)time(NULL));
+ }
+
+ int hasttitle=0, hastartist=0, hastalbum=0, hasttrack=0;
+ int hastyear=0, hastlength=0;
+ int hasdisc=0, hasalbumartist=0;
+ int hasttype=0, hasdiscs=0, hastracks=0, hasbitrate=0;
+ int the_length_sec=0;
+ wchar_t m_artist[1024] = {0}, tmp[1024] = {0};
+
+ if(getFileInfoW(filename, DB_FIELDNAME_type, tmp, sizeof(tmp) / sizeof(wchar_t)) )
+ {
+ if(tmp[0]) { int type=_wtoi(tmp); db_setFieldInt(s,MAINTABLE_ID_TYPE,type); hasttype++; }
+ }
+
+ if (getFileInfoW(filename, DB_FIELDNAME_length,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0]) { db_setFieldInt(s,MAINTABLE_ID_LENGTH,the_length_sec=(_wtoi(tmp)/1000)); hastlength++; }
+ }
+
+ if (getFileInfoW(filename, DB_FIELDNAME_bitrate,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0]) { db_setFieldInt(s,MAINTABLE_ID_BITRATE,_wtoi(tmp)); hasbitrate++; }
+ }
+
+ if(use_metadata && getFileInfoW(filename, DB_FIELDNAME_title,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ db_setFieldStringW(s,MAINTABLE_ID_TITLE,tmp);
+ hasttitle++;
+ }
+
+ getFileInfoW( filename, DB_FIELDNAME_artist, tmp, sizeof( tmp ) / sizeof( wchar_t ) );
+
+ if(tmp[0])
+ {
+ StringCchCopyW(m_artist, 1024, tmp);
+ db_setFieldStringW(s,MAINTABLE_ID_ARTIST,tmp);
+ hastartist++;
+ }
+
+ getFileInfoW(filename, DB_FIELDNAME_album,tmp,sizeof(tmp)/sizeof(wchar_t));
+
+ if(tmp[0])
+ {
+ db_setFieldStringW(s,MAINTABLE_ID_ALBUM,tmp);
+ hastalbum++;
+ }
+
+ GetOptionalField(s, filename, DB_FIELDNAME_comment, MAINTABLE_ID_COMMENT);
+
+ getFileInfoW(filename, DB_FIELDNAME_year,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0] && !wcsstr(tmp,L"__") && !wcsstr(tmp,L"/") && !wcsstr(tmp,L"\\") && !wcsstr(tmp,L".")) {
+ wchar_t *p=tmp;
+ while (p && *p)
+ {
+ if (*p == L'_') *p=L'0';
+ p++;
+ }
+ int y=_wtoi(tmp);
+ if(y!=0) { db_setFieldInt(s,MAINTABLE_ID_YEAR,_wtoi(tmp)); hastyear++; }
+ }
+ GetOptionalField(s, filename, DB_FIELDNAME_genre, MAINTABLE_ID_GENRE);
+
+ getFileInfoW(filename, DB_FIELDNAME_track,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0])
+ {
+ int track, tracks;
+ ParseIntSlashInt(tmp, &track, &tracks);
+ if (track > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_TRACKNB,track);
+ hasttrack++;
+ }
+ if (tracks > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_TRACKS,tracks);
+ hastracks++;
+ }
+ }
+ getFileInfoW(filename, DB_FIELDNAME_disc,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0])
+ {
+ int disc, discs;
+ ParseIntSlashInt(tmp, &disc, &discs);
+ if (disc > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_DISC,disc);
+ hasdisc++;
+ }
+ if (discs > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_DISCS,discs);
+ hasdiscs++;
+ }
+ }
+ getFileInfoW(filename, DB_FIELDNAME_albumartist,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0]) { db_setFieldStringW(s,MAINTABLE_ID_ALBUMARTIST,tmp); hasalbumartist++; }
+ GetOptionalField(s, filename, DB_FIELDNAME_publisher, MAINTABLE_ID_PUBLISHER);
+ GetOptionalField(s, filename, DB_FIELDNAME_composer, MAINTABLE_ID_COMPOSER);
+ GetOptionalField(s, filename, DB_FIELDNAME_replaygain_album_gain, MAINTABLE_ID_ALBUMGAIN);
+ GetOptionalField(s, filename, DB_FIELDNAME_replaygain_track_gain, MAINTABLE_ID_TRACKGAIN);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_bpm, MAINTABLE_ID_BPM);
+ GetOptionalField(s, filename, DB_FIELDNAME_GracenoteFileID, MAINTABLE_ID_GRACENOTEFILEID);
+ GetOptionalField(s, filename, DB_FIELDNAME_GracenoteExtData, MAINTABLE_ID_GRACENOTEEXTDATA);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_lossless, MAINTABLE_ID_LOSSLESS);
+ GetOptionalField(s, filename, DB_FIELDNAME_category, MAINTABLE_ID_CATEGORY);
+ GetOptionalField(s, filename, DB_FIELDNAME_codec, MAINTABLE_ID_CODEC);
+ GetOptionalField(s, filename, DB_FIELDNAME_director, MAINTABLE_ID_DIRECTOR);
+ GetOptionalField(s, filename, DB_FIELDNAME_producer, MAINTABLE_ID_PRODUCER);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_width, MAINTABLE_ID_WIDTH);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_height, MAINTABLE_ID_HEIGHT);
+ if (g_config->ReadInt(L"writeratings", 0))
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_rating, MAINTABLE_ID_RATING);
+ else
+ GetNonBlankFieldInt(s, filename, DB_FIELDNAME_rating, MAINTABLE_ID_RATING);
+ GetOptionalField(s, filename, L"mime", MAINTABLE_ID_MIMETYPE);
+ }
+
+ int guessmode = guess_mode;
+ if (guessmode != 2 && ((!hasttitle) + (!hastartist) + (!hastalbum) + (!hasttrack) >= (g_guessifany ? 1 : 4)))
+ {
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0, *guessbuf = 0;
+
+ if (guessmode==1)
+ {
+ guessbuf = _wcsdup(filename2[0] ? filename2 : filename);
+
+ wchar_t *p=scanstr_backW(guessbuf, L"\\/.", guessbuf);
+ if (*p == '.')
+ {
+ *p = 0;
+ p = scanstr_backW(guessbuf, L"\\/", guessbuf);
+ }
+
+ if (p > guessbuf)
+ {
+ *p = 0;
+ title = p+1;
+ p=scanstr_backW(guessbuf, L"\\/", guessbuf);
+ if (p > guessbuf)
+ {
+ *p = 0;
+ album = p+1;
+ p=scanstr_backW(guessbuf,L"\\/", guessbuf);
+ if (p > guessbuf)
+ {
+ *p = 0;
+ artist = p+1;
+ }
+ }
+ }
+ }
+ else
+ guessbuf = guessTitles(filename2[0] ? filename2 : filename, &tn, &artist, &album, &title);
+
+ if (guessbuf)
+ {
+ if (!hasttitle && title) { hasttitle++; db_setFieldStringW(s,MAINTABLE_ID_TITLE,title); }
+ if (!hastartist && artist) { hastartist++; db_setFieldStringW(s,MAINTABLE_ID_ARTIST,artist); StringCbCopyW(m_artist, sizeof(m_artist), artist); }
+ if (!hastalbum && album) { hastalbum++; db_setFieldStringW(s,MAINTABLE_ID_ALBUM,album); }
+ if (!hasttrack && tn) { hasttrack++; db_setFieldInt(s,MAINTABLE_ID_TRACKNB,tn); }
+ free(guessbuf);
+ }
+ }
+
+ if (!hastlength || !hasttitle)
+ {
+ // try to query length and title using older GetFileInfo input plugin API
+ wchar_t ft[1024] = {0};
+ basicFileInfoStructW bi={0};
+ bi.filename=filename2[0]?filename2:filename;
+ bi.length=-1;
+ bi.title=ft;
+ bi.titlelen=1024;
+ skipTitleInfo=true;
+ LeaveCriticalSection(&g_db_cs); // benski> not actually sure if this is safe, but it prevents a deadlock
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&bi,IPC_GET_BASIC_FILE_INFOW);
+ EnterCriticalSection(&g_db_cs);
+ skipTitleInfo=false;
+ if (!hastlength && bi.length >= 0)
+ {
+ hastlength=1;
+ db_setFieldInt(s,MAINTABLE_ID_LENGTH,the_length_sec=bi.length);
+ }
+ if (!hasttitle && ft[0])
+ {
+ hasttitle=1;
+ db_setFieldStringW(s,MAINTABLE_ID_TITLE,ft);
+ }
+ }
+
+ // set up default (empty) strings/values
+ if (!hasttitle) // title=filename
+ {
+ wchar_t *p = PathFindFileNameW(filename), *dup = _wcsdup(p);
+ if (dup)
+ {
+ PathRemoveExtensionW(dup);
+ db_setFieldStringW(s,MAINTABLE_ID_TITLE,dup);
+ free(dup);
+ }
+ }
+
+ if (!hastartist) db_removeField(s,MAINTABLE_ID_ARTIST);
+ if (!hastalbum) db_removeField(s,MAINTABLE_ID_ALBUM);
+ if (!hasttrack) db_removeField(s,MAINTABLE_ID_TRACKNB);
+ if (!hastracks) db_removeField(s,MAINTABLE_ID_TRACKS);
+ if (!hastyear) db_removeField(s,MAINTABLE_ID_YEAR);
+ if (!hastlength) db_removeField(s,MAINTABLE_ID_LENGTH);
+ if (!hasttype) db_setFieldInt(s,MAINTABLE_ID_TYPE,0); //audio
+ if (!hasdisc) db_removeField(s, MAINTABLE_ID_DISC);
+ if (!hasdiscs) db_removeField(s, MAINTABLE_ID_DISCS);
+ if (!hasalbumartist)
+ {
+ if (hastartist && g_config->ReadInt(L"artist_as_albumartist", 1))
+ db_setFieldStringW(s, MAINTABLE_ID_ALBUMARTIST, m_artist);
+ else
+ db_removeField(s, MAINTABLE_ID_ALBUMARTIST);
+ }
+
+ if (file_size != INVALID_FILE_SIZE)
+ {
+ db_setFieldInt64(s,MAINTABLE_ID_FILESIZE, file_size);
+ }
+ else db_removeField(s,MAINTABLE_ID_FILESIZE);
+
+ db_setFieldInt(s,MAINTABLE_ID_LASTUPDTIME, (int)time(NULL));
+ db_setFieldInt(s,MAINTABLE_ID_FILETIME, (int)file_time);
+
+ if (!hasbitrate && the_length_sec)
+ {
+ __int64 br =(file_size*8LL) / (__int64)the_length_sec;
+ br /= 1000;
+ db_setFieldInt(s,MAINTABLE_ID_BITRATE,(int)br);
+ }
+
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+
+ if (found)
+ {
+ // Issue a wasabi system callback after we have successfully updated a file in the ml database
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED, (size_t)filename, 0);
+ }
+ else
+ {
+ // Issue a wasabi system callback after we have successfully added a file in the ml database
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_ADDED, (size_t)filename, 0);
+ }
+
+ return 1;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/api__ml_local.h b/Src/Plugins/Library/ml_local/api__ml_local.h
new file mode 100644
index 00000000..7c602232
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/api__ml_local.h
@@ -0,0 +1,15 @@
+#pragma once
+#include "../Agave/Agave.h"
+
+DECLARE_EXTERNAL_SERVICE(api_application, WASABI_API_APP);
+DECLARE_EXTERNAL_SERVICE(api_explorerfindfile, WASABI_API_EXPLORERFINDFILE);
+DECLARE_EXTERNAL_SERVICE(api_language, WASABI_API_LNG);
+DECLARE_EXTERNAL_SERVICE(api_syscb, WASABI_API_SYSCB);
+DECLARE_EXTERNAL_SERVICE(api_memmgr, WASABI_API_MEMMGR);
+DECLARE_EXTERNAL_SERVICE(api_albumart, AGAVE_API_ALBUMART);
+DECLARE_EXTERNAL_SERVICE(api_metadata, AGAVE_API_METADATA);
+DECLARE_EXTERNAL_SERVICE(api_playlistmanager, AGAVE_API_PLAYLISTMANAGER);
+DECLARE_EXTERNAL_SERVICE(api_itunes_importer, AGAVE_API_ITUNES_IMPORTER);
+DECLARE_EXTERNAL_SERVICE(api_playlist_generator, AGAVE_API_PLAYLIST_GENERATOR);
+DECLARE_EXTERNAL_SERVICE(api_threadpool, WASABI_API_THREADPOOL);
+
diff --git a/Src/Plugins/Library/ml_local/api_mldb.cpp b/Src/Plugins/Library/ml_local/api_mldb.cpp
new file mode 100644
index 00000000..67220738
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/api_mldb.cpp
@@ -0,0 +1,3 @@
+#include "api_mldb.h"
+
+// this file exists solely to ensure that api_mldb.h can be #include'd by itself \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/api_mldb.h b/Src/Plugins/Library/ml_local/api_mldb.h
new file mode 100644
index 00000000..ed792573
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/api_mldb.h
@@ -0,0 +1,166 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include "..\..\General\gen_ml/ml.h"
+
+class api_mldb : public Dispatchable
+{
+protected:
+ api_mldb() {}
+ ~api_mldb() {}
+public:
+ itemRecordW *GetFile(const wchar_t *filename);
+ itemRecordW *GetFileIf(const wchar_t *filename, const wchar_t *query); // returns the item record for a filename, but also checks against the passed query
+ itemRecordListW *GetAlbum(const wchar_t *albumname, const wchar_t *albumartist);
+ itemRecordListW *Query(const wchar_t *query);
+ itemRecordListW *QueryLimit(const wchar_t *query, unsigned int limit);
+
+ void SetField(const wchar_t *filename, const char *field, const wchar_t *value);
+ void SetFieldInteger(const wchar_t *filename, const char *field, int value);
+ void SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16]);
+ void Sync();
+
+ void FreeRecord(itemRecordW *record);
+ void FreeRecordList(itemRecordListW *recordList);
+
+ int AddFile(const wchar_t *filename);
+ int RemoveFile(const wchar_t *filename);
+
+ /* wrappers around ndestring */
+ void RetainString(wchar_t *str);
+ void ReleaseString(wchar_t *str);
+ wchar_t *DuplicateString(const wchar_t *str);
+
+ int GetMaxInteger(const char *field, int *max);
+
+ DISPATCH_CODES
+ {
+ API_MLDB_GETFILE = 10,
+ API_MLDB_GETFILEIF = 11,
+ API_MLDB_GETALBUM = 20,
+ API_MLDB_QUERY = 30,
+ API_MLDB_QUERYLIMIT = 31,
+ API_MLDB_FREERECORD = 40,
+ API_MLDB_FREERECORDLIST = 50,
+ API_MLDB_SETFIELD = 60,
+ API_MLDB_SETFIELDINT = 61,
+ API_MLDB_SETFIELDINT128 = 62,
+ API_MLDB_SYNC = 70,
+ API_MLDB_ADDFILE = 80,
+ API_MLDB_REMOVEFILE = 90,
+ API_MLDB_RETAINSTRING = 100,
+ API_MLDB_RELEASESTRING = 110,
+ API_MLDB_DUPLICATESTRING = 120,
+ API_MLDB_GETMAXINTEGER = 130,
+ };
+
+ typedef struct
+ {
+ time_t played;
+ int count;
+ } played_info;
+
+ enum
+ {
+ // System callbacks for api_mldb
+ SYSCALLBACK = MK4CC('m','l','d','b'), // Unique identifier for mldb_api callbacks
+ MLDB_FILE_ADDED = 10, // param1 = filename, param2 = (not used), Callback event for when a new file is added to the local mldb
+ MLDB_FILE_REMOVED_PRE = 20, // param1 = filename, param2 = (not used), Callback event for when a file is removed from the local mldb (before it happens)
+ MLDB_FILE_REMOVED_POST = 25, // param1 = filename, param2 = (not used), Callback event for when a file is removed from the local mldb (after it happens)
+ MLDB_FILE_UPDATED = 30, // param1 = filename, param2 = (not used), Callback event for when a file is modified in the local mldb
+ MLDB_FILE_UPDATED_EXTERNAL = 35, // param1 = filename, param2 = (not used), Callback event for when a file is modified and is not in the local mldb
+ MLDB_CLEARED = 40, // param1 = filenames, param2 = count, Callback event for when the local mldb is cleared (useful so removed is not triggered for all files)
+ MLDB_FILE_PLAYED = 50, // param1 = filename, param2 = played_info, Callback event for when a file is tracked as playing in the local mldb
+ MLDB_FILE_GET_CLOUD_STATUS = 60, // param1 = filename, param2 = HMENU*, Callback event for when ml_local needs to show a cloud status menu (returned by the handler in param2)
+ MLDB_FILE_PROCESS_CLOUD_STATUS = 65, // param1 = menu_item, param2 = int*, Callback event for when ml_local needs to process the result of a cloud status menu
+ };
+};
+
+inline itemRecordW *api_mldb::GetFile(const wchar_t *filename)
+{
+ return _call(API_MLDB_GETFILE, (itemRecordW *)0, filename);
+}
+
+inline itemRecordW *api_mldb::GetFileIf(const wchar_t *filename, const wchar_t *query)
+{
+ return _call(API_MLDB_GETFILEIF, (itemRecordW *)0, filename, query);
+}
+
+inline itemRecordListW *api_mldb::GetAlbum(const wchar_t *albumname, const wchar_t *albumartist)
+{
+ return _call(API_MLDB_GETALBUM, (itemRecordListW *)0, albumname, albumartist);
+}
+
+inline itemRecordListW *api_mldb::Query(const wchar_t *query)
+{
+ return _call(API_MLDB_QUERY, (itemRecordListW *)0, query);
+}
+
+inline itemRecordListW *api_mldb::QueryLimit(const wchar_t *query, unsigned int limit)
+{
+ return _call(API_MLDB_QUERYLIMIT, (itemRecordListW *)0, query, limit);
+}
+
+inline void api_mldb::FreeRecord(itemRecordW *record)
+{
+ _voidcall(API_MLDB_FREERECORD, record);
+}
+
+inline void api_mldb::FreeRecordList(itemRecordListW *recordList)
+{
+ _voidcall(API_MLDB_FREERECORDLIST, recordList);
+}
+
+inline void api_mldb::SetField(const wchar_t *filename, const char *field, const wchar_t *value)
+{
+ _voidcall(API_MLDB_SETFIELD, filename, field, value);
+}
+
+inline void api_mldb::SetFieldInteger(const wchar_t *filename, const char *field, int value)
+{
+ _voidcall(API_MLDB_SETFIELDINT, filename, field, value);
+}
+
+inline void api_mldb::SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16])
+{
+ _voidcall(API_MLDB_SETFIELDINT128, filename, field, value);
+}
+
+inline void api_mldb::Sync()
+{
+ _voidcall(API_MLDB_SYNC);
+}
+
+inline int api_mldb::AddFile(const wchar_t *filename)
+{
+ return _call(API_MLDB_ADDFILE, (int)0, filename);
+}
+
+inline int api_mldb::RemoveFile(const wchar_t *filename)
+{
+ return _call(API_MLDB_REMOVEFILE, (int)0, filename);
+}
+
+inline void api_mldb::RetainString(wchar_t *str)
+{
+ _voidcall(API_MLDB_RETAINSTRING, str);
+}
+
+inline void api_mldb::ReleaseString(wchar_t *str)
+{
+ _voidcall(API_MLDB_RELEASESTRING, str);
+}
+
+inline wchar_t *api_mldb::DuplicateString(const wchar_t *str)
+{
+ return _call(API_MLDB_DUPLICATESTRING, (wchar_t *)0, str);
+}
+
+inline int api_mldb::GetMaxInteger(const char *field, int *max)
+{
+ return _call(API_MLDB_GETMAXINTEGER, (int)1, field, max);
+}
+
+// {5A94DABC-E19A-4a12-9AA8-852D8BF06532}
+static const GUID mldbApiGuid =
+{ 0x5a94dabc, 0xe19a, 0x4a12, { 0x9a, 0xa8, 0x85, 0x2d, 0x8b, 0xf0, 0x65, 0x32 } };
diff --git a/Src/Plugins/Library/ml_local/bgscan.cpp b/Src/Plugins/Library/ml_local/bgscan.cpp
new file mode 100644
index 00000000..8b4d9bdc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/bgscan.cpp
@@ -0,0 +1,940 @@
+#include "main.h"
+#include "resource.h"
+#include "api_mldb.h"
+#include "../Winamp/strutil.h"
+
+enum {
+ STATUS_SEARCHING,
+ STATUS_GETINFO,
+ STATUS_DONE,
+};
+
+extern HWND g_bgrescan_status_hwnd;
+extern nde_scanner_t m_media_scanner;
+
+#define MAX_RECURSE_DEPTH 32
+/* Event handles */
+static HANDLE scan_killswitch=0;
+static HANDLE scan_cancel=0;
+static HANDLE scan_cancel_complete=0;
+/* Thread handle */
+static HANDLE scan_thread=0;
+/* extension list */
+static wchar_t *scan_extlist=0;
+
+static void SyncTable()
+{
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+}
+
+static bool ScanCancelled(HANDLE *events, int count)
+{
+ // make sure no one cancelled us
+ DWORD eventFired=WaitForMultipleObjectsEx(count, events, FALSE, 0, TRUE);
+ if (eventFired >= WAIT_OBJECT_0 && eventFired < (WAIT_OBJECT_0+count))
+ return true;
+
+ return false;
+}
+
+static bool SupportedType(const wchar_t *ext)
+{
+ if (!scan_extlist)
+ scan_extlist=(wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_EXTLISTW);
+
+ // dunno how this would happen but should verify
+ if (!scan_extlist || scan_extlist == (wchar_t *)1)
+ return false;
+
+ const wchar_t *a = scan_extlist;
+ while (a && *a)
+ {
+ if (CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, a, -1, ext, -1) == CSTR_EQUAL)
+ {
+ return true;
+ }
+ a+=wcslen(a)+1;
+ }
+ return false;
+}
+
+static void CountFolder(const wchar_t *folder, int recurse, HANDLE cancelswitch, volatile int *found)
+{
+ wchar_t filespec[MAX_PATH] = {0};
+ PathCombineW(filespec, folder, L"*.*");
+
+ WIN32_FIND_DATAW findData = {0};
+
+ HANDLE h = FindFirstFileW(filespec, &findData);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t status[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_DIR, status, 150);
+
+ const wchar_t *p=folder+wcslen(folder);
+ while (p > folder && *p != '\\') p--;
+ p--;
+ while (p >= folder && *p != '\\') p--;
+
+ StringCbCatW(status, sizeof(status), ++p);
+
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+
+ HANDLE events[2] = { scan_killswitch, cancelswitch};
+ do
+ {
+ // make sure no one cancelled us
+ if (ScanCancelled(events, 2))
+ break;
+
+ /* if it's a directory (And not either of the two special dirs */
+ if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ && lstrcmpiW(findData.cFileName, L".")
+ && lstrcmpiW(findData.cFileName, L".."))
+ {
+ if (recurse && recurse < MAX_RECURSE_DEPTH)
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ CountFolder(filespec, recurse+1, cancelswitch, found); // add 1 so we can verify recurse depth
+ }
+ }
+
+ if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ wchar_t *ext=extensionW(findData.cFileName);
+ if (ext && ext[0] && SupportedType(ext))
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, b, 150);
+ StringCbCatW(b, sizeof(b), filespec);
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ if (found)
+ (*found)++;
+ }
+ }
+
+ } while (FindNextFileW(h, &findData));
+ FindClose(h);
+ }
+ else if (!(GetFileAttributesW(folder) & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, b, 150);
+ StringCbCatW(b, sizeof(b), folder);
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ if (found)
+ (*found)++;
+ }
+}
+
+static void ScanFolder(const wchar_t *folder, int recurse, int metadata, int guessmode, HANDLE cancelswitch, volatile int *scanned)
+{
+ if ((unsigned long)folder < 65536) return;
+
+ wchar_t filespec[MAX_PATH] = {0};
+ wchar_t status[150+MAX_PATH] = {0};
+ PathCombineW(filespec, folder, L"*.*");
+
+ WIN32_FIND_DATAW findData = {0};
+
+ HANDLE h = FindFirstFileW(filespec, &findData);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_DIR, status, 150);
+
+ const wchar_t *p=folder+wcslen(folder);
+ while (p > folder && *p != '\\') p--;
+ p--;
+ while (p >= folder && *p != '\\') p--;
+
+ StringCbCatW(status, sizeof(status), ++p);
+
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+
+ HANDLE events[2] = { scan_killswitch, cancelswitch};
+ do
+ {
+ // make sure no one cancelled us
+ if (ScanCancelled(events, 2))
+ break;
+
+ /* if it's a directory (And not either of the two special dirs */
+ if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ && lstrcmpiW(findData.cFileName, L".")
+ && lstrcmpiW(findData.cFileName, L".."))
+ {
+ if (recurse && recurse < MAX_RECURSE_DEPTH)
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ ScanFolder(filespec, recurse+1, metadata, guessmode, cancelswitch, scanned); // add 1 so we can verify recurse depth
+ }
+ }
+
+ if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ wchar_t *ext=extensionW(findData.cFileName);
+ if (ext && ext[0] && SupportedType(ext))
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, status, 150);
+ StringCbCatW(status, sizeof(status), filespec);
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+ addFileToDb(filespec, 0, metadata, guessmode);
+ if (scanned)
+ (*scanned)++;
+ }
+ }
+ } while (FindNextFileW(h, &findData));
+ FindClose(h);
+ }
+ else if (!(GetFileAttributesW(folder) & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, status, 150);
+ StringCbCatW(status, sizeof(status), folder);
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+ addFileToDb(folder, 0, metadata, guessmode);
+ if (scanned)
+ (*scanned)++;
+ }
+}
+
+static DWORD CALLBACK ScanThreadProc(LPVOID param)
+{
+ /* sit and run APCs until we get signalled to die */
+ HANDLE events[2] = { scan_killswitch, scan_cancel};
+ int eventFired;
+ do
+ {
+ eventFired=WaitForMultipleObjectsEx(2, events, FALSE, INFINITE, TRUE);
+ switch(eventFired)
+ {
+ case WAIT_OBJECT_0+1: // cancel event
+ ResetEvent(scan_cancel);
+ SetEvent(scan_cancel_complete);
+ break;
+ }
+ }
+ while (eventFired != WAIT_OBJECT_0);
+
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist=0;
+ return 0;
+}
+
+static bool ScanCreateThread()
+{
+ if (!scan_thread)
+ {
+ /* create events */
+ scan_killswitch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ scan_cancel = CreateEvent(NULL, TRUE, FALSE, NULL);
+ scan_cancel_complete = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset event
+
+ /* start thread */
+ scan_thread = CreateThread(NULL, 0, ScanThreadProc, 0, 0, 0);
+ }
+
+ return !!scan_thread;
+}
+
+void Scan_Cancel()
+{
+ HWND old = g_bgrescan_status_hwnd; // clear g_bgrescan_status_hwnd so that we don't deadlock when the BG thread calls SetWindowText
+ g_bgrescan_status_hwnd = 0;
+ if (scan_cancel)
+ SignalObjectAndWait(scan_cancel, scan_cancel_complete, INFINITE, FALSE);
+ g_bgrescan_status_hwnd = old;
+}
+
+void Scan_Kill()
+{
+ HWND old = g_bgrescan_status_hwnd; // clear g_bgrescan_status_hwnd so that we don't deadlock when the BG thread calls SetWindowText
+ g_bgrescan_status_hwnd = 0;
+ if (scan_thread)
+ SignalObjectAndWait(scan_killswitch, scan_thread, INFINITE, FALSE);
+ g_bgrescan_status_hwnd = old;
+}
+
+/* ---------------
+* Scan_ScanFolder
+* ---------------
+*/
+
+struct ScanFolderParams
+{
+ ScanFolderParams(const wchar_t *_path, int _guess, int _meta, int _recurse)
+ {
+ path = _wcsdup(_path);
+ guess = _guess >= 0 ? _guess : g_config->ReadInt(L"guessmode",0);;
+ meta = _meta >= 0 ? _meta : g_config->ReadInt(L"usemetadata",1);
+ recurse = _recurse;
+ }
+ ~ScanFolderParams()
+ {
+ free(path);
+ }
+ wchar_t *path;
+ int guess;
+ int meta;
+ int recurse;
+};
+
+static VOID CALLBACK ScanFolderAPC(ULONG_PTR param)
+{
+ // clear extension list to get latest config
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist = 0;
+
+ ScanFolderParams *params = (ScanFolderParams *)param;
+ ScanFolder(params->path, params->recurse, params->meta, params->guess, scan_cancel, 0);
+ SyncTable();
+ delete params;
+}
+
+void Scan_ScanFolderBackground(const wchar_t *path, int guess, int meta, int recurse)
+{
+ if (ScanCreateThread())
+ {
+ ScanFolderParams *params = new ScanFolderParams(path, guess, meta, recurse);
+ if (QueueUserAPC(ScanFolderAPC, scan_thread, (ULONG_PTR)params) == 0)
+ delete params;
+ }
+}
+
+/* ---------------
+* Scan_ScanFolders
+* ---------------
+*/
+
+struct ScanFoldersParams
+{
+ ScanFoldersParams(wchar_t **_path, size_t _count, int *_guess, int *_meta, int *_recurse)
+ {
+ path = _path;
+ count = _count;
+ guess = _guess;
+ meta = _meta;
+ recurse = _recurse;
+ found = 0;
+ scanned = 0;
+ cancel_switch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ status = STATUS_SEARCHING;
+ ui = 0;
+ in_timer = 0;
+
+ }
+ ~ScanFoldersParams()
+ {
+ for (size_t i=0;i!=count;i++)
+ free(path[i]);
+ free(path);
+ free(guess);
+ free(meta);
+ free(recurse);
+ CloseHandle(cancel_switch);
+ }
+ wchar_t **path;
+ size_t count;
+ int *guess;
+ int *meta;
+ int *recurse;
+ volatile int found;
+ volatile int scanned;
+ volatile int status;
+ int in_timer;
+ HANDLE cancel_switch;
+ HWND ui;
+};
+
+static VOID CALLBACK ScanFoldersAPC(ULONG_PTR param)
+{
+ // clear extension list to get latest config
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist = 0;
+
+ ScanFoldersParams *params = (ScanFoldersParams *)param;
+ HANDLE events[2] = { scan_killswitch, params->cancel_switch};
+
+ for (size_t i=0;i!=params->count;i++)
+ {
+ if (ScanCancelled(events, 2))
+ break;
+
+ CountFolder(params->path[i], params->recurse[i], params->cancel_switch, &params->found);
+ }
+
+ params->status = STATUS_GETINFO;
+
+ for (size_t i=0;i!=params->count;i++)
+ {
+ if (ScanCancelled(events, 2))
+ break;
+
+ int guess = params->guess[i] >= 0 ? params->guess[i] : g_config->ReadInt(L"guessmode",0);;
+ int meta = params->meta[i] >= 0 ? params->meta[i] : g_config->ReadInt(L"usemetadata",1);
+ ScanFolder(params->path[i], params->recurse[i], meta, guess, params->cancel_switch, &params->scanned);
+ }
+
+ params->status = STATUS_DONE;
+ PostMessage(params->ui, WM_APP, 0, 0);
+}
+
+static INT_PTR CALLBACK ScanFileUI(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,WASABI_API_LNGSTRINGW(IDS_INITIALIZING));
+
+ ScanFoldersParams *params = (ScanFoldersParams *)lParam;
+ params->ui = hwndDlg;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ if (QueueUserAPC(ScanFoldersAPC, scan_thread, (ULONG_PTR)lParam) == 0)
+ EndDialog(hwndDlg, 0);
+ else
+ {
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETRANGE,0,MAKELPARAM(0, 100));
+ SetTimer(hwndDlg,0x123,300,NULL);
+ }
+
+ // show window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"scan_x", -1), g_config->ReadInt(L"scan_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+
+ case WM_TIMER:
+ {
+ ScanFoldersParams *params = (ScanFoldersParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (params->in_timer) break;
+ params->in_timer++;
+
+ if(params->status==STATUS_SEARCHING)
+ {
+ wchar_t tmp[512] = {0};
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_SEARCHING_X_FILES_FOUND), params->found);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ }
+ else if(params->status==STATUS_GETINFO)
+ {
+ wchar_t tmp[512] = {0};
+ int perc=params->found?(params->scanned*100/params->found):0;
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_GETTING_INFO_FROM_FILES_PERCENT),perc);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,perc,0);
+ }
+ params->in_timer--;
+ }
+ break;
+
+ case WM_APP:
+ {
+ KillTimer(hwndDlg,0x123);
+ SyncTable();
+ EndDialog(hwndDlg,0);
+ }
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam)==IDCANCEL)
+ {
+ ScanFoldersParams *params = (ScanFoldersParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetEvent(params->cancel_switch);
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ RECT scan_rect = {0};
+ GetWindowRect(hwndDlg, &scan_rect);
+ g_config->WriteInt(L"scan_x", scan_rect.left);
+ g_config->WriteInt(L"scan_y", scan_rect.top);
+
+ KillTimer(hwndDlg,0x123);
+ ScanFoldersParams *params = (ScanFoldersParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
+ delete params;
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+/* When you call this function, it will own the memory and release it with free() */
+void Scan_ScanFolders(HWND parent, size_t count, wchar_t **paths, int *guess, int *meta, int *recurse)
+{
+ openDb();
+
+ if (g_table && ScanCreateThread())
+ {
+ ScanFoldersParams *params = new ScanFoldersParams(paths, count, guess, meta, recurse);
+ WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_ADDSTUFF,
+ parent, (DLGPROC)ScanFileUI, (LPARAM)params);
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,NDE_Table_GetRecordsCount(g_table),IPC_STATS_LIBRARY_ITEMCNT);
+ }
+ else
+ {
+ for (size_t i=0;i!=count;i++)
+ free(paths[i]);
+ free(paths);
+ }
+}
+
+void Scan_ScanFolder(HWND parent, const wchar_t *path, int guess, int meta, int recurse)
+{
+ // kind of a hack ...
+ if (ScanCreateThread())
+ {
+ wchar_t **paths = (wchar_t **)calloc(1, sizeof(wchar_t*));
+ int *guesses = (int *)calloc(1, sizeof(int));
+ int *metas = (int *)calloc(1, sizeof(int));
+ int *recs = (int *)calloc(1, sizeof(int));
+ *guesses = guess;
+ *metas = meta;
+ *recs = recurse;
+ paths[0] = _wcsdup(path);
+ Scan_ScanFolders(parent, 1, paths, guesses, metas, recs);
+ }
+}
+
+static VOID CALLBACK BackgroundScanAPC(ULONG_PTR param)
+{
+ openDb();
+ if (!g_table)
+ return;
+
+ HANDLE events[2] = { scan_killswitch, scan_cancel};
+
+ // clear extension list to get latest config
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist = 0;
+
+ // read list from config
+ UINT codePage = CP_ACP;
+ char scandirlist[65536] = {0};
+ if (!g_config->ReadString("scandirlist", 0, scandirlist, 65536))
+ {
+ g_config->ReadString("scandirlist_utf8","", scandirlist, 65536);
+ codePage = CP_UTF8;
+ }
+
+ AutoWide s1(scandirlist, codePage);
+ size_t len = wcslen(s1)+2;
+ wchar_t *s =(wchar_t*)calloc(len, sizeof(wchar_t));
+ if (s)
+ {
+ lstrcpynW(s, s1, len);
+ s[wcslen(s)+1]=0;
+
+ wchar_t *p=s;
+ while (p && *p == L'|') p++;
+
+ while ((p=wcsstr(p,L"|")))
+ {
+ *p++=0;
+ while (p && *p == L'|') p++;
+ }
+ p=s;
+
+ // iterate through list
+ while (p && *p && !ScanCancelled(events, 2))
+ {
+ while (p && *p == L'|') p++;
+
+ int use_metadata=g_config->ReadInt(L"usemetadata",1);
+ int guess_mode=g_config->ReadInt(L"guessmode",0);
+ int recurse=1;
+ if (*p == L'<' && wcsstr(p,L">"))
+ {
+ p++;
+ while (p && *p != L'>')
+ {
+ // <MmSs>can prefix directory
+ // M=metadata use override
+ // m=no metadata
+ // S=smart guessing
+ // s=stupid guessing
+ if (*p == L'M') use_metadata=1;
+ else if (*p == L'm') use_metadata=0;
+ else if (*p == L'S') guess_mode=0;
+ else if (*p == L's') guess_mode=1;
+ else if (*p == L'r') recurse=0;
+ else if (*p == L'g') guess_mode=2;
+ p++;
+ }
+ p++;
+ }
+ ScanFolder(p, recurse, use_metadata, guess_mode, scan_cancel, 0);
+ p+=wcslen(p)+1;
+ }
+
+ free(s);
+ }
+
+ /* Remove missing files */
+ if (!ScanCancelled(events, 2) && g_config->ReadInt(L"bgrescan_compact",1))
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t scanner = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(scanner, L"");
+ NDE_Scanner_First(scanner);
+again:
+ nde_field_t f=NDE_Scanner_GetFieldByID(scanner, MAINTABLE_ID_FILENAME);
+ wchar_t *gs=0;
+ if (f)
+ {
+ gs = NDE_StringField_GetString(f);
+ ndestring_retain(gs);
+ if (GetFileAttributesW(gs) != INVALID_FILE_ATTRIBUTES)
+ {
+ NDE_Scanner_Next(scanner);
+ }
+ else
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)gs, 0);
+
+ NDE_Scanner_Delete(scanner);
+ NDE_Scanner_Post(scanner);
+
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)gs, 0);
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (f) // done checking for unused files
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_CHECKING_FOR_FILE, b, 150);
+ StringCbCatW(b, sizeof(b), PathFindFileNameW(gs));
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ ndestring_release(gs);
+ gs=0;
+ if (!ScanCancelled(events, 2))
+ {
+ EnterCriticalSection(&g_db_cs);
+ goto again;
+ }
+ }
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_DestroyScanner(g_table, scanner);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_COMPACTING, b, 150);
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ }
+
+ // TODO: hmmm, safe to do on separate thread?
+ EnterCriticalSection(&g_db_cs);
+ if (!ScanCancelled(events, 2))
+ {
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Sync(g_table); // this is currently b0rk3d -- fucko :)
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ if (m_media_scanner)
+ {
+ m_media_scanner=NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,NDE_Table_GetRecordsCount(g_table),IPC_STATS_LIBRARY_ITEMCNT);
+ }
+ else
+ {
+ NDE_Table_Sync(g_table); // this is currently b0rk3d -- fucko :)
+ g_table_dirty=0;
+ }
+ LeaveCriticalSection(&g_db_cs);
+ if (IsWindow(g_bgrescan_status_hwnd))
+ SetWindowTextW(g_bgrescan_status_hwnd,L"");
+ g_bgscan_last_rescan = time(NULL);
+ g_bgscan_scanning = 0;
+}
+
+void Scan_BackgroundScan()
+{
+ if (ScanCreateThread())
+ {
+ g_bgrescan_force = 0;
+ g_bgscan_last_rescan = time(NULL);
+ g_bgscan_scanning = 1;
+
+ QueueUserAPC(BackgroundScanAPC, scan_thread, (ULONG_PTR)0);
+
+ }
+}
+
+static void RemoveFiles(HANDLE cancelswitch, volatile int *found, volatile int *count, volatile int *scanned)
+{
+ // TODO: benski> we might need to keep the database lock the whole time. need to think it thru
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t myscanner=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(myscanner, L"");
+ NDE_Scanner_First(myscanner);
+ *found=0;
+ *scanned=0;
+ *count=NDE_Table_GetRecordsCount(g_table);
+ LeaveCriticalSection(&g_db_cs);
+
+ HANDLE events[2] = { scan_killswitch, cancelswitch};
+
+ bool fileRemoved = false;
+ wchar_t *filename;
+ while (!ScanCancelled(events, 2))
+ {
+ EnterCriticalSection(&g_db_cs);
+ if (!NDE_Scanner_BOF(myscanner) && !NDE_Scanner_EOF(myscanner))
+ {
+ nde_field_t f= NDE_Scanner_GetFieldByID(myscanner, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ (*scanned)++;
+
+ filename = (NDE_StringField_GetString(f));
+
+ if (GetFileAttributesW(NDE_StringField_GetString(f)) != INVALID_FILE_ATTRIBUTES)
+ {
+ NDE_Scanner_Next(myscanner);
+ }
+ else
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)filename, 0);
+
+ //remove file
+ NDE_Scanner_Delete(myscanner);
+ NDE_Scanner_Post(myscanner);
+ (*found)++;
+ fileRemoved = true;
+ }
+ }
+ else
+ {
+ //remove file
+ NDE_Scanner_Delete(myscanner);
+ NDE_Scanner_Post(myscanner);
+ (*found)++;
+ fileRemoved = false;
+ }
+ }
+ else // last file
+ {
+ LeaveCriticalSection(&g_db_cs);
+ break;
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (fileRemoved)
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)filename, 0);
+ fileRemoved = false;
+ }
+ }
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_DestroyScanner(g_table, myscanner); // important that we delete the scanner BEFORE
+ myscanner=0;
+
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Sync(g_table); // this is currently b0rk3d -- fucko :)
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ if (m_media_scanner)
+ {
+ m_media_scanner=NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+}
+
+struct RemoveFilesParams
+{
+ RemoveFilesParams()
+ {
+ found = 0;
+ scanned = 0;
+ total = 0;
+ cancel_switch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ ui = 0;
+ in_timer = 0;
+ }
+ ~RemoveFilesParams()
+ {
+
+ CloseHandle(cancel_switch);
+ }
+ volatile int found;
+ volatile int scanned;
+ volatile int total;
+
+ int in_timer;
+ HANDLE cancel_switch;
+ HWND ui;
+};
+
+static VOID CALLBACK RemoveFilesAPC(ULONG_PTR param)
+{
+ RemoveFilesParams *params = (RemoveFilesParams *)param;
+
+ RemoveFiles(params->cancel_switch, &params->found, &params->total, &params->scanned);
+ PostMessage(params->ui, WM_APP, 0, 0);
+}
+
+static INT_PTR CALLBACK RemoveFilesUI(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowTextW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_REMOVING_FILES_NOT_EXISTING));
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,WASABI_API_LNGSTRINGW(IDS_INITIALIZING));
+
+ RemoveFilesParams *params = (RemoveFilesParams *)lParam;
+ params->ui = hwndDlg;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ if (QueueUserAPC(RemoveFilesAPC, scan_thread, (ULONG_PTR)lParam) == 0)
+ EndDialog(hwndDlg, 0);
+ else
+ {
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETRANGE,0,MAKELPARAM(0, 100));
+ SetTimer(hwndDlg,0x123,300,NULL);
+ }
+
+ // show window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"scan_x", -1), g_config->ReadInt(L"scan_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+
+ case WM_TIMER:
+ {
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (params->in_timer) break;
+ params->in_timer++;
+
+ if(params->total)
+ {
+ wchar_t tmp[512] = {0};
+ int perc=(params->scanned*100/(params->total?params->total:1));
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_SCANNING_X_OF_X_X_REMOVED),params->scanned,params->total,params->found);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,perc,0);
+ }
+
+ params->in_timer--;
+ }
+ break;
+
+ case WM_APP:
+ {
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ KillTimer(hwndDlg,0x123);
+
+ wchar_t tmp[512] = {0};
+ int perc=(params->scanned*100/(params->total?params->total:1));
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_SCANNED_X_FILES_X_REMOVED),params->total,params->found);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,perc,0);
+
+ SyncTable();
+ EndDialog(hwndDlg,0);
+ }
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam)==IDCANCEL)
+ {
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetEvent(params->cancel_switch);
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ RECT scan_rect = {0};
+ GetWindowRect(hwndDlg, &scan_rect);
+ g_config->WriteInt(L"scan_x", scan_rect.left);
+ g_config->WriteInt(L"scan_y", scan_rect.top);
+
+ KillTimer(hwndDlg,0x123);
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
+ delete params;
+ }
+ return FALSE;
+ }
+ return FALSE;
+}
+
+void Scan_RemoveFiles(HWND parent)
+{
+ openDb();
+ if (g_table && ScanCreateThread())
+ {
+ RemoveFilesParams *params = new RemoveFilesParams();
+ WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_ADDSTUFF,
+ parent, (DLGPROC)RemoveFilesUI, (LPARAM)params);
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,NDE_Table_GetRecordsCount(g_table),IPC_STATS_LIBRARY_ITEMCNT);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/contnr.cpp b/Src/Plugins/Library/ml_local/contnr.cpp
new file mode 100644
index 00000000..6306f3f2
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/contnr.cpp
@@ -0,0 +1,979 @@
+/**************************************************************************
+ THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ PARTICULAR PURPOSE.
+
+ Copyright 1998 Microsoft Corporation. All Rights Reserved.
+**************************************************************************/
+
+/**************************************************************************
+
+ File: contnr.cpp
+
+ Description: This file contains the complete implementation of an
+ ActiveX control container. This purpose of this container
+ is to test a single control being hosted.
+
+**************************************************************************/
+
+/**************************************************************************
+ #include statements
+**************************************************************************/
+#include "main.h"
+#include <windows.h>
+#include <commctrl.h>
+#include "contnr.h"
+
+#include <mshtmdid.h>
+#include <shlobj.h>
+#include "ml_local.h"
+
+/**************************************************************************
+
+ CContainer::CContainer()
+
+**************************************************************************/
+extern IDispatch *winampExternal;
+
+CContainer::CContainer()
+{
+ m_cRefs = 1;
+ m_hwnd = NULL;
+ m_punk = NULL;
+ m_scrollbars= 1;
+ m_allowScripts=1;
+ m_restrictAlreadySet=0;
+ m_hwndStatus = 0;
+
+}
+
+/**************************************************************************
+
+ CContainer::~CContainer()
+
+**************************************************************************/
+
+CContainer::~CContainer()
+{
+ if (m_punk)
+ {
+ m_punk->Release();
+ m_punk=NULL;
+ }
+}
+
+/**************************************************************************
+
+ CContainer::QueryInterface()
+
+**************************************************************************/
+
+STDMETHODIMP CContainer::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IOleClientSite))
+ *ppvObject = (IOleClientSite *)this;
+ else if (IsEqualIID(riid, IID_IOleInPlaceSite))
+ *ppvObject = (IOleInPlaceSite *)this;
+ else if (IsEqualIID(riid, IID_IOleInPlaceFrame))
+ *ppvObject = (IOleInPlaceFrame *)this;
+ else if (IsEqualIID(riid, IID_IOleInPlaceUIWindow))
+ *ppvObject = (IOleInPlaceUIWindow *)this;
+ else if (IsEqualIID(riid, IID_IOleControlSite))
+ *ppvObject = (IOleControlSite *)this;
+ else if (IsEqualIID(riid, IID_IOleWindow))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_IDocHostUIHandler))
+ *ppvObject = (IDocHostUIHandler *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::AddRef()
+
+**************************************************************************/
+
+ULONG CContainer::AddRef(void)
+{
+ return ++m_cRefs;
+}
+
+/**************************************************************************
+
+ CContainer::Release()
+
+**************************************************************************/
+
+ULONG CContainer::Release(void)
+{
+ if (--m_cRefs)
+ return m_cRefs;
+
+ delete this;
+ return 0;
+}
+
+/**************************************************************************
+
+ CContainer::SaveObject()
+
+**************************************************************************/
+
+HRESULT CContainer::SaveObject()
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetMoniker()
+
+**************************************************************************/
+
+HRESULT CContainer::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER * ppMk)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetContainer()
+
+**************************************************************************/
+
+HRESULT CContainer::GetContainer(LPOLECONTAINER * ppContainer)
+{
+ return E_NOINTERFACE;
+}
+
+/**************************************************************************
+
+ CContainer::ShowObject()
+
+**************************************************************************/
+
+HRESULT CContainer::ShowObject()
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnShowWindow()
+
+**************************************************************************/
+
+HRESULT CContainer::OnShowWindow(BOOL fShow)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::RequestNewObjectLayout()
+
+**************************************************************************/
+
+HRESULT CContainer::RequestNewObjectLayout()
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IOleWindow
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::GetWindow()
+
+**************************************************************************/
+
+HRESULT CContainer::GetWindow(HWND * lphwnd)
+{
+ if (!IsWindow(m_hwnd))
+ return S_FALSE;
+
+ *lphwnd = m_hwnd;
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::ContextSensitiveHelp()
+
+**************************************************************************/
+
+HRESULT CContainer::ContextSensitiveHelp(BOOL fEnterMode)
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IOleInPlaceSite
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::CanInPlaceActivate()
+
+**************************************************************************/
+
+HRESULT CContainer::CanInPlaceActivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnInPlaceActivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnInPlaceActivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnUIActivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnUIActivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::GetWindowContext()
+
+**************************************************************************/
+
+HRESULT CContainer::GetWindowContext (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppIIPUIWin,
+ LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
+{
+ *ppFrame = (IOleInPlaceFrame *)this;
+ *ppIIPUIWin = NULL;
+
+ RECT rect;
+ GetClientRect(m_hwnd, &rect);
+ lprcPosRect->left = 0;
+ lprcPosRect->top = 0;
+ lprcPosRect->right = rect.right;
+ lprcPosRect->bottom = rect.bottom;
+
+ CopyRect(lprcClipRect, lprcPosRect);
+
+ lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);
+ lpFrameInfo->fMDIApp = FALSE;
+ lpFrameInfo->hwndFrame = m_hwnd;
+ lpFrameInfo->haccel = 0;
+ lpFrameInfo->cAccelEntries = 0;
+
+ (*ppFrame)->AddRef();
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::Scroll()
+
+**************************************************************************/
+
+HRESULT CContainer::Scroll(SIZE scrollExtent)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::OnUIDeactivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnUIDeactivate(BOOL fUndoable)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnInPlaceDeactivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnInPlaceDeactivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::DiscardUndoState()
+
+**************************************************************************/
+
+HRESULT CContainer::DiscardUndoState(void)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::DeactivateAndUndo()
+
+**************************************************************************/
+
+HRESULT CContainer::DeactivateAndUndo(void)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::OnPosRectChange()
+
+**************************************************************************/
+
+HRESULT CContainer::OnPosRectChange(LPCRECT lprcPosRect)
+{
+ HRESULT hr;
+ IOleInPlaceObject *pipo;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (SUCCEEDED(hr))
+ {
+ pipo->SetObjectRects(lprcPosRect, lprcPosRect);
+ pipo->Release();
+ }
+ return(S_OK);
+}
+
+// ***********************************************************************
+// IOleInPlaceUIWindow
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::GetBorder()
+
+**************************************************************************/
+
+HRESULT CContainer::GetBorder(LPRECT lprectBorder)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::RequestBorderSpace()
+
+**************************************************************************/
+
+HRESULT CContainer::RequestBorderSpace(LPCBORDERWIDTHS lpborderwidths)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetBorderSpace()
+
+**************************************************************************/
+
+HRESULT CContainer::SetBorderSpace(LPCBORDERWIDTHS lpborderwidths)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetActiveObject()
+
+**************************************************************************/
+
+HRESULT CContainer::SetActiveObject(IOleInPlaceActiveObject * pActiveObject, LPCOLESTR lpszObjName)
+{
+ return S_OK;
+}
+
+// ***********************************************************************
+// IOleInPlaceFrame
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::InsertMenus()
+
+**************************************************************************/
+
+HRESULT CContainer::InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetMenu()
+
+**************************************************************************/
+
+HRESULT CContainer::SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::RemoveMenus()
+
+**************************************************************************/
+
+HRESULT CContainer::RemoveMenus(HMENU hmenuShared)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetStatusText()
+
+**************************************************************************/
+
+HRESULT CContainer::SetStatusText(LPCOLESTR pszStatusText)
+{
+ char status[MAX_PATH]; // ansi version of status text
+
+ if (NULL == pszStatusText)
+ return E_POINTER;
+
+ WideCharToMultiByte(CP_ACP, 0, pszStatusText, -1, status, MAX_PATH, NULL, NULL);
+
+ if (IsWindow(m_hwndStatus))
+ SendMessage(m_hwndStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)status);
+
+ return (S_OK);
+}
+
+/**************************************************************************
+
+ CContainer::EnableModeless()
+
+**************************************************************************/
+
+HRESULT CContainer::EnableModeless(BOOL fEnable)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::TranslateAccelerator()
+
+**************************************************************************/
+
+HRESULT CContainer::TranslateAccelerator(LPMSG lpmsg, WORD wID)
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IOleControlSite
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::OnControlInfoChanged()
+
+**************************************************************************/
+
+HRESULT CContainer::OnControlInfoChanged()
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::LockInPlaceActive()
+
+**************************************************************************/
+
+HRESULT CContainer::LockInPlaceActive(BOOL fLock)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetExtendedControl()
+
+**************************************************************************/
+
+HRESULT CContainer::GetExtendedControl(IDispatch **ppDisp)
+{
+ if (ppDisp == NULL)
+ return E_INVALIDARG;
+
+ *ppDisp = (IDispatch *)this;
+ (*ppDisp)->AddRef();
+
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::TransformCoords()
+
+**************************************************************************/
+
+HRESULT CContainer::TransformCoords(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::TranslateAccelerator()
+
+**************************************************************************/
+
+HRESULT CContainer::TranslateAccelerator(LPMSG pMsg, DWORD grfModifiers)
+{
+ return S_FALSE;
+}
+
+/**************************************************************************
+
+ CContainer::OnFocus()
+
+**************************************************************************/
+
+HRESULT CContainer::OnFocus(BOOL fGotFocus)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::ShowPropertyFrame()
+
+**************************************************************************/
+
+HRESULT CContainer::ShowPropertyFrame(void)
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IDispatch
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::GetIDsOfNames()
+
+**************************************************************************/
+
+HRESULT CContainer::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+}
+
+/**************************************************************************
+
+ CContainer::GetTypeInfo()
+
+**************************************************************************/
+
+HRESULT CContainer::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetTypeInfoCount()
+
+**************************************************************************/
+
+HRESULT CContainer::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::Invoke()
+
+**************************************************************************/
+
+HRESULT CContainer::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ if(dispid==DISPID_AMBIENT_DLCONTROL)
+ {
+ m_restrictAlreadySet=1;
+ if(!m_allowScripts)
+ {
+ pvarResult->vt = VT_I4;
+ pvarResult->lVal = DLCTL_DLIMAGES|DLCTL_NO_SCRIPTS|DLCTL_NO_DLACTIVEXCTLS|DLCTL_NO_RUNACTIVEXCTLS|DLCTL_NO_JAVA;
+ return S_OK;
+ }
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+// ***********************************************************************
+// Public (non-interface) Methods
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::add()
+
+**************************************************************************/
+
+void CContainer::add(BSTR bstrClsid)
+{
+ CLSID clsid; // CLSID of the control object
+ HRESULT hr; // return code
+
+ CLSIDFromString(bstrClsid, &clsid);
+ CoCreateInstance(clsid,
+ NULL,
+ CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
+ IID_IUnknown,
+ (PVOID *)&m_punk);
+
+ if (!m_punk)
+ return;
+
+ IOleObject *pioo;
+ hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (FAILED(hr))
+ return;
+
+ pioo->SetClientSite(this);
+ pioo->Release();
+
+ IPersistStreamInit *ppsi;
+ hr = m_punk->QueryInterface(IID_IPersistStreamInit, (PVOID *)&ppsi);
+ if (SUCCEEDED(hr))
+ {
+ ppsi->InitNew();
+ ppsi->Release();
+ }
+}
+
+/**************************************************************************
+
+ CContainer::remove()
+
+**************************************************************************/
+
+void CContainer::remove()
+{
+ if (!m_punk)
+ return;
+
+ HRESULT hr;
+ IOleObject *pioo;
+ IOleInPlaceObject *pipo;
+
+ hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (SUCCEEDED(hr))
+ {
+ pioo->Close(OLECLOSE_NOSAVE);
+ pioo->SetClientSite(NULL);
+ pioo->Release();
+ }
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (SUCCEEDED(hr))
+ {
+ pipo->UIDeactivate();
+ pipo->InPlaceDeactivate();
+ pipo->Release();
+ }
+
+ m_punk->Release();
+ m_punk = NULL;
+}
+
+/**************************************************************************
+
+ CContainer::setParent()
+
+**************************************************************************/
+
+void CContainer::setParent(HWND hwndParent)
+{
+ m_hwnd = hwndParent;
+}
+
+/**************************************************************************
+
+ CContainer::setLocation()
+
+**************************************************************************/
+
+void CContainer::setLocation(int x, int y, int width, int height)
+{
+ RECT rc;
+ ::SetRect(&rc, x, y, x + width, y + height);
+
+ if (!m_punk) return;
+
+ HRESULT hr;
+ IOleInPlaceObject *pipo;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (FAILED(hr))
+ return;
+
+ pipo->SetObjectRects(&rc, &rc);
+ pipo->Release();
+}
+
+BOOL CContainer::SetRect(RECT *prc)
+{
+ HRESULT hr;
+ IOleInPlaceObject *pipo;
+
+
+ if (!m_punk) return FALSE;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (SUCCEEDED(hr))
+ {
+ hr = pipo->SetObjectRects(prc, prc);
+ pipo->Release();
+ }
+ return SUCCEEDED(hr);
+}
+/**************************************************************************
+
+ CContainer::setVisible()
+
+**************************************************************************/
+
+void CContainer::setVisible(BOOL fVisible)
+{
+ if (!m_punk)
+ return;
+
+ HRESULT hr;
+ IOleObject *pioo;
+
+ hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (FAILED(hr))
+ return;
+
+ if (fVisible)
+ {
+
+ pioo->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, m_hwnd, NULL);
+ pioo->DoVerb(OLEIVERB_SHOW, NULL, this, 0, m_hwnd, NULL);
+ }
+ else
+ pioo->DoVerb(OLEIVERB_HIDE, NULL, this, 0, m_hwnd, NULL);
+
+ pioo->Release();
+}
+
+/**************************************************************************
+
+ CContainer::setFocus()
+
+**************************************************************************/
+
+void CContainer::setFocus(BOOL fFocus)
+{
+ if (!m_punk)
+ return;
+
+ if (fFocus)
+ {
+ IOleObject *pioo;
+ HRESULT hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (FAILED(hr))
+ return;
+
+ pioo->DoVerb(OLEIVERB_UIACTIVATE, NULL, this, 0, m_hwnd, NULL);
+ pioo->Release();
+ }
+}
+
+/**************************************************************************
+
+ CContainer::setStatusWindow()
+
+**************************************************************************/
+
+void CContainer::setStatusWindow(HWND hwndStatus)
+{
+ m_hwndStatus = hwndStatus;
+}
+
+/**************************************************************************
+
+ CContainer::translateKey()
+
+**************************************************************************/
+
+void CContainer::translateKey(MSG msg)
+{
+ if (!m_punk)
+ return;
+
+ HRESULT hr;
+ IOleInPlaceActiveObject *pao;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceActiveObject, (PVOID *)&pao);
+ if (FAILED(hr))
+ return;
+
+ pao->TranslateAccelerator(&msg);
+ pao->Release();
+}
+
+/**************************************************************************
+
+ * CContainer::getDispatch()
+
+**************************************************************************/
+
+IDispatch * CContainer::getDispatch()
+{
+ if (!m_punk)
+ return NULL;
+
+ IDispatch *pdisp;
+ m_punk->QueryInterface(IID_IDispatch, (PVOID *)&pdisp);
+ return pdisp;
+}
+
+/**************************************************************************
+
+ * CContainer::getUnknown()
+
+**************************************************************************/
+
+IUnknown * CContainer::getUnknown()
+{
+ if (!m_punk)
+ return NULL;
+
+ m_punk->AddRef();
+ return m_punk;
+}
+
+void CContainer::setScrollbars(int scroll)
+{
+ m_scrollbars=scroll;
+}
+
+// ***********************************************************************
+// IDocHostUIHandler
+// ***********************************************************************
+
+HRESULT CContainer::ShowContextMenu(DWORD dwID, POINT __RPC_FAR *ppt, IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CContainer::GetHostInfo(DOCHOSTUIINFO __RPC_FAR *pInfo)
+{
+ pInfo->dwFlags=0x00200000|(m_scrollbars?0:8);
+ return S_OK;
+}
+
+HRESULT CContainer::ShowUI(DWORD dwID, IOleInPlaceActiveObject __RPC_FAR *pActiveObject, IOleCommandTarget __RPC_FAR *pCommandTarget, IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CContainer::HideUI(void)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::UpdateUI(void)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::OnDocWindowActivate(BOOL fActivate)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::OnFrameWindowActivate(BOOL fActivate)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::TranslateAccelerator(LPMSG lpMsg, const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::GetOptionKeyPath(LPOLESTR __RPC_FAR *pchKey, DWORD dw)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::GetDropTarget(IDropTarget __RPC_FAR *pDropTarget, IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::GetExternal(IDispatch __RPC_FAR *__RPC_FAR *ppDispatch)
+{
+ *ppDispatch = winampExternal;
+ return S_OK; //E_NOTIMPL;
+}
+
+
+HRESULT CContainer::TranslateUrl(DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn, OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::FilterDataObject(IDataObject __RPC_FAR *pDO, IDataObject __RPC_FAR *__RPC_FAR *ppDORet)
+{
+ return E_NOTIMPL;
+}
+
diff --git a/Src/Plugins/Library/ml_local/contnr.h b/Src/Plugins/Library/ml_local/contnr.h
new file mode 100644
index 00000000..aa969591
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/contnr.h
@@ -0,0 +1,153 @@
+/**************************************************************************
+ THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ PARTICULAR PURPOSE.
+
+ Copyright 1998 Microsoft Corporation. All Rights Reserved.
+**************************************************************************/
+
+/**************************************************************************
+
+ File: contnr.h
+
+ Description: This file contains the complete class specification of an
+ ActiveX control container. This purpose of this container
+ is to test a single control being hosted.
+
+**************************************************************************/
+
+#ifndef _CONTAINER_H_
+#define _CONTAINER_H_
+
+/**************************************************************************
+ #include statements
+**************************************************************************/
+
+#include <ocidl.h>
+#include <mshtmhst.h>
+
+/**************************************************************************
+ class definitions
+**************************************************************************/
+
+class CContainer : public IOleClientSite,
+ public IOleInPlaceSite,
+ public IOleInPlaceFrame,
+ public IOleControlSite,
+ public IDocHostUIHandler,
+ public IDispatch
+{
+ private:
+ ULONG m_cRefs; // ref count
+ HWND m_hwnd; // window handle of the container
+ HWND m_hwndStatus; // status window handle
+ IUnknown *m_punk; // IUnknown of contained object
+ int m_scrollbars;
+
+ int m_allowScripts, m_restrictAlreadySet;
+
+ public:
+ CContainer();
+ ~CContainer();
+
+ public:
+ // *** IUnknown Methods ***
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IOleClientSite Methods ***
+ STDMETHOD (SaveObject)();
+ STDMETHOD (GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER *ppMk);
+ STDMETHOD (GetContainer)(LPOLECONTAINER *ppContainer);
+ STDMETHOD (ShowObject)();
+ STDMETHOD (OnShowWindow)(BOOL fShow);
+ STDMETHOD (RequestNewObjectLayout)();
+
+ // *** IOleWindow Methods ***
+ STDMETHOD (GetWindow) (HWND * phwnd);
+ STDMETHOD (ContextSensitiveHelp) (BOOL fEnterMode);
+
+ // *** IOleInPlaceSite Methods ***
+ STDMETHOD (CanInPlaceActivate) (void);
+ STDMETHOD (OnInPlaceActivate) (void);
+ STDMETHOD (OnUIActivate) (void);
+ STDMETHOD (GetWindowContext) (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo);
+ STDMETHOD (Scroll) (SIZE scrollExtent);
+ STDMETHOD (OnUIDeactivate) (BOOL fUndoable);
+ STDMETHOD (OnInPlaceDeactivate) (void);
+ STDMETHOD (DiscardUndoState) (void);
+ STDMETHOD (DeactivateAndUndo) (void);
+ STDMETHOD (OnPosRectChange) (LPCRECT lprcPosRect);
+
+ // *** IOleInPlaceUIWindow Methods ***
+ STDMETHOD (GetBorder)(LPRECT lprectBorder);
+ STDMETHOD (RequestBorderSpace)(LPCBORDERWIDTHS lpborderwidths);
+ STDMETHOD (SetBorderSpace)(LPCBORDERWIDTHS lpborderwidths);
+ STDMETHOD (SetActiveObject)(IOleInPlaceActiveObject * pActiveObject,
+ LPCOLESTR lpszObjName);
+
+ // *** IOleInPlaceFrame Methods ***
+ STDMETHOD (InsertMenus)(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
+ STDMETHOD (SetMenu)(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject);
+ STDMETHOD (RemoveMenus)(HMENU hmenuShared);
+ STDMETHOD (SetStatusText)(LPCOLESTR pszStatusText);
+ STDMETHOD (EnableModeless)(BOOL fEnable);
+ STDMETHOD (TranslateAccelerator)(LPMSG lpmsg, WORD wID);
+
+ // *** IOleControlSite Methods ***
+ STDMETHOD (OnControlInfoChanged)(void);
+ STDMETHOD (LockInPlaceActive)(BOOL fLock);
+ STDMETHOD (GetExtendedControl)(IDispatch **ppDisp);
+ STDMETHOD (TransformCoords)(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags);
+ STDMETHOD (TranslateAccelerator)(LPMSG pMsg, DWORD grfModifiers);
+ STDMETHOD (OnFocus)(BOOL fGotFocus);
+ STDMETHOD (ShowPropertyFrame)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ // *** IDocHostUIHandler Methods ***
+ STDMETHOD (ShowContextMenu)(DWORD dwID, POINT __RPC_FAR *ppt, IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved);
+ STDMETHOD (GetHostInfo)(DOCHOSTUIINFO __RPC_FAR *pInfo);
+ STDMETHOD (ShowUI)(DWORD dwID, IOleInPlaceActiveObject __RPC_FAR *pActiveObject, IOleCommandTarget __RPC_FAR *pCommandTarget, IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc);
+ STDMETHOD (HideUI)(void);
+ STDMETHOD (UpdateUI)(void);
+ STDMETHOD (OnDocWindowActivate)(BOOL fActivate);
+ STDMETHOD (OnFrameWindowActivate)(BOOL fActivate);
+ STDMETHOD (ResizeBorder)(LPCRECT prcBorder, IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow);
+ STDMETHOD (TranslateAccelerator)(LPMSG lpMsg, const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID);
+ STDMETHOD (GetOptionKeyPath)(LPOLESTR __RPC_FAR *pchKey, DWORD dw);
+ STDMETHOD (GetDropTarget)(IDropTarget __RPC_FAR *pDropTarget, IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget);
+ STDMETHOD (GetExternal)(IDispatch __RPC_FAR *__RPC_FAR *ppDispatch);
+ STDMETHOD (TranslateUrl)(DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn, OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut);
+ STDMETHOD (FilterDataObject)(IDataObject __RPC_FAR *pDO, IDataObject __RPC_FAR *__RPC_FAR *ppDORet);
+
+ public:
+ void add(BSTR clsid);
+ void remove();
+ void setParent(HWND hwndParent);
+ void setLocation(int x, int y, int width, int height);
+
+ void setVisible(BOOL fVisible);
+ void setFocus(BOOL fFocus);
+ void setStatusWindow(HWND hwndStatus);
+ void translateKey(MSG msg);
+ void setScrollbars(int scroll);
+
+ void setAllowScripts(int s) { m_allowScripts=s; }
+ int getAllowScripts() { return m_allowScripts; }
+
+ int getRestrictAlreadySet() { return m_restrictAlreadySet; }
+
+ IDispatch *getDispatch();
+ IUnknown * getUnknown();
+
+ BOOL SetRect(RECT *prc);
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/db.h b/Src/Plugins/Library/ml_local/db.h
new file mode 100644
index 00000000..76b2eca0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/db.h
@@ -0,0 +1,64 @@
+#ifndef NULLSOT_LOCALMEDIA_DB_H
+#define NULLSOT_LOCALMEDIA_DB_H
+
+#define MAINTABLE_ID_FILENAME 0
+#define MAINTABLE_ID_TITLE 1
+#define MAINTABLE_ID_ARTIST 2
+#define MAINTABLE_ID_ALBUM 3
+#define MAINTABLE_ID_YEAR 4
+#define MAINTABLE_ID_GENRE 5
+#define MAINTABLE_ID_COMMENT 6
+#define MAINTABLE_ID_TRACKNB 7
+#define MAINTABLE_ID_LENGTH 8 //in seconds
+#define MAINTABLE_ID_TYPE 9 //0=audio, 1=video
+#define MAINTABLE_ID_LASTUPDTIME 10 // last time (seconds since 1970) of db update of this item
+#define MAINTABLE_ID_LASTPLAY 11 // last time (seconds since 1970) of last play
+#define MAINTABLE_ID_RATING 12
+#define MAINTABLE_ID_GRACENOTE_ID 14
+#define MAINTABLE_ID_PLAYCOUNT 15 // play count
+#define MAINTABLE_ID_FILETIME 16 // file time
+#define MAINTABLE_ID_FILESIZE 17 // file size, kilobytes
+#define MAINTABLE_ID_BITRATE 18 // file bitratea, kbps
+
+#include "../nde/nde.h"
+#include <map>
+#include <string>
+
+// DataBase manipulations
+class DB
+{
+// construcotrs
+public:
+ DB();
+ ~DB();
+
+// methods
+public:
+ int Open();
+ int Close();
+ int Nuke();
+ int AddColumn(char* metaKey, int type); // returns index of the new column or -1 on error
+private:
+ BOOL Discover(void);
+ void ClearMap(void);
+
+
+// properties
+public:
+ void SetTableDir(const char* tableDir);
+ const char* GetTableDir();
+ int GetColumnsCount();
+ int GetColumnId(char *metaKey); // returns index of the column or -1 if can't find
+
+// fields
+private:
+ char * tableDir;
+ Database db;
+ Table *table;
+ Scanner *sc;
+
+ std::map< std::string, int> columnsMap;
+
+};
+
+#endif //NULLSOT_LOCALMEDIA_DB_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/db_error.txt b/Src/Plugins/Library/ml_local/db_error.txt
new file mode 100644
index 00000000..055cc656
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/db_error.txt
@@ -0,0 +1,6 @@
+
+There was an error reading the local database. This could be due to the database becoming corrupted or having been removed.
+
+If the database files (main.dat and main.idx) have become corrupted then unless you have a back up, you will need to reset the database and then re-add any media files required back into the database.
+
+By resetting the database, any custom ratings and play counts not already stored in the media file(s) will be lost which also will have happened if the database was corrupted. \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/editinfo.cpp b/Src/Plugins/Library/ml_local/editinfo.cpp
new file mode 100644
index 00000000..4242cbfe
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/editinfo.cpp
@@ -0,0 +1,816 @@
+#include "main.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/ListView.h"
+#include "../Winamp/strutil.h"
+#include "resource.h"
+#include <time.h>
+#define MAKESAFE(x) ((x)?(x):L"")
+
+extern W_ListView resultlist;
+extern itemRecordListW itemCache;
+volatile int no_lv_update = 0;
+//////////////// info editor fun
+
+
+// must be one of OUR item records (since we free it)
+static void UpdateItemRecordFromDB(itemRecordW *song)
+{
+ // look in the database for the updated song info
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, song->filename))
+ {
+ // now we can actually update the itemCache itemRecordW from the value in the db
+ itemRecordW item = {0};
+ itemRecordListW obj = {&item, 0, 1};
+ ScannerRefToObjCacheNFNW(s, &obj, false);
+
+ item.filename = song->filename;
+ song->filename = NULL; // set to NULL so freeRecord doesn't delete the filename string
+
+ freeRecord(song); // delete old item
+ *song = item; // replace with our new (BETTER :) item
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+}
+
+//physically update metadata in a given file
+int updateFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *data)
+{
+ extendedFileInfoStructW efis = {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? wcslen(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+static int m_upd_nb, m_stopped, m_upd_nb_all, m_upd_nb_cur;
+static nde_scanner_t m_scanner;
+
+// sets part and parts to -1 or 0 on fail/missing (e.g. parts will be -1 on "1", but 0 on "1/")
+void ParseIntSlashInt(wchar_t *string, int *part, int *parts)
+{
+ *part = -1;
+ *parts = -1;
+
+ if (string && string[0])
+ {
+ *part = _wtoi(string);
+ while (string && *string && *string != '/')
+ {
+ string++;
+ }
+ if (string && *string == '/')
+ {
+ string++;
+ *parts = _wtoi(string);
+ }
+ }
+}
+
+// TODO: benski> can this copy-and-paste code be factored?
+static INT_PTR CALLBACK updateFiles_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowTextW(hwndDlg, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_UPDATING_FILES));
+ SetWindowLong(GetDlgItem(hwndDlg, IDC_STATUS), GWL_STYLE, (GetWindowLong(GetDlgItem(hwndDlg, IDC_STATUS), GWL_STYLE)&~SS_CENTER) | SS_LEFTNOWORDWRAP);
+ SetTimer(hwndDlg, 0x123, 30, NULL);
+ m_upd_nb = 0;
+ EnterCriticalSection(&g_db_cs);
+ m_scanner = NDE_Table_CreateScanner(g_table);
+ m_stopped = 0;
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, m_upd_nb_all));
+ m_upd_nb_cur = 0;
+
+ // show window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"scan_x", -1), g_config->ReadInt(L"scan_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+
+ break;
+ }
+ case WM_TIMER:
+ if (wParam == 0x123 && !m_stopped)
+ {
+ unsigned int start_t = GetTickCount();
+again:
+ {
+ int l = resultlist.GetCount();
+
+ while (m_upd_nb < l && !resultlist.GetSelected(m_upd_nb))
+ m_upd_nb++;
+
+ if (m_upd_nb >= l)
+ {
+ //done
+ EndDialog(hwndDlg, 1);
+ break;
+ }
+
+ int i = m_upd_nb++;
+
+ itemRecordW *song = itemCache.Items + i;
+ HWND hwndParent = GetParent(hwndDlg);
+
+ wchar_t stattmp[512] = {0};
+ wchar_t *p = scanstr_backW(song->filename, L"\\", song->filename - 1) + 1;
+ wsprintfW(stattmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_UPDATING_X), p);
+ SetDlgItemTextW(hwndDlg, IDC_STATUS, stattmp);
+
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, m_upd_nb_cur, 0);
+ m_upd_nb_cur++;
+
+ int updtagz = !!IsDlgButtonChecked(hwndParent, IDC_CHECK1);
+ if (!NDE_Scanner_LocateNDEFilename(m_scanner, MAINTABLE_ID_FILENAME, FIRST_RECORD, song->filename))
+ {
+ break;
+ }
+
+ NDE_Scanner_Edit(m_scanner);
+
+#define CHECK_AND_COPY(IDCHECK, ID, field, item) if (IsDlgButtonChecked(hwndParent, IDCHECK)) {\
+ wchar_t blah[2048] = {0}; GetDlgItemTextW(hwndParent, ID, blah, 2048);\
+ if (wcscmp(MAKESAFE(item), blah))\
+ { if (blah[0]) db_setFieldStringW(m_scanner, field, blah);\
+ else db_removeField(m_scanner, field);\
+ ndestring_release(item);\
+ item = ndestring_wcsdup(blah);}}
+
+ CHECK_AND_COPY(IDC_CHECK_ARTIST, IDC_EDIT_ARTIST, MAINTABLE_ID_ARTIST, song->artist);
+ CHECK_AND_COPY(IDC_CHECK_TITLE, IDC_EDIT_TITLE, MAINTABLE_ID_TITLE, song->title);
+ CHECK_AND_COPY(IDC_CHECK_ALBUM, IDC_EDIT_ALBUM, MAINTABLE_ID_ALBUM, song->album);
+ CHECK_AND_COPY(IDC_CHECK_COMMENT, IDC_EDIT_COMMENT, MAINTABLE_ID_COMMENT, song->comment);
+ CHECK_AND_COPY(IDC_CHECK_GENRE, IDC_EDIT_GENRE, MAINTABLE_ID_GENRE, song->genre);
+ CHECK_AND_COPY(IDC_CHECK_ALBUMARTIST, IDC_EDIT_ALBUMARTIST, MAINTABLE_ID_ALBUMARTIST, song->albumartist);
+ CHECK_AND_COPY(IDC_CHECK_PUBLISHER, IDC_EDIT_PUBLISHER, MAINTABLE_ID_PUBLISHER, song->publisher);
+ CHECK_AND_COPY(IDC_CHECK_COMPOSER, IDC_EDIT_COMPOSER, MAINTABLE_ID_COMPOSER, song->composer);
+ CHECK_AND_COPY(IDC_CHECK_CATEGORY, IDC_EDIT_CATEGORY, MAINTABLE_ID_CATEGORY, song->category);
+
+#define CHECK_AND_COPY_EXTENDED(IDCHECK, ID, field, name) if (IsDlgButtonChecked(hwndParent, IDCHECK)) {\
+ wchar_t blah[2048] = {0}; GetDlgItemTextW(hwndParent, ID, blah, 2048);\
+ wchar_t *oldData = getRecordExtendedItem_fast(song, name);\
+ if (wcscmp(MAKESAFE(oldData), blah))\
+ { if (blah[0]) db_setFieldStringW(m_scanner, field, blah);\
+ else db_removeField(m_scanner, field);\
+ wchar_t *nde_blah = ndestring_wcsdup(blah);\
+ setRecordExtendedItem(song, name, nde_blah);\
+ ndestring_release(nde_blah);}}
+
+ CHECK_AND_COPY_EXTENDED(IDC_CHECK_DIRECTOR, IDC_EDIT_DIRECTOR, MAINTABLE_ID_DIRECTOR, extended_fields.director);
+ CHECK_AND_COPY_EXTENDED(IDC_CHECK_PRODUCER, IDC_EDIT_PRODUCER, MAINTABLE_ID_PRODUCER, extended_fields.producer);
+ CHECK_AND_COPY_EXTENDED(IDC_CHECK_PODCAST_CHANNEL, IDC_EDIT_PODCAST_CHANNEL, MAINTABLE_ID_PODCASTCHANNEL, extended_fields.podcastchannel);
+
+#define CHECK_AND_COPY_EXTENDED_PC(IDCHECK, field, name) \
+ wchar_t blah[2048] = {0}; StringCchPrintfW(blah, 2048, L"%d", (IsDlgButtonChecked(hwndParent, IDCHECK) == BST_CHECKED));\
+ wchar_t *oldData = getRecordExtendedItem_fast(song, name);\
+ if (wcscmp(MAKESAFE(oldData), blah))\
+ { if (blah[0]) db_setFieldInt(m_scanner, field, _wtoi(blah));\
+ else db_removeField(m_scanner, field);\
+ wchar_t *nde_blah = ndestring_wcsdup(blah);\
+ setRecordExtendedItem(song, name, nde_blah);\
+ ndestring_release(nde_blah);}
+
+ CHECK_AND_COPY_EXTENDED_PC(IDC_CHECK_PODCAST, MAINTABLE_ID_ISPODCAST, extended_fields.ispodcast);
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_TRACK))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemTextW(hwndParent, IDC_EDIT_TRACK, blah, 64);
+ int track, tracks;
+ ParseIntSlashInt(blah, &track, &tracks);
+ if (tracks <= 0) tracks = -1;
+ if (track <= 0) track = -1;
+
+ if (song->track != track || song->tracks != tracks)
+ {
+ if (track>0) db_setFieldInt(m_scanner, MAINTABLE_ID_TRACKNB, track);
+ else db_removeField(m_scanner, MAINTABLE_ID_TRACKNB);
+ if (tracks>0) db_setFieldInt(m_scanner, MAINTABLE_ID_TRACKS, tracks);
+ else db_removeField(m_scanner, MAINTABLE_ID_TRACKS);
+ song->track = track;
+ song->tracks = tracks;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_DISC))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemTextW(hwndParent, IDC_EDIT_DISC, blah, 64);
+ int disc, discs;
+ ParseIntSlashInt(blah, &disc, &discs);
+ if (discs <= 0) discs = -1;
+ if (disc <= 0) disc = -1;
+
+ if (song->disc != disc || song->discs != discs)
+ {
+ if (disc>0) db_setFieldInt(m_scanner, MAINTABLE_ID_DISC, disc);
+ else db_removeField(m_scanner, MAINTABLE_ID_DISC);
+ if (discs>0) db_setFieldInt(m_scanner, MAINTABLE_ID_DISCS, discs);
+ else db_removeField(m_scanner, MAINTABLE_ID_DISCS);
+ song->disc = disc;
+ song->discs = discs;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_YEAR))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndParent, IDC_EDIT_YEAR, blah, 64);
+ int n = _wtoi(blah);
+ if (n <= 0) n = -1;
+ if (song->year != n)
+ {
+ if (n > 0) db_setFieldInt(m_scanner, MAINTABLE_ID_YEAR, n);
+ else db_removeField(m_scanner, MAINTABLE_ID_YEAR);
+ song->year = n;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_BPM))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndParent, IDC_EDIT_BPM, blah, 64);
+ int n = _wtoi(blah);
+ if (n <= 0) n = -1;
+ if (song->bpm != n)
+ {
+ if (n > 0) db_setFieldInt(m_scanner, MAINTABLE_ID_BPM, n);
+ else db_removeField(m_scanner, MAINTABLE_ID_BPM);
+ song->bpm = n;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_RATING))
+ {
+ int n = SendDlgItemMessage(hwndParent, IDC_COMBO_RATING, CB_GETCURSEL, 0, 0), rating = -1;
+ if (n != CB_ERR && (n >= 0 && n < 5))
+ {
+ rating = 5 - n;
+ }
+ /*int n = atoi(blah);
+ if (n < 0 || n > 5) n = -1;*/
+ if (song->rating != rating)
+ {
+ if (rating > 0) db_setFieldInt(m_scanner, MAINTABLE_ID_RATING, n);
+ else db_removeField(m_scanner, MAINTABLE_ID_RATING);
+ song->rating = rating;
+ }
+ }
+
+ if (updtagz)
+ {
+ m_stopped = 1;
+retry:
+ if (updateFileInfo(song->filename, DB_FIELDNAME_title, song->title)) // if this returns 0, then this format doesnt even support extended
+ {
+ updateFileInfo(song->filename, DB_FIELDNAME_artist, song->artist);
+ updateFileInfo(song->filename, DB_FIELDNAME_album, song->album);
+ updateFileInfo(song->filename, DB_FIELDNAME_comment, song->comment);
+ updateFileInfo(song->filename, DB_FIELDNAME_genre, song->genre);
+
+ wchar_t buf[32] = {0};
+ if (song->track > 0)
+ {
+ if (song->tracks > 0)
+ wsprintfW(buf, L"%d/%d", song->track, song->tracks);
+ else
+ wsprintfW(buf, L"%d", song->track);
+ }
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_track, buf);
+
+ if (song->year > 0) wsprintfW(buf, L"%d", song->year);
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_year, buf);
+
+ if (song->disc > 0)
+ {
+ if (song->discs > 0)
+ wsprintfW(buf, L"%d/%d", song->disc, song->discs);
+ else
+ wsprintfW(buf, L"%d", song->disc);
+ }
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_disc, buf);
+
+ if (song->rating > 0) wsprintfW(buf, L"%d", song->rating);
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_rating, buf);
+
+ if (song->bpm > 0) wsprintfW(buf, L"%d", song->bpm);
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_bpm, buf);
+
+ updateFileInfo(song->filename, DB_FIELDNAME_albumartist, song->albumartist);
+ updateFileInfo(song->filename, DB_FIELDNAME_publisher, song->publisher);
+ updateFileInfo(song->filename, DB_FIELDNAME_composer, song->composer);
+ updateFileInfo(song->filename, DB_FIELDNAME_category, song->category);
+ updateFileInfo(song->filename, DB_FIELDNAME_director, getRecordExtendedItem_fast(song, extended_fields.director));
+ updateFileInfo(song->filename, DB_FIELDNAME_producer, getRecordExtendedItem_fast(song, extended_fields.producer));
+
+ if (!SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO))
+ {
+ wchar_t tmp[1024] = {0};
+ wsprintfW(tmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_ERROR_UPDATING_FILE), song->filename);
+ int ret = MessageBoxW(hwndDlg, tmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_INFO_UPDATING_ERROR), MB_RETRYCANCEL);
+ if (ret == IDRETRY) goto retry;
+ if (ret == IDCANCEL)
+ {
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ }
+ }
+ m_stopped = 0;
+ }
+
+ db_setFieldInt(m_scanner, MAINTABLE_ID_LASTUPDTIME, (int)time(NULL));
+ db_setFieldInt(m_scanner, MAINTABLE_ID_FILETIME, (int)time(NULL));
+ NDE_Scanner_Post(m_scanner);
+
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED, (size_t)song->filename, 0);
+ }
+ if (GetTickCount() - start_t < 30) goto again;
+ }
+ break;
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDCANCEL)
+ {
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case WM_DESTROY:
+ {
+ RECT scan_rect = {0};
+ GetWindowRect(hwndDlg, &scan_rect);
+ g_config->WriteInt(L"scan_x", scan_rect.left);
+ g_config->WriteInt(L"scan_y", scan_rect.top);
+
+ NDE_Table_DestroyScanner(g_table, m_scanner);
+ NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static int checkEditInfoClick(HWND hwndDlg, POINT p, int item, int check)
+{
+ RECT r = {0};
+ GetWindowRect(GetDlgItem(hwndDlg, item), &r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r.right);
+ if (PtInRect(&r, p) && !IsDlgButtonChecked(hwndDlg, check))
+ {
+ CheckDlgButton(hwndDlg, check, TRUE);
+ if (item == IDC_COMBO_RATING) SendDlgItemMessage(hwndDlg, IDC_COMBO_RATING, CB_SHOWDROPDOWN, TRUE, 0);
+ EnableWindow(GetDlgItem(hwndDlg, item), TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, item), (LPARAM)TRUE);
+ return 1;
+ }
+ return 0;
+}
+
+void getViewport(RECT *r, HWND wnd, int full, RECT *sr)
+{
+ POINT *p = NULL;
+ if (p || sr || wnd)
+ {
+ HMONITOR hm = NULL;
+
+ if (sr)
+ hm = MonitorFromRect(sr, MONITOR_DEFAULTTONEAREST);
+ else if (wnd)
+ hm = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
+ else if (p)
+ hm = MonitorFromPoint(*p, MONITOR_DEFAULTTONEAREST);
+
+ if (hm)
+ {
+ MONITORINFOEXW mi;
+ memset(&mi, 0, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+
+ if (GetMonitorInfoW(hm, &mi))
+ {
+ if (!full)
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+ return ;
+ }
+ }
+ }
+ if (full)
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics(SM_CXSCREEN);
+ r->bottom = GetSystemMetrics(SM_CYSCREEN);
+ }
+ else
+ {
+ SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0);
+ }
+}
+
+BOOL windowOffScreen(HWND hwnd, POINT pt)
+{
+ RECT r = {0}, wnd = {0}, sr = {0};
+ GetWindowRect(hwnd, &wnd);
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + (wnd.right - wnd.left);
+ sr.bottom = sr.top + (wnd.bottom - wnd.top);
+ getViewport(&r, hwnd, 0, &sr);
+ return !PtInRect(&r, pt);
+}
+
+// TODO: benski> can this copy-and-paste code be factored?
+static INT_PTR CALLBACK editInfo_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t *last_artist = NULL, *last_title = NULL, *last_album = NULL, *last_genre = NULL,
+ *last_comment = NULL, *last_albumartist = NULL, *last_composer = NULL,
+ *last_publisher = NULL, *last_ispodcast = NULL, *last_podcastchannel = NULL;
+ const wchar_t *last_category = NULL, *last_director = NULL, *last_producer = NULL;
+ int last_year = -1, last_track = -1, last_disc = -1, last_discs = -1, last_tracks = -1,
+ last_bpm = -1, last_rating = -1, disable_artist = 0, disable_title = 0,
+ disable_album = 0, disable_genre = 0, disable_year = 0, disable_track = 0,
+ disable_comment = 0, disable_disc = 0, disable_albumartist = 0, disable_composer = 0,
+ disable_publisher = 0, disable_discs = 0, disable_tracks = 0, disable_category = 0,
+ disable_director = 0, disable_producer = 0, disable_bpm = 0, disable_rating = 0,
+ disable_ispodcast = 0, disable_podcastchannel = 0, nb = 0;
+
+ if (g_config->ReadInt(L"upd_tagz", 1)) CheckDlgButton(hwndDlg, IDC_CHECK1, BST_CHECKED);
+
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
+
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605\u2605\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0,
+ (LPARAM)WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_NO_RATING));
+
+ for (int i = 0; i < resultlist.GetCount(); i++)
+ {
+ if (!resultlist.GetSelected(i)) continue;
+
+ itemRecordW *song = itemCache.Items + i;
+
+#define SAVE_LAST_STR(last, check, disable) if (!disable && check && check[0]) { if (!last) last = check; else if (wcscmp(check, last)) disable = 1; }
+#define SAVE_LAST_INT(last, check, disable) if (!disable && check > 0) { if (last == -1) last = check; else if (last != check) disable = 1; }
+
+ SAVE_LAST_STR(last_artist, song->artist, disable_artist);
+ SAVE_LAST_STR(last_title, song->title, disable_title);
+ SAVE_LAST_STR(last_album, song->album, disable_album);
+ SAVE_LAST_STR(last_comment, song->comment, disable_comment);
+ SAVE_LAST_STR(last_genre, song->genre, disable_genre);
+
+ SAVE_LAST_INT(last_year, song->year, disable_year);
+ SAVE_LAST_INT(last_track, song->track, disable_track);
+ SAVE_LAST_INT(last_tracks, song->tracks, disable_tracks);
+ SAVE_LAST_INT(last_disc, song->disc, disable_disc);
+ SAVE_LAST_INT(last_discs, song->discs, disable_discs);
+ SAVE_LAST_INT(last_rating, song->rating, disable_rating);
+ SAVE_LAST_INT(last_bpm, song->bpm, disable_bpm);
+
+ SAVE_LAST_STR(last_albumartist, song->albumartist, disable_albumartist);
+ SAVE_LAST_STR(last_composer, song->composer, disable_composer);
+ SAVE_LAST_STR(last_publisher, song->publisher, disable_publisher);
+ SAVE_LAST_STR(last_category, song->category, disable_category);
+
+#define SAVE_LAST_STR_EXTENDED(last, name, disable) if (!disable) { wchar_t *check = getRecordExtendedItem_fast(song, name); if (check && check[0]) { if (!last) last = check; else if (wcscmp(check, last)) disable = 1; }};
+
+ SAVE_LAST_STR_EXTENDED(last_director, extended_fields.director, disable_director);
+ SAVE_LAST_STR_EXTENDED(last_producer, extended_fields.producer, disable_producer);
+ SAVE_LAST_STR_EXTENDED(last_podcastchannel, extended_fields.podcastchannel, disable_podcastchannel);
+ SAVE_LAST_STR_EXTENDED(last_ispodcast, extended_fields.ispodcast, disable_ispodcast);
+ nb++;
+ }
+
+ if (!disable_artist && last_artist) SetDlgItemTextW(hwndDlg, IDC_EDIT_ARTIST, last_artist);
+ if (!disable_title && last_title) SetDlgItemTextW(hwndDlg, IDC_EDIT_TITLE, last_title);
+ if (!disable_album && last_album) SetDlgItemTextW(hwndDlg, IDC_EDIT_ALBUM, last_album);
+ if (!disable_comment && last_comment) SetDlgItemTextW(hwndDlg, IDC_EDIT_COMMENT, last_comment);
+ if (!disable_albumartist && last_albumartist) SetDlgItemTextW(hwndDlg, IDC_EDIT_ALBUMARTIST, last_albumartist);
+ if (!disable_composer && last_composer) SetDlgItemTextW(hwndDlg, IDC_EDIT_COMPOSER, last_composer);
+ if (!disable_publisher && last_publisher) SetDlgItemTextW(hwndDlg, IDC_EDIT_PUBLISHER, last_publisher);
+ if (!disable_genre && last_genre) SetDlgItemTextW(hwndDlg, IDC_EDIT_GENRE, last_genre);
+ if (!disable_category && last_category) SetDlgItemTextW(hwndDlg, IDC_EDIT_CATEGORY, last_category);
+ if (!disable_director && last_director) SetDlgItemTextW(hwndDlg, IDC_EDIT_DIRECTOR, last_director);
+ if (!disable_producer && last_producer) SetDlgItemTextW(hwndDlg, IDC_EDIT_PRODUCER, last_producer);
+ if (!disable_podcastchannel && last_podcastchannel) SetDlgItemTextW(hwndDlg, IDC_EDIT_PODCAST_CHANNEL, last_podcastchannel);
+ if (!disable_ispodcast && last_ispodcast)
+ {
+ CheckDlgButton(hwndDlg, IDC_CHECK_PODCAST, (_wtoi(last_ispodcast) == 1 ? BST_CHECKED : BST_UNCHECKED));
+ }
+ if (!disable_year && last_year > 0)
+ {
+ wchar_t tmp[64] = {0};
+ wsprintfW(tmp, L"%d", last_year);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_YEAR, tmp);
+ }
+ if (!disable_bpm && last_bpm > 0)
+ {
+ wchar_t tmp[64] = {0};
+ wsprintfW(tmp, L"%d", last_bpm);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_BPM, tmp);
+ }
+ if (!disable_rating)
+ {
+ if (last_rating > 0 && last_rating <= 5)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_COMBO_RATING, CB_SETCURSEL, 5 - last_rating, 0);
+ }
+ else SendDlgItemMessage(hwndDlg, IDC_COMBO_RATING, CB_SETCURSEL, 5, 0);
+ }
+ if (!disable_track && last_track > 0 && !disable_tracks)
+ {
+ wchar_t tmp[64] = {0};
+ if (!disable_tracks && last_tracks > 0)
+ wsprintfW(tmp, L"%d/%d", last_track, last_tracks);
+ else
+ wsprintfW(tmp, L"%d", last_track);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_TRACK, tmp);
+ }
+ if (!disable_disc && last_disc > 0
+ && !disable_discs)
+ {
+ wchar_t tmp[64] = {0};
+ if (!disable_discs && last_discs > 0)
+ wsprintfW(tmp, L"%d/%d", last_disc, last_discs);
+ else
+ wsprintfW(tmp, L"%d", last_disc);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_DISC, tmp);
+ }
+ wchar_t tmp[512] = {0};
+ wsprintfW(tmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), (nb==1?IDS_X_ITEM_SELECTED:IDS_X_ITEMS_SELECTED)), nb);
+ SetDlgItemTextW(hwndDlg, IDC_HEADER, tmp);
+ m_upd_nb_all = nb;
+
+ // show edit info window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"edit_x", -1), g_config->ReadInt(L"edit_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ return 1;
+ case WM_COMMAND:
+#define HANDLE_CONTROL(item, check) { int enabled = IsDlgButtonChecked(hwndDlg, check); EnableWindow(GetDlgItem(hwndDlg, item), enabled); EnableWindow(GetDlgItem(hwndDlg, IDOK), enabled); }
+ switch (LOWORD(wParam))
+ {
+ case IDC_CHECK_ARTIST: HANDLE_CONTROL(IDC_EDIT_ARTIST, IDC_CHECK_ARTIST); break;
+ case IDC_CHECK_TITLE: HANDLE_CONTROL(IDC_EDIT_TITLE, IDC_CHECK_TITLE); break;
+ case IDC_CHECK_ALBUM: HANDLE_CONTROL(IDC_EDIT_ALBUM, IDC_CHECK_ALBUM); break;
+ case IDC_CHECK_COMMENT: HANDLE_CONTROL(IDC_EDIT_COMMENT, IDC_CHECK_COMMENT); break;
+ case IDC_CHECK_ALBUMARTIST: HANDLE_CONTROL(IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST); break;
+ case IDC_CHECK_COMPOSER: HANDLE_CONTROL(IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER); break;
+ case IDC_CHECK_PUBLISHER: HANDLE_CONTROL(IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER); break;
+ case IDC_CHECK_TRACK: HANDLE_CONTROL(IDC_EDIT_TRACK, IDC_CHECK_TRACK); break;
+ case IDC_CHECK_DISC: HANDLE_CONTROL(IDC_EDIT_DISC, IDC_CHECK_DISC); break;
+ case IDC_CHECK_GENRE: HANDLE_CONTROL(IDC_EDIT_GENRE, IDC_CHECK_GENRE); break;
+ case IDC_CHECK_YEAR: HANDLE_CONTROL(IDC_EDIT_YEAR, IDC_CHECK_YEAR); break;
+ case IDC_CHECK_CATEGORY: HANDLE_CONTROL(IDC_EDIT_CATEGORY, IDC_CHECK_CATEGORY); break;
+ case IDC_CHECK_DIRECTOR: HANDLE_CONTROL(IDC_EDIT_DIRECTOR, IDC_CHECK_DIRECTOR); break;
+ case IDC_CHECK_PRODUCER: HANDLE_CONTROL(IDC_EDIT_PRODUCER, IDC_CHECK_PRODUCER); break;
+ case IDC_CHECK_PODCAST_CHANNEL: HANDLE_CONTROL(IDC_EDIT_PODCAST_CHANNEL, IDC_CHECK_PODCAST_CHANNEL); break;
+ case IDC_CHECK_BPM: HANDLE_CONTROL(IDC_EDIT_BPM, IDC_CHECK_BPM); break;
+ case IDC_CHECK_RATING: HANDLE_CONTROL(IDC_COMBO_RATING, IDC_CHECK_RATING); break;
+ case IDOK:
+ {
+ int updtagz = !!IsDlgButtonChecked(hwndDlg, IDC_CHECK1);
+ g_config->WriteInt(L"upd_tagz", updtagz);
+ int ret = WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_ADDSTUFF,
+ hwndDlg, (DLGPROC)updateFiles_dialogProc, 0);
+ ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+ if (!ret) break;
+ }
+ case IDCANCEL:
+ RECT edit_rect = {0};
+ GetWindowRect(hwndDlg, &edit_rect);
+ g_config->WriteInt(L"edit_x", edit_rect.left);
+ g_config->WriteInt(L"edit_y", edit_rect.top);
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ case WM_LBUTTONDOWN:
+ {
+ POINTS p = MAKEPOINTS(lParam);
+ POINT p2 = {p.x, p.y};
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ARTIST, IDC_CHECK_ARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TITLE, IDC_CHECK_TITLE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUM, IDC_CHECK_ALBUM)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_COMMENT, IDC_CHECK_COMMENT)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TRACK, IDC_CHECK_TRACK)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_GENRE, IDC_CHECK_GENRE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_YEAR, IDC_CHECK_YEAR)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_DISC, IDC_CHECK_DISC)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_CATEGORY, IDC_CHECK_CATEGORY)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_DIRECTOR, IDC_CHECK_DIRECTOR)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PRODUCER, IDC_CHECK_PRODUCER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PODCAST_CHANNEL, IDC_CHECK_PODCAST_CHANNEL)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_BPM, IDC_CHECK_BPM)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_COMBO_RATING, IDC_CHECK_RATING)) break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void editInfo(HWND hwndParent)
+{
+ no_lv_update++;
+ bgQuery_Stop();
+
+ WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_EDIT_INFO,
+ hwndParent, (DLGPROC)editInfo_dialogProc, 0);
+
+ EatKeyboard();
+ no_lv_update--;
+}
+
+#define REFRESHCB_NUMITEMS (WM_USER)
+#define REFRESHCB_ITERATE (WM_USER+1)
+#define REFRESHCB_FINISH (WM_USER+2)
+static bool refreshKill;
+static int RefreshMetadataThread(HANDLE handle, void *_callback, intptr_t id)
+{
+ HWND callback = (HWND)_callback;
+ int l = resultlist.GetCount();
+ PostMessage(callback, REFRESHCB_NUMITEMS, 0, resultlist.GetSelectedCount());
+ for (int i = 0; i < l; i++)
+ {
+ if (refreshKill)
+ break;
+ if (!resultlist.GetSelected(i)) continue;
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+
+ EnterCriticalSection(&g_db_cs);
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(song->filename, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+
+ addFileToDb(song->filename, TRUE, meta, guess, 0, 0, true);
+ UpdateItemRecordFromDB(song);
+
+ PostMessage(callback, REFRESHCB_ITERATE, 0, 0);
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ PostMessage(callback, REFRESHCB_FINISH, 0, 0);
+ return 0;
+}
+
+static void WriteStatus(HWND hwndDlg, int dlgId, int numFiles, int totalFiles)
+{
+ wchar_t temp[1024] = {0};
+ if (numFiles + 1 > totalFiles)
+ WASABI_API_LNGSTRINGW_BUF(IDS_FINISHED, temp, 1024);
+ else
+ StringCchPrintfW(temp, 1024, WASABI_API_LNGSTRINGW(IDS_REFRESH_MESSAGE), numFiles + 1, totalFiles);
+ SetDlgItemTextW(hwndDlg, dlgId, temp);
+}
+
+static int numFiles, totalFiles;
+static INT_PTR WINAPI RefreshDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ numFiles = 0;
+ totalFiles = 0;
+ refreshKill = false;
+ WASABI_API_THREADPOOL->RunFunction(0, RefreshMetadataThread, (void *)hwndDlg, 0, api_threadpool::FLAG_LONG_EXECUTION);
+
+ // show refresh info window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"refresh_x", -1), g_config->ReadInt(L"refresh_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_DESTROY:
+ {
+ RECT refresh_rect = {0};
+ GetWindowRect(hwndDlg, &refresh_rect);
+ g_config->WriteInt(L"refresh_x", refresh_rect.left);
+ g_config->WriteInt(L"refresh_y", refresh_rect.top);
+ }
+ break;
+ case REFRESHCB_NUMITEMS:
+ totalFiles = lParam;
+ WriteStatus(hwndDlg, IDC_REFRESHMETADATA_STATUS, numFiles, totalFiles);
+ break;
+ case REFRESHCB_ITERATE:
+ numFiles++;
+ WriteStatus(hwndDlg, IDC_REFRESHMETADATA_STATUS, numFiles, totalFiles);
+ break;
+ case REFRESHCB_FINISH:
+ EndDialog(hwndDlg, 0);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCANCEL:
+ refreshKill = true;
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+void RefreshMetadata(HWND parent)
+{
+ bgQuery_Stop();
+
+ WASABI_API_DIALOGBOXW(IDD_REFRESH_METADATA, parent, RefreshDlgProc);
+ ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+}
+
+// when the rowcache is enabled, the filename pointers should be identical
+void UpdateRating_RowCache(const wchar_t *filename, int new_rating)
+{
+ int itemcount = itemCache.Size;
+ for (int i = 0; i < itemcount; i++)
+ {
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+ if (song->filename == filename || !nde_wcsicmp_fn(song->filename,filename))
+ {
+ song->rating = new_rating;
+ ListView_RedrawItems(resultlist.getwnd(), i, i);
+ break;
+ }
+ }
+}
+
+void UpdateLocalResultsCache(const wchar_t *filename) // perhaps just a itemRecordW parm
+{
+ // Search thru the itemCache looking for the file that was changed
+ int itemcount = itemCache.Size;
+ for (int i = 0; i < itemcount; i++)
+ {
+ // TODO: linear search, yuck, look at this later
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+ if (nde_wcsicmp_fn(song->filename,filename) == 0)
+ {
+ UpdateItemRecordFromDB(song);
+ SetTimer(GetParent(resultlist.getwnd()), UPDATE_RESULT_LIST_TIMER_ID, 500, 0);
+ break;
+ }
+ }
+}
+
+void fileInfoDialogs(HWND hwndParent)
+{
+ no_lv_update++; // this might block other attempts from going thru but that's OK
+ bgQuery_Stop();
+ int l = resultlist.GetCount(), i;
+ int needref = 0;
+ for (i = 0;i < l;i++)
+ {
+ if (!resultlist.GetSelected(i)) continue;
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+
+ infoBoxParamW p;
+ p.filename = song->filename;
+ p.parent = hwndParent;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW)) break;
+
+ needref = 1;
+ UpdateItemRecordFromDB(song);
+ }
+ EatKeyboard();
+ if (needref) ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+ no_lv_update--;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/editquery.cpp b/Src/Plugins/Library/ml_local/editquery.cpp
new file mode 100644
index 00000000..e1d4ca23
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/editquery.cpp
@@ -0,0 +1,1013 @@
+#include "main.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include "editquery.h"
+#include "../nde/NDE.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "time.h"
+#include "../Agave/Language/api_language.h"
+#include "../replicant/nu/AutoChar.h"
+#define ListBox_GetTextLenW(hwndCtl, index) ((int)(DWORD)SendMessageW((hwndCtl), LB_GETTEXTLEN, (WPARAM)(int)(index), 0L))
+#define ListBox_GetTextW(hwndCtl, index, lpszBuffer) ((int)(DWORD)SendMessageW((hwndCtl), LB_GETTEXT, (WPARAM)(int)(index), (LPARAM)(LPCWSTR)(lpszBuffer)))
+
+int myatoi(wchar_t *p, int len) {
+ wchar_t *w = (wchar_t *)calloc((len+1), sizeof(wchar_t));
+ if (w)
+ {
+ wcsncpy(w, p, len);
+ w[len] = 0;
+ int a = _wtoi(w);
+ free(w);
+ return a;
+ }
+ return 0;
+}
+
+static wchar_t *monthtable[12] = {L"January", L"February", L"March", L"April", L"May", L"June", L"July", L"August", L"September", L"October", L"November", L"December" };
+INT_PTR CALLBACK editQueryDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK editDateTimeDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static wchar_t *qe_field = NULL;
+
+static GayStringW qe_curquery;
+static GayStringW qe_curexpr;
+static GayStringW qe_origquery;
+static GayStringW qe_curorigin;
+static GayStringW qe_curdate;
+
+void qe_freeStuff() {
+ qe_curquery.Set(L"");
+ qe_curquery.Compact();
+ qe_curexpr.Set(L"");
+ qe_curexpr.Compact();
+ qe_curorigin.Set(L"");
+ qe_curorigin.Compact();
+ qe_origquery.Set(L"");
+ qe_origquery.Compact();
+}
+
+static GayStringW te_ret;
+static GayStringW te_result;
+int te_cur_selected_month=0;
+
+const wchar_t *editTime(HWND wnd, const wchar_t *curfield) {
+ te_ret.Set(curfield);
+
+ TimeParse tp;
+ NDE_Time_ApplyConversion(0, curfield, &tp);
+
+ if (tp.relative_day == -2 || tp.relative_kwday != -1) {
+ wchar_t titleStr[64] = {0};
+ int r = MessageBoxW(wnd, WASABI_API_LNGSTRINGW(IDS_DATE_TIME_IS_TOO_COMPLEX),
+ WASABI_API_LNGSTRINGW_BUF(IDS_DATE_TIME_EDITOR_QUESTION,titleStr,64), MB_YESNO);
+ if (r == IDNO) {
+ return curfield;
+ }
+ }
+
+ INT_PTR r = WASABI_API_DIALOGBOXW(IDD_TIMEEDITOR, wnd, editDateTimeDialogProc);
+ if (r == IDOK) {
+ te_ret.Set(te_result.Get());
+ return te_ret.Get();
+ }
+
+ return NULL;
+}
+
+const wchar_t *editQuery(HWND wnd, const wchar_t *curquery, wchar_t *newQuery, size_t len)
+{
+ qe_field = NULL;
+
+ qe_curquery.Set(curquery);
+ qe_origquery.Set(curquery);
+
+ INT_PTR r = WASABI_API_DIALOGBOXW(IDD_EDIT_QUERY, wnd, editQueryDialogProc);
+
+ if (qe_field) free(qe_field);
+
+ if (r == IDOK)
+ {
+ lstrcpynW(newQuery, qe_curquery.Get(), len);
+ qe_freeStuff();
+ return newQuery;
+ }
+ qe_freeStuff();
+ return NULL;
+}
+
+static bool FillListProc(Record *record, Field *entry, void *context)
+{
+ HWND hwndDlg = (HWND)context;
+ SendMessage(hwndDlg, LB_ADDSTRING, 0, (LPARAM)NDE_ColumnField_GetFieldName((nde_field_t)entry));
+ return true;
+}
+
+void qe_fillFieldsList( HWND hwndDlg )
+{
+ ListBox_ResetContent( hwndDlg );
+
+ Record *cr = ( (Table *)g_table )->GetColumns(); // TODO: don't use C++ NDE API
+ if ( cr )
+ {
+ cr->WalkFields( FillListProc, (void *)hwndDlg );
+ }
+
+ int len = ListBox_GetTextLenW( hwndDlg, 0 );
+
+ if ( qe_field != NULL )
+ free( qe_field );
+
+ qe_field = (wchar_t *)calloc( ( len + 1 ), sizeof( wchar_t ) );
+ ListBox_GetTextW( hwndDlg, 0, qe_field );
+
+ qe_field[ len ] = 0;
+ ListBox_SetCurSel( hwndDlg, 0 );
+}
+
+void qe_doEnableDisable( HWND hwndDlg, int *n, int num, int how )
+{
+ for ( int i = 0; i < num; i++ )
+ EnableWindow( GetDlgItem( hwndDlg, n[ i ] ), how );
+}
+
+void qe_enableDisableItem( HWND hwndDlg, int id, int tf )
+{
+ EnableWindow( GetDlgItem( hwndDlg, id ), tf );
+}
+
+void qe_fallback( HWND hwndDlg, int disabling, int enabled )
+{
+ if ( IsDlgButtonChecked( hwndDlg, disabling ) )
+ {
+ CheckDlgButton( hwndDlg, disabling, BST_UNCHECKED );
+ CheckDlgButton( hwndDlg, enabled, BST_CHECKED );
+ }
+}
+
+int ctrls_datetime[] = {IDC_STATIC_DATETIME, IDC_EDIT_DATETIME, IDC_BUTTON_EDITDATETIME, IDC_STATIC_CURDATETIME};
+int ctrls_datetime_base[] = {IDC_STATIC_DATETIME, IDC_CHECK_ABSOLUTE, IDC_CHECK_RELATIVE};
+int ctrls_datetime_relative[] = {IDC_CHECK_SELECTIVE, IDC_CHECK_TIMEAGO, IDC_STATIC_QUERYTIME, IDC_STATIC_RESULT};
+int ctrls_datetime_relative_ago[] = {IDC_STATIC_TIMEAGO, IDC_EDIT_TIMEAGO, IDC_RADIO_TIMEAGO_Y,
+ IDC_RADIO_TIMEAGO_H, IDC_RADIO_TIMEAGO_M, IDC_RADIO_TIMEAGO_MIN, IDC_RADIO_TIMEAGO_W, IDC_RADIO_TIMEAGO_S,
+ IDC_RADIO_TIMEAGO_D};
+int ctrls_datetime_relative_ago_direction[] = {IDC_STATIC_DIRECTION, IDC_RADIO_AFTER, IDC_RADIO_BEFORE};
+int ctrls_datetime_relative_selective[] = {IDC_STATIC_SELECTIVE, IDC_STATIC_YEAR, IDC_RADIO_THISYEAR,
+ IDC_RADIO_YEAR, IDC_EDIT_YEAR, IDC_STATIC_MONTH, IDC_RADIO_THISMONTH, IDC_RADIO_MONTH, IDC_BUTTON_MONTH,
+ IDC_STATIC_DAY, IDC_RADIO_THISDAY, IDC_RADIO_DAY, IDC_EDIT_DAY, IDC_STATIC_TIME, IDC_RADIO_THISTIME,
+ IDC_RADIO_TIME, IDC_RADIO_NOON, IDC_RADIO_MIDNIGHT, IDC_BUTTON_NOW, IDC_BUTTON_PICK, IDC_DATETIMEPICKER2};
+int ctrls_datetime_absolute[] = {IDC_STATIC_ABSOLUTE, IDC_DATETIMEPICKER, IDC_DATETIMEPICKER1, IDC_STATIC_RELATIVE};
+int ctrls_string_ops[] = {IDC_RADIO_ISLIKE, IDC_RADIO_BEGINS, IDC_RADIO_ENDS, IDC_RADIO_CONTAINS};
+
+int ctrls_string[] = {IDC_STATIC_STRING, IDC_EDIT_STRING};
+
+void qe_showHideOperators(HWND hwndDlg) {
+ if (!qe_curquery.Get() || !*qe_curquery.Get()) {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_AND), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_OR), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, ID_BUTTON_SENDTOQUERY), SW_SHOW);
+ } else {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_AND), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_OR), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, ID_BUTTON_SENDTOQUERY), SW_HIDE);
+ }
+}
+
+void qe_updateResultingDate(HWND hwndDlg) {
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_EDIT_DATETIME)) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY)) {
+ time_t t = NDE_Time_ApplyConversion(0, qe_curdate.Get(), 0);
+ struct tm *ts = localtime(&t);
+ wchar_t qe_tempstr[4096] = {0};
+ if (ts)
+ wsprintfW(qe_tempstr, L"%02d/%d/%04d %02d:%02d:%02d", ts->tm_mon+1, ts->tm_mday, ts->tm_year+1900, ts->tm_hour, ts->tm_min, ts->tm_sec);
+ else
+ wcsncpy(qe_tempstr, L"DATE_OUTOFRANGE", 4096);
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_CURDATETIME, qe_tempstr);
+ } else {
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_CURDATETIME, L"N/A");
+ }
+}
+
+void te_updateResultingDate(HWND hwndDlg) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE)) {
+ time_t t = NDE_Time_ApplyConversion(0, qe_curorigin.Get(), 0);
+ struct tm *ts = localtime(&t);
+ wchar_t qe_tempstr[4096] = {0};
+ if (ts)
+ wsprintfW(qe_tempstr, L"%02d/%d/%04d %02d:%02d:%02d", ts->tm_mon+1, ts->tm_mday, ts->tm_year+1900, ts->tm_hour, ts->tm_min, ts->tm_sec);
+ else
+ wcsncpy(qe_tempstr, L"DATE_OUTOFRANGE", 4096);
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_QUERYTIME, qe_tempstr);
+ } else {
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_QUERYTIME, L"N/A");
+ }
+}
+
+void qe_enableDisable(HWND hwndDlg) {
+ if (!*qe_field) return;
+ nde_field_t field = NDE_Table_GetColumnByName(g_table, AutoChar(qe_field));
+ if (!field) return;
+ int type = NDE_ColumnField_GetDataType(field);
+
+ qe_showHideOperators(hwndDlg);
+
+ switch (type) {
+ case FIELD_DATETIME: {
+ qe_fallback(hwndDlg, IDC_RADIO_ISLIKE, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_BEGINS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_ENDS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_CONTAINS, IDC_RADIO_EQUAL);
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 0);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_STRING));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 1);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_AFTER));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BEFORE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_SINCE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_UNTIL));
+ break;
+ }
+ case FIELD_LENGTH: {
+ qe_fallback(hwndDlg, IDC_RADIO_ISLIKE, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_BEGINS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_ENDS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_CONTAINS, IDC_RADIO_EQUAL);
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 1);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_LENGTH));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_ABOVE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BELOW));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_ABOVE_OR_EQUAL));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_BELOW_OR_EQUAL));
+ break;
+ }
+ case FIELD_FILENAME:
+ case FIELD_STRING: {
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 1);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 1);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_STRING));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_ABOVE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BELOW));
+ SetDlgItemTextW(hwndDlg,IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_ABOVE_OR_EQUAL));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_BELOW_OR_EQUAL));
+ break;
+ }
+ case FIELD_INTEGER: {
+ qe_fallback(hwndDlg, IDC_RADIO_ISLIKE, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_BEGINS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_ENDS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_CONTAINS, IDC_RADIO_EQUAL);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_NUMBER));
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 1);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 0);
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_ABOVE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BELOW));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_ABOVE_OR_EQUAL));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_BELOW_OR_EQUAL));
+ break;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY)) {
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 0);
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ return;
+ }
+}
+
+void te_enableDisable(HWND hwndDlg) {
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative, sizeof(ctrls_datetime_relative)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_absolute, sizeof(ctrls_datetime_absolute)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_ABSOLUTE));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative_ago, sizeof(ctrls_datetime_relative_ago)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative_ago_direction, sizeof(ctrls_datetime_relative_ago_direction)/sizeof(int), !IsDlgButtonChecked(hwndDlg, IDC_CHECK_ABSOLUTE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative_selective, sizeof(ctrls_datetime_relative_selective)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE));
+ int origin = IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE);
+ qe_enableDisableItem(hwndDlg, IDC_EDIT_YEAR, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_YEAR));
+ qe_enableDisableItem(hwndDlg, IDC_BUTTON_MONTH, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_MONTH));
+ qe_enableDisableItem(hwndDlg, IDC_EDIT_DAY, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_DAY));
+ qe_enableDisableItem(hwndDlg, IDC_DATETIMEPICKER2, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIME));
+
+ SetDlgItemTextW(hwndDlg, IDC_CHECK_TIMEAGO, WASABI_API_LNGSTRINGW((IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE) ? IDS_OFFSET_BY : IDS_TIME_AGO)));
+ SetWindowTextW(GetDlgItem(hwndDlg, IDC_BUTTON_MONTH), monthtable[te_cur_selected_month]);
+
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_ABSOLUTE), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_RELATIVE), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_TIMEAGO), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_SELECTIVE), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_STATIC_DIRECTION), NULL, TRUE);
+}
+
+const wchar_t *te_getDateTime(HWND hwndDlg) {
+ static GayStringW str;
+ wchar_t qe_tempstr[4096] = {0};
+ str.Set(L"");
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_ABSOLUTE)) {
+ GayStringW sd;
+ SYSTEMTIME st={0,0,0,0,0,0,0,0};
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), &st);
+ if (st.wYear != 0) {
+ wsprintfW(qe_tempstr, L"%02d/%d/%02d", st.wMonth, st.wDay, st.wYear);
+ sd.Append(qe_tempstr);
+ }
+ SYSTEMTIME stt={0,0,0,0,0,0,0,0};
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), &stt);
+ if (stt.wYear != 0) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ wsprintfW(qe_tempstr, L"%02d:%02d:%02d", stt.wHour, stt.wMinute, stt.wSecond);
+ sd.Append(qe_tempstr);
+ }
+ if (sd.Get() && *sd.Get()) {
+ str.Set(sd.Get());
+ return str.Get();
+ }
+ } else if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE)) {
+ GayStringW sd;
+ int gotshit = 0;
+ int gotmonth = 0;
+ int gotthismonth = 0;
+ int gotday = 0;
+ int lgotthistime = 0;
+ int lgotthisday = 0;
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO)) {
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_TIMEAGO, NULL, TRUE);
+ wsprintfW(qe_tempstr, L"%d", v);
+ sd.Append(qe_tempstr);
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_Y)) { sd.Append(L" year"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_M)) { sd.Append(L" month"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_W)) { sd.Append(L" week"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_D)) { sd.Append(L" day"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_H)) sd.Append(L" h");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_MIN)) sd.Append(L" mn");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_S)) sd.Append(L" s");
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE)) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO) && IsDlgButtonChecked(hwndDlg, IDC_RADIO_AFTER)) sd.Append(L" after");
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO) && IsDlgButtonChecked(hwndDlg, IDC_RADIO_BEFORE)) sd.Append(L" before");
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_DAY, NULL, TRUE);
+ wsprintfW(qe_tempstr, L"%s%d", !gotmonth ? L"the " : L"", v);
+ int u = (v - ((int)((double)v/10.0)) * 10);
+ if (v < 1 || v > 31) {
+ sd.Append(L"DAY_OUTOFRANGE");
+ gotshit = 1;
+ } else {
+ sd.Append(qe_tempstr);
+ switch (u) {
+ case 1: sd.Append(L"st"); gotshit = 1; break;
+ case 2: sd.Append(L"nd"); gotshit = 1; break;
+ case 3: sd.Append(L"rd"); gotshit = 1; break;
+ default: sd.Append(L"th"); gotshit = 1; break;
+ }
+ }
+ if (!gotthismonth && IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ sd.Append(L" of this month");
+ gotthismonth = 1;
+ }
+ gotshit = 1;
+ gotday = 1;
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"now");
+ gotshit = 1;
+ } else {
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this date");
+ gotshit = 1;
+ } else {
+ if (!gotthismonth && IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this month");
+ gotthismonth = 1;
+ gotshit = 1;
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this day");
+ lgotthisday = 1;
+ gotshit = 1;
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this time");
+ lgotthistime = 1;
+ lgotthisday = 0;
+ gotshit = 1;
+ }
+ }
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (lgotthistime) {
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY))
+ sd.Append(L"on ");
+ else {
+ sd.Append(L"in ");
+ }
+ } else if (lgotthisday) sd.Append(L"of ");
+ switch (te_cur_selected_month) {
+ case 0: sd.Append(L"Jan"); gotshit = 1; gotmonth = 1; break;
+ case 1: sd.Append(L"Feb"); gotshit = 1; gotmonth = 1; break;
+ case 2: sd.Append(L"Mar"); gotshit = 1; gotmonth = 1; break;
+ case 3: sd.Append(L"Apr"); gotshit = 1; gotmonth = 1; break;
+ case 4: sd.Append(L"May"); gotshit = 1; gotmonth = 1; break;
+ case 5: sd.Append(L"Jun"); gotshit = 1; gotmonth = 1; break;
+ case 6: sd.Append(L"Jul"); gotshit = 1; gotmonth = 1; break;
+ case 7: sd.Append(L"Aug"); gotshit = 1; gotmonth = 1; break;
+ case 8: sd.Append(L"Sep"); gotshit = 1; gotmonth = 1; break;
+ case 9: sd.Append(L"Oct"); gotshit = 1; gotmonth = 1; break;
+ case 10: sd.Append(L"Nov"); gotshit = 1; gotmonth = 1; break;
+ case 11: sd.Append(L"Dec"); gotshit = 1; gotmonth = 1; break;
+ default: sd.Append(L"MONTH_OUTOFRANGE"); gotshit = 1; gotmonth = 1; break;
+ }
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY) &&
+ !IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_DAY, NULL, TRUE);
+ wsprintfW(qe_tempstr, L"%s%d", !gotmonth ? L"the " : L"", v);
+ int u = (v - ((int)((double)v/10.0)) * 10);
+ if (v < 1 || v > 31) {
+ sd.Append(L"DAY_OUTOFRANGE");
+ gotshit = 1;
+ } else {
+ sd.Append(qe_tempstr);
+ switch (u) {
+ case 1: sd.Append(L"st"); gotshit = 1; break;
+ case 2: sd.Append(L"nd"); gotshit = 1; break;
+ case 3: sd.Append(L"rd"); gotshit = 1; break;
+ default: sd.Append(L"th"); gotshit = 1; break;
+ }
+ }
+ if (!gotthismonth && IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ sd.Append(L" of this month");
+ }
+ gotthismonth = 1;
+ gotshit = 1;
+ gotday = 1;
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR)) {
+
+ if ((gotshit || gotday) && !gotmonth) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"in ");
+ } else if (gotmonth && gotday) sd.Append(L", ");
+ else if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_YEAR, NULL, TRUE);
+ if (v <= 69) v += 2000;
+ else if (v > 69 && v < 100) v += 1900;
+ if (v <= 1969 || v >= 2038)
+ wcsncpy(qe_tempstr, L"YEAR_OUTOFRANGE", 4096);
+ else
+ wsprintfW(qe_tempstr, L"%d", v);
+ sd.Append(qe_tempstr);
+ gotshit = 1;
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME)) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_NOON)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (gotshit) sd.Append(L"at ");
+ sd.Append(L"noon");
+ } else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_MIDNIGHT)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (gotshit) sd.Append(L"at ");
+ sd.Append(L"midnight");
+ } else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIME)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (gotshit) sd.Append(L"at ");
+ SYSTEMTIME stt={0,0,0,0,0,0,0,0};
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER2), &stt);
+ wsprintfW(qe_tempstr, L"%02d:%02d:%02d", stt.wHour, stt.wMinute, stt.wSecond);
+ sd.Append(qe_tempstr);
+ gotshit = 1;
+ }
+ }
+ } else {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO))
+ sd.Append(L" ago");
+ }
+ qe_curorigin.Set(sd.Get());
+ if (sd.Get() && *sd.Get()) {
+ str.Set(sd.Get());
+ return str.Get();
+ }
+ }
+ return NULL;
+}
+
+void qe_updateQuery(HWND hwndDlg) {
+ if (!*qe_field) return;
+ wchar_t qe_tempstr[4096] = {0};
+ nde_field_t field = NDE_Table_GetColumnByName(g_table, AutoChar(qe_field));
+ if (!field) return;
+ int type =NDE_ColumnField_GetDataType(field);
+
+ *qe_tempstr = NULL;
+ GayStringW str;
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_NOT))
+ str.Append(L"!(");
+ str.Append(qe_field);
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_EQUAL))
+ str.Append(L" ==");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ABOVE))
+ str.Append(L" >");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_BELOW))
+ str.Append(L" <");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ABOVEOREQUAL))
+ str.Append(L" >=");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_BELOWOREQUAL))
+ str.Append(L" <=");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISLIKE))
+ str.Append(L" like");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY))
+ str.Append(L" isempty");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_BEGINS))
+ str.Append(L" begins");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ENDS))
+ str.Append(L" ends");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_CONTAINS))
+ str.Append(L" has");
+
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY)) {
+ switch(type) {
+ case FIELD_DATETIME: {
+ str.Append(L" [");
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_DATETIME, qe_tempstr, 4096);
+ qe_curdate.Set(qe_tempstr);
+ str.Append(qe_curdate.Get());
+ str.Append(L"]");
+ }
+ break;
+ case FIELD_INTEGER: {
+ wsprintfW(qe_tempstr, L" %d", GetDlgItemInt(hwndDlg, IDC_EDIT_STRING, NULL, 1));
+ str.Append(qe_tempstr);
+ }
+ break;
+ case FIELD_FILENAME:
+ case FIELD_STRING: {
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_STRING, qe_tempstr, 4096);
+ str.Append(L" \"");
+ GayStringW escaped;
+ queryStrEscape(qe_tempstr, escaped);
+ str.Append(escaped.Get());
+ str.Append(L"\"");
+ }
+ break;
+ case FIELD_LENGTH: {
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_STRING, qe_tempstr, 4096);
+ wchar_t *z = wcschr(qe_tempstr, L':');
+ if (z) {
+ wchar_t *zz = wcschr(z+1, L':');
+ int a, b, c=0,v=0;
+ a = myatoi(qe_tempstr, z-qe_tempstr);
+ if (zz) { b = myatoi(z+1, zz-(z+1)); c = _wtoi(zz+1); v = a * 3600 + b * 60 + c; }
+ else { b = _wtoi(z+1); v = a * 60 + b; }
+
+ if (v < 60)
+ wsprintfW(qe_tempstr,L"%d",v);
+ else if (v < 60*60)
+ wsprintfW(qe_tempstr,L"%d:%02d",v/60,v%60);
+ else
+ wsprintfW(qe_tempstr,L"%d:%02d:%02d",v/60/60,(v/60)%60,v%60);
+
+ str.Append(qe_tempstr);
+ } else {
+ wsprintfW(qe_tempstr, L" %d", GetDlgItemInt(hwndDlg, IDC_EDIT_STRING, NULL, 1));
+ str.Append(qe_tempstr);
+ }
+ }
+ break;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_NOT))
+ str.Append(L")");
+
+ qe_curexpr.Set(str.Get());
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_RESULT, str.Get());
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_QUERY, qe_curquery.Get());
+
+ qe_updateResultingDate(hwndDlg);
+}
+
+void qe_update(HWND hwndDlg) {
+ qe_enableDisable(hwndDlg);
+ qe_updateQuery(hwndDlg);
+}
+
+void te_updateResult(HWND hwndDlg) {
+ const wchar_t *s = te_getDateTime(hwndDlg);
+ te_result.Set(s == NULL ? L"":s);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_RESULT, te_result.Get());
+}
+
+void te_update(HWND hwndDlg) {
+ te_enableDisable(hwndDlg);
+ te_updateResult(hwndDlg);
+}
+
+void qe_pushExpression(HWND hwndDlg, const wchar_t *op) {
+ GayStringW newq;
+ if (op && qe_curquery.Get() && *qe_curquery.Get()) {
+ newq.Append(L"(");
+ newq.Append(qe_curquery.Get());
+ newq.Append(L") ");
+ newq.Append(op);
+ newq.Append(L" (");
+ newq.Append(qe_curexpr.Get());
+ newq.Append(L")");
+ qe_curquery.Set(newq.Get());
+ } else {
+ qe_curquery.Set(qe_curexpr.Get());
+ }
+ qe_update(hwndDlg);
+}
+
+SYSTEMTIME pickedtime;
+SYSTEMTIME pickeddate;
+
+INT_PTR CALLBACK pickDateTimeDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG: {
+ return TRUE;
+ }
+ case WM_COMMAND: {
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ memset(&pickedtime, 0, sizeof(pickedtime));
+ memset(&pickeddate, 0, sizeof(pickeddate));
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), &pickeddate);
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), &pickedtime);
+ EndDialog(hwndDlg, IDOK);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+ }
+ break;
+ }
+ return FALSE;
+}
+
+INT_PTR CALLBACK editDateTimeDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+
+ switch(uMsg) {
+ case WM_INITDIALOG: {
+ const wchar_t *p = te_ret.Get();
+ while (p) {
+ if (*p != L' ') break;
+ p++;
+ }
+ int empty = (p && *p == 0);
+ TimeParse tp;
+ NDE_Time_ApplyConversion(0, te_ret.Get(), &tp);
+
+ if (!empty) {
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, tp.relative_year == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_YEAR, tp.relative_year != -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, tp.relative_month == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MONTH, tp.relative_month != -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, tp.relative_day == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_DAY, tp.relative_day != -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, tp.relative_hour == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_NOON, (tp.relative_hour == 12 && tp.relative_min == 0 && tp.relative_sec == 0) ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MIDNIGHT, (tp.relative_hour == 0 && tp.relative_min == 0 && tp.relative_sec == 0) ? BST_CHECKED : BST_UNCHECKED);
+ int timeyet = IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME);
+ timeyet |= IsDlgButtonChecked(hwndDlg, IDC_RADIO_NOON);
+ timeyet |= IsDlgButtonChecked(hwndDlg, IDC_RADIO_MIDNIGHT);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIME, !timeyet ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_CHECK_RELATIVE, tp.is_relative ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_CHECK_ABSOLUTE, !tp.is_relative ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_BEFORE, tp.offset_whence == 1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_AFTER, tp.offset_whence == 0 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_CHECK_TIMEAGO, tp.offset_used == 1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_Y, tp.offset_what == 0 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_M, tp.offset_what == 1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_W, tp.offset_what == 2 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_D, tp.offset_what == 3 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_H, tp.offset_what == 4 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_MIN, tp.offset_what == 5 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_S, tp.offset_what == 6 ? BST_CHECKED : BST_UNCHECKED);
+ if (tp.is_relative && (tp.relative_year != -1 || tp.relative_month != -1 || tp.relative_day != -1 || tp.relative_hour != -1 || tp.relative_min != -1 || tp.relative_sec != -1))
+ CheckDlgButton(hwndDlg, IDC_CHECK_SELECTIVE, BST_CHECKED);
+ else
+ CheckDlgButton(hwndDlg, IDC_CHECK_SELECTIVE, BST_UNCHECKED);
+ }
+ wchar_t t[64] = {0};
+ wsprintfW(t, L"%d", tp.offset_value);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_TIMEAGO, t);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_RESULT, te_ret.Get());
+ {
+ wchar_t qe_tempstr[4096] = {0};
+ time_t now;
+ time(&now);
+ struct tm *ts = localtime(&now);
+ wsprintfW(qe_tempstr, L"%d", tp.relative_year != -1 ? tp.relative_year : ts->tm_year+1900);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_YEAR, qe_tempstr);
+ wsprintfW(qe_tempstr, L"%d", tp.relative_day != -1 ? tp.relative_day : ts->tm_mday);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_DAY, qe_tempstr);
+ te_cur_selected_month = tp.relative_month != -1 ? tp.relative_month : ts->tm_mon;
+ }
+ if (!empty && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_NOON) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_MIDNIGHT)) {
+ SYSTEMTIME st={0};
+ st.wHour = (WORD)tp.relative_hour;
+ st.wMinute = (WORD)tp.relative_min;
+ st.wSecond = (WORD)tp.relative_sec;
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER2), GDT_VALID, &st);
+ }
+ if (!empty && !tp.is_relative) {
+ time_t o = tp.absolute_datetime;
+ struct tm *t = localtime(&o);
+ if (t) {
+ if (!tp.absolute_hasdate)
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), GDT_NONE, NULL);
+ else {
+ SYSTEMTIME st={0};
+ st.wYear = (WORD)t->tm_year;
+ st.wDay = (WORD)t->tm_mday;
+ st.wMonth = (WORD)t->tm_mon;
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), GDT_VALID, &st);
+ }
+ if (!tp.absolute_hastime)
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), GDT_NONE, NULL);
+ else {
+ SYSTEMTIME st2={0};
+ GetSystemTime(&st2);
+ st2.wHour = (WORD)t->tm_hour;
+ st2.wMinute = (WORD)t->tm_min;
+ st2.wSecond = (WORD)t->tm_sec;
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), GDT_VALID, &st2);
+ }
+ }
+ CheckDlgButton(hwndDlg, IDC_CHECK_TIMEAGO, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_W, 1);
+ SetDlgItemText(hwndDlg, IDC_EDIT_TIMEAGO, L"1");
+ CheckDlgButton(hwndDlg, IDC_RADIO_BEFORE, 1);
+ } else if (!empty) {
+ if (!IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE)) {
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, 1);
+ }
+ } else if (empty) {
+ CheckDlgButton(hwndDlg, IDC_CHECK_TIMEAGO, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_W, 1);
+ SetDlgItemText(hwndDlg, IDC_EDIT_TIMEAGO, L"1");
+ CheckDlgButton(hwndDlg, IDC_RADIO_BEFORE, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, 1);
+ CheckDlgButton(hwndDlg, IDC_CHECK_RELATIVE, 1);
+ }
+ te_enableDisable(hwndDlg);
+ SetTimer(hwndDlg, 0, 1000, NULL);
+ }
+ return 1;
+ case WM_COMMAND: {
+ switch (LOWORD(wParam)) {
+ case IDOK: EndDialog(hwndDlg, IDOK); break;
+ case IDCANCEL: EndDialog(hwndDlg, IDCANCEL); break;
+ case IDC_EDIT_TIMEAGO:
+ case IDC_EDIT_YEAR:
+ case IDC_EDIT_DAY:
+ if (HIWORD(wParam) != EN_CHANGE) break;
+ case IDC_CHECK_ABSOLUTE:
+ case IDC_CHECK_RELATIVE:
+ case IDC_CHECK_TIMEAGO:
+ case IDC_CHECK_SELECTIVE:
+ case IDC_RADIO_THISYEAR:
+ case IDC_RADIO_THISMONTH:
+ case IDC_RADIO_THISDAY:
+ case IDC_RADIO_THISTIME:
+ case IDC_RADIO_YEAR:
+ case IDC_RADIO_MONTH:
+ case IDC_RADIO_DAY:
+ case IDC_RADIO_TIME:
+ case IDC_RADIO_NOON:
+ case IDC_RADIO_MIDNIGHT:
+ case IDC_RADIO_EQUAL:
+ case IDC_RADIO_ABOVE:
+ case IDC_RADIO_ABOVEOREQUAL:
+ case IDC_RADIO_BELOW:
+ case IDC_RADIO_BELOWOREQUAL:
+ case IDC_RADIO_ISLIKE:
+ case IDC_RADIO_ISEMPTY:
+ case IDC_RADIO_BEGINS:
+ case IDC_RADIO_ENDS:
+ case IDC_RADIO_CONTAINS:
+ case IDC_CHECK_NOT:
+ case IDC_RADIO_TIMEAGO_Y:
+ case IDC_RADIO_TIMEAGO_M:
+ case IDC_RADIO_TIMEAGO_D:
+ case IDC_RADIO_TIMEAGO_H:
+ case IDC_RADIO_TIMEAGO_MIN:
+ case IDC_RADIO_TIMEAGO_S:
+ case IDC_RADIO_TIMEAGO_W:
+ case IDC_RADIO_AFTER:
+ case IDC_RADIO_BEFORE:
+ te_update(hwndDlg);
+ return 0;
+ case IDC_BUTTON_PICK: {
+ INT_PTR r = WASABI_API_DIALOGBOXW(IDD_EDIT_QUERY_PICK, hwndDlg, pickDateTimeDialogProc);
+ if (r == IDOK) {
+ if (pickeddate.wYear != 0) {
+ SetDlgItemInt(hwndDlg, IDC_EDIT_YEAR, pickeddate.wYear, 0);
+ te_cur_selected_month = pickeddate.wMonth-1;
+ SetDlgItemInt(hwndDlg, IDC_EDIT_DAY, pickeddate.wDay, 0);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_YEAR, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MONTH, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_DAY, BST_CHECKED);
+ }
+ if (pickedtime.wYear != 0) {
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER2), GDT_VALID, &pickedtime);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIME, BST_CHECKED);
+ }
+ te_update(hwndDlg);
+ }
+ }
+ break;
+ case IDC_BUTTON_NOW:
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_YEAR, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MONTH, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_DAY, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIME, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_NOON, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MIDNIGHT, BST_UNCHECKED);
+ te_update(hwndDlg);
+ return 0;
+ case IDC_BUTTON_MONTH: {
+ HMENU menu = CreatePopupMenu();
+ for (int i=0;i<12;i++)
+ AppendMenuW(menu, MF_STRING|(te_cur_selected_month==i)?MF_CHECKED:MF_UNCHECKED, i+1, monthtable[i]);
+ POINT pt;
+ GetCursorPos(&pt);
+ int r = TrackPopupMenuEx(menu, TPM_LEFTALIGN|TPM_BOTTOMALIGN|TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, pt.x, pt.y, hwndDlg, NULL);
+ if (r)
+ {
+ te_cur_selected_month = r-1;
+ te_update(hwndDlg);
+ }
+ break;
+ }
+ }
+ }
+ case WM_TIMER: {
+ te_updateResultingDate(hwndDlg);
+ return 0;
+ }
+ case WM_NOTIFY: {
+ int idCtrl = (int)wParam;
+ NMHDR *hdr = (NMHDR*)lParam;
+ switch (idCtrl) {
+ case IDC_DATETIMEPICKER:
+ case IDC_DATETIMEPICKER1:
+ case IDC_DATETIMEPICKER2:
+ if (hdr->code == DTN_DATETIMECHANGE)
+ te_update(hwndDlg);
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+INT_PTR CALLBACK editQueryDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ qe_fillFieldsList( GetDlgItem( hwndDlg, IDC_LIST_FIELDS ) );
+ SetDlgItemTextW( hwndDlg, IDC_EDIT_RESULT, qe_curquery.Get() );
+ CheckDlgButton( hwndDlg, IDC_RADIO_EQUAL, MF_CHECKED );
+ qe_update( hwndDlg );
+ SetTimer( hwndDlg, 0, 1000, NULL );
+ return 1;
+ }
+ case WM_TIMER:
+ {
+ qe_updateResultingDate( hwndDlg );
+ return 0;
+ }
+ case WM_COMMAND:
+ {
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ if ( !qe_curquery.Get() || !*qe_curquery.Get() && qe_curexpr.Get() && *qe_curexpr.Get() )
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ int r = MessageBoxW( hwndDlg, WASABI_API_LNGSTRINGW( IDS_QUERY_FIELD_IS_EMPTY ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_EMPTY_QUERY, titleStr, 32 ), MB_YESNOCANCEL );
+ if ( r == IDYES )
+ qe_curquery.Set( qe_curexpr.Get() );
+ else if ( r == IDCANCEL ) break;
+ }
+ if ( qe_origquery.Get() && *qe_origquery.Get() && qe_curquery.Get() || *qe_curquery.Get() && qe_curexpr.Get() && *qe_curexpr.Get() )
+ {
+ if ( !wcscmp( qe_curquery.Get(), qe_origquery.Get() ) )
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ int r = MessageBoxW( hwndDlg, WASABI_API_LNGSTRINGW( IDS_NO_CHANGES_MADE_TO_QUERY ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_QUERY_NOT_CHANGED, titleStr, 32 ), MB_YESNOCANCEL );
+ if ( r == IDYES )
+ qe_curquery.Set( qe_curexpr.Get() );
+ else if ( r == IDCANCEL ) break;
+ }
+ }
+ EndDialog( hwndDlg, IDOK );
+ break;
+ case IDCANCEL: EndDialog( hwndDlg, IDCANCEL ); break;
+
+ case IDC_EDIT_STRING:
+ case IDC_EDIT_DATETIME:
+ if ( HIWORD( wParam ) != EN_CHANGE ) break;
+ case IDC_RADIO_EQUAL:
+ case IDC_RADIO_ABOVE:
+ case IDC_RADIO_ABOVEOREQUAL:
+ case IDC_RADIO_BELOW:
+ case IDC_RADIO_BELOWOREQUAL:
+ case IDC_RADIO_ISLIKE:
+ case IDC_RADIO_ISEMPTY:
+ case IDC_RADIO_BEGINS:
+ case IDC_RADIO_ENDS:
+ case IDC_RADIO_CONTAINS:
+ case IDC_CHECK_NOT:
+ qe_update( hwndDlg );
+ return 0;
+ case IDC_LIST_FIELDS:
+ if ( HIWORD( wParam ) == CBN_SELCHANGE )
+ {
+ HWND wnd = GetDlgItem( hwndDlg, IDC_LIST_FIELDS );
+ int idx = ListBox_GetCurSel( wnd );
+ int len = ListBox_GetTextLenW( wnd, idx );
+ if ( qe_field != NULL ) free( qe_field );
+ qe_field = (wchar_t *)calloc( ( len + 1 ), sizeof( wchar_t ) );
+ ListBox_GetTextW( wnd, idx, qe_field );
+
+ qe_field[ len ] = 0;
+ qe_update( hwndDlg );
+ }
+ break;
+ case IDC_BUTTON_AND:
+ qe_pushExpression( hwndDlg, L"AND" );
+ break;
+ case IDC_BUTTON_OR:
+ qe_pushExpression( hwndDlg, L"OR" );
+ break;
+ case ID_BUTTON_SENDTOQUERY:
+ qe_pushExpression( hwndDlg, NULL );
+ break;
+ case IDC_BUTTON_EDITDATETIME:
+ {
+ const wchar_t *res = editTime( hwndDlg, qe_curdate.Get() );
+ if ( res != NULL )
+ {
+ SetDlgItemTextW( hwndDlg, IDC_EDIT_DATETIME, res );
+ qe_curdate.Set( res );
+ }
+ qe_update( hwndDlg );
+ break;
+ }
+ case IDC_EDIT_QUERY:
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ wchar_t qe_tempstr[ 4096 ] = { 0 };
+ GetDlgItemTextW( hwndDlg, IDC_EDIT_QUERY, qe_tempstr, 4096 );
+ qe_curquery.Set( qe_tempstr );
+ qe_showHideOperators( hwndDlg );
+ }
+ break;
+ }
+ }
+ break;
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/editquery.h b/Src/Plugins/Library/ml_local/editquery.h
new file mode 100644
index 00000000..c5e2a3c0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/editquery.h
@@ -0,0 +1,7 @@
+#ifndef _EDITQUERY_H
+#define _EDITQUERY_H
+
+const wchar_t *editQuery(HWND wnd, const wchar_t *curquery, wchar_t *newQuery, size_t len);
+extern const wchar_t *editTime(HWND wnd, const wchar_t *curfield);
+
+#endif
diff --git a/Src/Plugins/Library/ml_local/evntsink.cpp b/Src/Plugins/Library/ml_local/evntsink.cpp
new file mode 100644
index 00000000..e3301e39
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/evntsink.cpp
@@ -0,0 +1,239 @@
+/**************************************************************************
+ THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ PARTICULAR PURPOSE.
+
+ Copyright 1998 Microsoft Corporation. All Rights Reserved.
+**************************************************************************/
+
+/**************************************************************************
+
+ File: evntsink.cpp
+
+ Description: This file contains the implementation of the event sink.
+
+**************************************************************************/
+
+/**************************************************************************
+ #include statements
+**************************************************************************/
+
+#include "main.h"
+#include <windows.h>
+#include "evntsink.h"
+
+/**************************************************************************
+ function prototypes
+**************************************************************************/
+
+/**************************************************************************
+ global variables and definitions
+**************************************************************************/
+
+/**************************************************************************
+
+ CEventSink::CEventSink()
+
+**************************************************************************/
+
+CEventSink::CEventSink()
+{
+ m_cRefs = 1;
+}
+
+/**************************************************************************
+
+ CEventSink::QueryInterface()
+
+**************************************************************************/
+
+STDMETHODIMP CEventSink::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CEventSink::AddRef()
+
+**************************************************************************/
+
+ULONG CEventSink::AddRef(void)
+{
+ return ++m_cRefs;
+}
+
+/**************************************************************************
+
+ CEventSink::Release()
+
+**************************************************************************/
+
+ULONG CEventSink::Release(void)
+{
+ if (--m_cRefs)
+ return m_cRefs;
+
+ delete this;
+ return 0;
+}
+
+/**************************************************************************
+
+ CEventSink::GetIDsOfNames()
+
+**************************************************************************/
+
+HRESULT CEventSink::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+}
+
+/**************************************************************************
+
+ CEventSink::GetTypeInfo()
+
+**************************************************************************/
+
+HRESULT CEventSink::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CEventSink::GetTypeInfoCount()
+
+**************************************************************************/
+
+HRESULT CEventSink::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CEventSink::Invoke()
+
+**************************************************************************/
+
+void main_setStatusText(LPCWSTR txt)
+{
+ char dest[512];
+ dest[0]=0;
+ WideCharToMultiByte(CP_ACP,0,txt,-1,dest,sizeof(dest),NULL,NULL);
+ //SetDlgItemText(m_hwnd,IDC_STATUS,dest);
+}
+
+void main_beforeNavigate(LPCWSTR txt)
+{
+ VARIANT *blah=(VARIANT *)txt;
+ char dest[512];
+ dest[0]=0;
+ WideCharToMultiByte(CP_ACP,0,blah->bstrVal,-1,dest,sizeof(dest),NULL,NULL);
+ //SetDlgItemText(m_hwnd,IDC_QUICKSEARCH,dest);
+}
+
+HRESULT CEventSink::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ // void StatusTextChange([in] BSTR Text);
+ case 0x66:
+ main_setStatusText(pdispparams->rgvarg[0].bstrVal);
+ //m_pApp->eventStatusTextChange(pdispparams->rgvarg[0].bstrVal);
+ break;
+
+ // void ProgressChange([in] long Progress, [in] long ProgressMax);
+ case 0x6c:
+ break;
+
+ // void CommandStateChange([in] long Command, [in] VARIANT_BOOL Enable);
+ case 0x69:
+ //m_pApp->eventCommandStateChange(pdispparams->rgvarg[1].lVal, pdispparams->rgvarg[0].boolVal);
+ break;
+
+ // void DownloadBegin();
+ case 0x6a:
+ //m_pApp->eventDownloadBegin();
+ break;
+
+ // void DownloadComplete();
+ case 0x68:
+ //m_pApp->eventDownloadComplete();
+ break;
+
+ // void TitleChange([in] BSTR Text);
+ case 0x071:
+ //m_pApp->eventTitleChange(pdispparams->rgvarg[0].bstrVal);
+ break;
+
+ // void PropertyChange([in] BSTR szProperty);
+ case 0x70:
+ //m_pApp->eventPropertyChange(pdispparams->rgvarg[0].bstrVal);
+ break;
+
+ // void BeforeNavigate2([in] IDispatch* pDisp, [in] VARIANT* URL, [in] VARIANT* Flags, [in] VARIANT* TargetFrameName, [in] VARIANT* PostData, [in] VARIANT* Headers, [in, out] VARIANT_BOOL* Cancel);
+ case 0xfa:
+ main_beforeNavigate(pdispparams->rgvarg[5].bstrVal);
+ break;
+
+ // void NewWindow2([in, out] IDispatch** ppDisp, [in, out] VARIANT_BOOL* Cancel);
+ case 0xfb:
+ break;
+
+ // void NavigateComplete2([in] IDispatch* pDisp, [in] VARIANT* URL);
+ case 0xfc:
+ break;
+
+ // void DocumentComplete([in] IDispatch* pDisp, [in] VARIANT* URL);
+ case 0x0103:
+ break;
+
+ // void OnQuit();
+ case 0xfd:
+ break;
+
+ // void OnVisible([in] VARIANT_BOOL Visible);
+ case 0xfe:
+ break;
+
+ // void OnToolBar([in] VARIANT_BOOL ToolBar);
+ case 0xff:
+ break;
+
+ // void OnMenuBar([in] VARIANT_BOOL MenuBar);
+ case 0x0100:
+ break;
+
+ // void OnStatusBar([in] VARIANT_BOOL StatusBar);
+ case 0x0101:
+ break;
+
+ // void OnFullScreen([in] VARIANT_BOOL FullScreen);
+ case 0x0102:
+ break;
+
+ // void OnTheaterMode([in] VARIANT_BOOL TheaterMode);
+ case 0x0104:
+ break;
+ }
+
+ return S_OK;
+}
diff --git a/Src/Plugins/Library/ml_local/evntsink.h b/Src/Plugins/Library/ml_local/evntsink.h
new file mode 100644
index 00000000..14eb2ab9
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/evntsink.h
@@ -0,0 +1,25 @@
+#ifndef _EVNTSINK_H
+#define _EVNTSINK_H
+
+class CEventSink : public IDispatch
+{
+ private:
+ ULONG m_cRefs; // ref count
+
+ public:
+ CEventSink();
+
+ public:
+ // *** IUnknown Methods ***
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/guess.cpp b/Src/Plugins/Library/ml_local/guess.cpp
new file mode 100644
index 00000000..24686ddc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/guess.cpp
@@ -0,0 +1,122 @@
+#include <string.h>
+#include <memory.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <windows.h>
+
+wchar_t *guessTitles(const wchar_t *filename,
+ int *tracknum,
+ wchar_t **artist,
+ wchar_t **album,
+ wchar_t **title) // should free the result of this function after using artist/album/title
+{
+ *tracknum=0;
+ *artist=0;
+ *album=0;
+ *title=0;
+
+ if (!filename[0]) return 0;
+
+ wchar_t *_artist=NULL;
+ wchar_t *_album=NULL;
+
+ const wchar_t *f=filename;
+ while (f && *f) f++;
+ while (f && (f > filename) && *f != L'/' && *f != L'\\') f--;
+
+ if (f == filename) return 0;
+
+ int dirlen = f-filename;
+
+ wchar_t *fullfn = (wchar_t*)_wcsdup(filename);
+ fullfn[dirlen]=0;
+
+ wchar_t *n=fullfn+dirlen;
+ while (n >= fullfn && *n != L'/' && *n != L'\\') n--;
+ n++;
+
+ // try to guess artist and album from the dirname
+ if (!wcsstr(n,L"-")) // assume dir name is album name, artist name unknown
+ {
+ *album=n;
+ _album=n;
+ }
+ else
+ {
+ *artist=_artist=n;
+ _album=wcsstr(n,L"-")+1;
+ wchar_t *t=_album-2;
+ while (t >= n && *t == L' ') t--;
+ t[1]=0;
+
+ while (_album && *_album == L' ') _album++;
+ *album=_album;
+ }
+
+ // get filename shizit
+ wcsncpy(fullfn+dirlen+1,filename+dirlen+1,wcslen(filename) - (dirlen + 1));
+
+ n=fullfn+dirlen+1;
+ while (n && *n) n++;
+ while (n > fullfn+dirlen+1 && *n != L'.') n--;
+ if (*n == L'.') *n=0;
+ n=fullfn+dirlen+1;
+
+ while (n && *n == L' ') n++;
+
+ // detect XX. filename
+ if (wcsstr(n,L".") && _wtoi(n) && _wtoi(n) < 130)
+ {
+ wchar_t *tmp=n;
+ while (tmp && *tmp >= L'0' && *tmp <= L'9') tmp++;
+ while (tmp && *tmp == L' ') tmp++;
+ if (tmp && *tmp == L'.')
+ {
+ *tracknum=_wtoi(n);
+ n=tmp+1;
+ while (n && *n == L'.') n++;
+ while (n && *n == L' ') n++;
+ }
+ }
+
+ // detect XX- filename
+ if (!*tracknum && wcsstr(n,L"-") && _wtoi(n) && _wtoi(n) < 130)
+ {
+ wchar_t *tmp=n;
+ while (tmp && *tmp >= L'0' && *tmp <= L'9') tmp++;
+ while (tmp && *tmp == L' ') tmp++;
+ if (tmp && *tmp == L'-')
+ {
+ *tracknum=_wtoi(n);
+ n=tmp+1;
+ while (n && *n == L'-') n++;
+ while (n && *n == L' ') n++;
+ }
+ }
+
+ while (wcsstr(n,L"-"))
+ {
+ wchar_t *a=n;
+ n=wcsstr(n,L"-");
+ {
+ wchar_t *t=n-1;
+ while (t >= a && *t == L' ') t--;
+ t[1]=0;
+ }
+ *n=0;
+ n++;
+ while (n && *n == L'-') n++;
+ while (n && *n == L' ') n++;
+
+ // a is the next token.
+ if (!*tracknum && !_wcsnicmp(a,L"Track ",6) && _wtoi(a+6)) *tracknum=_wtoi(a+6);
+ else if (*artist== _artist)
+ {
+ *artist=a;
+ }
+ if (*artist != _artist && *tracknum) break;
+ }
+ *title=n;
+
+ return fullfn;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/handleMessage.cpp b/Src/Plugins/Library/ml_local/handleMessage.cpp
new file mode 100644
index 00000000..f34aac4a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/handleMessage.cpp
@@ -0,0 +1,906 @@
+#include "main.h"
+#include "ml_local.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "../replicant/nu/ns_wc.h"
+#include "../nde/nde.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../nu/AutoWideFn.h"
+#include "editquery.h"
+#include <time.h>
+
+int queryEditOther(HWND hwnd, const char *query, const char *viewname, int mode);
+
+extern int m_item_mode;
+extern wchar_t m_item_name[256];
+extern wchar_t m_item_query[1024];
+
+LRESULT IPC_GetFileRatingW(INT_PTR fileName)
+{
+ int rating = 0;
+ const wchar_t *filename = (const wchar_t *)fileName;
+ if (!filename) return 0;
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+
+ if (found)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_RATING);
+ if (f && NDE_Field_GetType(f) == FIELD_INTEGER)
+ {
+ rating = min(5, max(0, NDE_IntegerField_GetValue(f)));
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+ }
+ return rating;
+}
+
+LRESULT IPC_SetFileRatingW(INT_PTR file_rating)
+{
+ const wchar_t *filename;
+ int rating;
+ int found = 0;
+
+ file_set_ratingW *data = (file_set_ratingW*)file_rating;
+ if (!data) return 0;
+
+ filename = data->fileName;
+ rating = min(5, max(0, data->newRating));
+
+ if (!filename) return 0;
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+ if (!found)
+ {
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(filename, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ addFileToDb(filename, 0, meta, guess, 1, (int)time(NULL));
+ }
+ if (filename2[0] && !found)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename2)) found = 2;
+ }
+ if (!found)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename)) found = 1;
+ }
+ if (found)
+ {
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, rating);
+ NDE_Scanner_Post(s);
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (rating > 0)
+ {
+ wsprintfW(buf, L"%d", rating);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+ }
+ return found != 0;
+}
+
+
+LRESULT IPC_GetFileRating(INT_PTR fileName)
+{
+ return IPC_GetFileRatingW((INT_PTR)(wchar_t *)AutoWide((const char *)fileName));
+}
+
+LRESULT IPC_SetFileRating(INT_PTR file_rating)
+{
+ file_set_rating *data = (file_set_rating*)file_rating;
+ if (!data) return 0;
+ file_set_ratingW dataW;
+ AutoWide wideFn(data->fileName);
+ dataW.fileName = wideFn;
+ dataW.newRating = data->newRating;
+ return IPC_SetFileRatingW((INT_PTR)&dataW);
+}
+
+int m_calling_getfileinfo;
+int getFileInfo(const char *filename, char *metadata, char *dest, int len)
+{
+ m_calling_getfileinfo=1;
+ dest[0]=0;
+ extendedFileInfoStruct efis={
+ filename,
+ metadata,
+ dest,
+ len,
+ };
+ int r = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFO); //will return 1 if wa2 supports this IPC call
+ m_calling_getfileinfo=0;
+ return r;
+}
+
+LRESULT IPC_GetFileInfo(INT_PTR param)
+{
+ if (!param) return 0;
+ itemRecord *ent = (itemRecord *)param;
+ if (!ent->filename || ent->filename && !*ent->filename) return 0;
+
+ char filename2[MAX_PATH] = {0}; // full lfn path if set
+ makeFilename2(ent->filename, filename2, ARRAYSIZE(filename2));
+
+ if (!g_table) openDb();
+ // first, check to see if it's in the db
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, (AutoWideFn)(ent->filename)) ||
+ (filename2[0] && NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, (AutoWideFn)(filename2))))
+ {
+ nde_field_t f;
+ // found it db, yay!
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TITLE);
+ if (!ent->title) ent->title = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ALBUM);
+ if (!ent->album) ent->album = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ARTIST);
+ if (!ent->artist) ent->artist = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_GENRE);
+ if (!ent->genre) ent->genre = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TRACKNB);
+ if (ent->track <= 0) ent->track = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_YEAR);
+ if (ent->year <= 0) ent->year = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LENGTH);
+ if (ent->length <= 0) ent->length = f ? NDE_IntegerField_GetValue(f) : -1;
+ //ent->extended_info=NULL;//for now
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ }
+ // if not, run the same shit as if we were adding it to the db!
+
+ char *fnp = filename2[0] ? filename2 : ent->filename; // fnp is the file we will run through the guesswork
+
+ char tmp[1024] = {0, };
+ if (getFileInfo(fnp, "title", tmp, ARRAYSIZE(tmp) - 1))
+ {
+ if (tmp[0] && !ent->title)
+ {
+ ent->title = _strdup(tmp);
+ }
+ if (!ent->artist)
+ {
+ getFileInfo(ent->filename, "artist", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->artist = _strdup(tmp);
+ }
+ }
+ if (!ent->album)
+ {
+ getFileInfo(ent->filename, "album", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->album = _strdup(tmp);
+ }
+ }
+ if (ent->year <= 0)
+ {
+ getFileInfo(ent->filename, "year", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0] && !strstr(tmp, "__") && !strstr(tmp, "/") && !strstr(tmp, "\\") && !strstr(tmp, "."))
+ {
+ char *p = tmp;
+ while (p && *p)
+ {
+ if (*p == '_') *p = '0';
+ p++;
+ }
+ int y = atoi(tmp);
+ if (y != 0) ent->year = y;
+ }
+ }
+ if (!ent->genre)
+ {
+ getFileInfo(ent->filename, "genre", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->genre = _strdup(tmp);
+ }
+ }
+ if (ent->track <= 0)
+ {
+ getFileInfo(ent->filename, "track", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->track = atoi(tmp);
+ }
+ }
+ if (ent->length <= 0)
+ {
+ getFileInfo(ent->filename, "length", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->length = atoi(tmp) / 1000;
+ }
+ }
+ }
+ if (!ent->title && !ent->artist && !ent->album && !ent->track)
+ {
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0;
+ wchar_t *guessbuf = guessTitles(AutoWide(filename2[0] ? filename2 : ent->filename), &tn, &artist, &album, &title);
+ if (guessbuf)
+ {
+ if (!ent->title && title)
+ {
+ ent->title = AutoCharDup(title);
+ }
+ if (!ent->artist && artist)
+ {
+ ent->artist = AutoCharDup(artist);
+ }
+ if (!ent->album && album)
+ {
+ ent->album = AutoCharDup(album);
+ }
+ if (ent->track <= 0 && tn)
+ {
+ ent->track = tn;
+ }
+ free(guessbuf);
+ }
+ }
+ return 0;
+}
+
+int m_calling_getfileinfoW;
+static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len)
+{
+ m_calling_getfileinfoW=1;
+ dest[0]=0;
+ extendedFileInfoStructW efis={
+ filename,
+ metadata,
+ dest,
+ len,
+ };
+ int r = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFOW); //will return 1 if wa2 supports this IPC call
+ m_calling_getfileinfoW=0;
+ return r;
+}
+
+LRESULT IPC_GetFileInfoW(INT_PTR param)
+{
+ if (!param) return 0;
+ itemRecordW *ent = (itemRecordW *)param;
+ if (!ent->filename || ent->filename && !*ent->filename) return 0;
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ makeFilename2W(ent->filename, filename2, ARRAYSIZE(filename2));
+
+ if (!g_table) openDb();
+ // first, check to see if it's in the db
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, ent->filename) ||
+ (filename2[0] && NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename2)))
+ {
+ nde_field_t f;
+ // found it db, yay!
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TITLE);
+ if (!ent->title) ent->title = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ALBUM);
+ if (!ent->album) ent->album = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ARTIST);
+ if (!ent->artist) ent->artist = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_GENRE);
+ if (!ent->genre) ent->genre = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TRACKNB);
+ if (ent->track <= 0) ent->track = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_YEAR);
+ if (ent->year <= 0) ent->year = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LENGTH);
+ if (ent->length <= 0) ent->length = f ? NDE_IntegerField_GetValue(f) : -1;
+ //ent->extended_info=NULL;//for now
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ }
+ // if not, run the same shit as if we were adding it to the db!
+
+ wchar_t *fnp = filename2[0] ? filename2 : ent->filename; // fnp is the file we will run through the guesswork
+
+ wchar_t tmp[1024] = {0, };
+ if (getFileInfoW(fnp, DB_FIELDNAME_title, tmp, ARRAYSIZE(tmp) - 1))
+ {
+ if (tmp[0] && !ent->title)
+ {
+ ent->title = _wcsdup(tmp);
+ }
+ if (!ent->artist)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_artist, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->artist = _wcsdup(tmp);
+ }
+ }
+ if (!ent->album)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_album, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->album = _wcsdup(tmp);
+ }
+ }
+ if (ent->year <= 0)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_year, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0] && !wcsstr(tmp, L"__") && !wcsstr(tmp, L"/") && !wcsstr(tmp, L"\\") && !wcsstr(tmp, L"."))
+ {
+ wchar_t *p = tmp;
+ while (p && *p)
+ {
+ if (*p == L'_') *p = L'0';
+ p++;
+ }
+ int y = _wtoi(tmp);
+ if (y != 0) ent->year = y;
+ }
+ }
+ if (!ent->genre)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_genre, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->genre = _wcsdup(tmp);
+ }
+ }
+ if (ent->track <= 0)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_track, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->track = _wtoi(tmp);
+ }
+ }
+ if (ent->length <= 0)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_length, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->length = _wtoi(tmp) / 1000;
+ }
+ }
+ }
+ if (!ent->title && !ent->artist && !ent->album && !ent->track)
+ {
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0;
+ wchar_t *guessbuf = guessTitles(filename2[0] ? filename2 : ent->filename, &tn, &artist, &album, &title);
+ if (guessbuf)
+ {
+ if (!ent->title && title)
+ {
+ ent->title = _wcsdup(title);
+ }
+ if (!ent->artist && artist)
+ {
+ ent->artist = _wcsdup(artist);
+ }
+ if (!ent->album && album)
+ {
+ ent->album = _wcsdup(album);
+ }
+ if (ent->track <= 0 && tn)
+ {
+ ent->track = tn;
+ }
+ free(guessbuf);
+ }
+ }
+ return 0;
+}
+
+LRESULT IPC_FreeFileInfo(INT_PTR param)
+{
+ itemRecord *ent = (itemRecord *)param;
+ if (!param) return 0;
+ if (ent->album)
+ {
+ free(ent->album); ent->album = NULL;
+ }
+ if (ent->artist)
+ {
+ free(ent->artist); ent->artist = NULL;
+ }
+ if (ent->comment)
+ {
+ free(ent->comment); ent->comment = NULL;
+ }
+ if (ent->genre)
+ {
+ free(ent->genre); ent->genre = NULL;
+ }
+ if (ent->title)
+ {
+ free(ent->title); ent->title = NULL;
+ }
+ //if (ent->extended_info) { free(ent->extended_info); ent->extended_info= NULL; }
+
+ return 0;
+}
+
+LRESULT IPC_FreeFileInfoW(INT_PTR param)
+{
+ itemRecordW *ent = (itemRecordW *)param;
+ if (!param) return 0;
+ if (ent->album)
+ {
+ free(ent->album); ent->album = NULL;
+ }
+ if (ent->artist)
+ {
+ free(ent->artist); ent->artist = NULL;
+ }
+ if (ent->comment)
+ {
+ free(ent->comment); ent->comment = NULL;
+ }
+ if (ent->genre)
+ {
+ free(ent->genre); ent->genre = NULL;
+ }
+ if (ent->title)
+ {
+ free(ent->title); ent->title = NULL;
+ }
+ //if (ent->extended_info) { free(ent->extended_info); ent->extended_info= NULL; }
+
+ return 0;
+}
+
+LRESULT IPC_EditView(INT_PTR param)
+{
+ static char dummyNameChar[256];
+ static char dummyQueryChar[1024];
+ ml_editview *mev = (ml_editview *)param;
+ if (mev == NULL) return 0;
+ if (queryEditOther(mev->dialog_parent, mev->query, mev->name, mev->mode))
+ {
+ mev->mode = m_item_mode;
+ WideCharToMultiByteSZ(CP_ACP, 0, m_item_name, -1, dummyNameChar, 256, 0, 0);
+ mev->name = dummyNameChar;
+ WideCharToMultiByteSZ(CP_ACP, 0, m_item_query, -1, dummyQueryChar, 1024, 0, 0);
+ mev->query = dummyQueryChar;
+ }
+ else
+ {
+ mev->dialog_parent = NULL;
+ mev->mode = -1;
+ mev->name = NULL;
+ mev->query = NULL;
+ return 0;
+ }
+ return 1;
+}
+
+LRESULT IPC_EditQuery(INT_PTR param)
+{
+ static char staticQuery[4096];
+ ml_editquery *meq = (ml_editquery *)param;
+ if (meq == NULL) return 0;
+ wchar_t querybuf[4096] = {0};
+ const wchar_t *newQuery = editQuery(meq->dialog_parent, AutoWide(meq->query), querybuf, 4096);
+ WideCharToMultiByteSZ(CP_ACP, 0, newQuery, -1, staticQuery, 4096, 0, 0);
+ meq->query = staticQuery;
+ return meq->query != NULL;
+}
+
+bool FindFileInDB(nde_scanner_t s, const wchar_t *filename)
+{
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ return !!FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+}
+
+INT_PTR HandleIpcMessage(INT_PTR msg, INT_PTR param)
+{
+ switch (msg)
+ {
+ /////////////////////// database API
+ case ML_IPC_DB_RUNQUERYW:
+ case ML_IPC_DB_RUNQUERY_SEARCHW:
+ case ML_IPC_DB_RUNQUERY_FILENAMEW:
+ case ML_IPC_DB_RUNQUERY_INDEXW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ mlQueryStructW *p = (mlQueryStructW *)param;
+ itemRecordListW *obj = &p->results;
+ int failed = 0;
+
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (msg == ML_IPC_DB_RUNQUERY_SEARCHW)
+ {
+ if (p->query)
+ {
+ GayStringW gs;
+ makeQueryStringFromText(&gs, p->query);
+ if (gs.Get() && gs.Get()[0]) NDE_Scanner_Query(s, gs.Get());
+ }
+ NDE_Scanner_First(s);
+ }
+ else if (msg == ML_IPC_DB_RUNQUERY_INDEXW)
+ {
+ failed=1;
+ }
+ else if (msg == ML_IPC_DB_RUNQUERY_FILENAMEW)
+ {
+ failed = 1;
+ if (p->query)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, p->query))
+ {
+ failed = 0;
+ }
+ }
+ }
+ else
+ {
+ if (p->query)
+ NDE_Scanner_Query(s, p->query);
+ NDE_Scanner_First(s);
+ }
+
+ if (!failed)
+ {
+ int cnt = 0;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(obj->Items[obj->Size].filename);
+ ScannerRefToObjCacheNFNW(s, obj, true);
+
+ // this makes 0 give unlimited results, 1 give 1, and so on.
+ if (++cnt == p->max_results || msg == ML_IPC_DB_RUNQUERY_FILENAMEW
+ /*|| msg == ML_IPC_DB_RUNQUERY_INDEX*/) break;
+
+ }
+ while (NDE_Scanner_Next(s));
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ compactRecordList(obj);
+
+ return failed ? -1 : 1;
+ }
+ return -1;
+
+ case ML_IPC_DB_RUNQUERY:
+ case ML_IPC_DB_RUNQUERY_SEARCH:
+ case ML_IPC_DB_RUNQUERY_FILENAME:
+ case ML_IPC_DB_RUNQUERY_INDEX:
+ {
+ mlQueryStruct *p = (mlQueryStruct*)param;
+ mlQueryStructW pW;
+ AutoWide wideQuery(p->query);
+ pW.query = wideQuery;
+ pW.max_results = p->max_results;
+ memset(&pW.results, 0, sizeof(pW.results));
+ INT_PTR res = HandleIpcMessage(msg+0x1000, (INT_PTR)&pW); // convienently the W messages are 0x1000 higher
+ itemRecordList *obj = &p->results;
+ // append new results to old results
+ for (int i=0;i!=pW.results.Size;i++)
+ {
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+ convertRecord(&obj->Items[obj->Size++], &pW.results.Items[i]);
+ }
+ freeRecordList(&pW.results);
+ return res;
+ }
+ case ML_IPC_DB_FREEQUERYRESULTS:
+ if (param)
+ {
+ mlQueryStruct *p = (mlQueryStruct*)param;
+ freeRecordList(&p->results);
+ return 1;
+ }
+ return -1;
+ case ML_IPC_DB_FREEQUERYRESULTSW:
+ if (param)
+ {
+ mlQueryStructW *p = (mlQueryStructW*)param;
+ freeRecordList(&p->results);
+ return 1;
+ }
+ return -1;
+ case ML_IPC_DB_REMOVEITEM:
+ case ML_IPC_DB_REMOVEITEMW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ wchar_t *filename = (((msg == ML_IPC_DB_REMOVEITEMW) ? (wchar_t*)param : AutoWideFn((char*)param)));
+ int ret = ( RemoveFileFromDB(filename) == 0 ) ? 1 : -2; // Change the return value from the stadard 0 == success, > 0 == fail
+ return ret;
+ }
+ return -1;
+ case ML_IPC_DB_UPDATEITEMW:
+ case ML_IPC_DB_ADDORUPDATEITEMW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ int ret = 1;
+ itemRecordW *item = (itemRecordW*)param;
+ if (!item->filename) return -1;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (FindFileInDB(s, item->filename))
+ {
+ NDE_Scanner_Edit(s);
+ }
+ else
+ {
+ if (msg == ML_IPC_DB_UPDATEITEMW) ret = -2;
+ else
+ {
+ // new file
+ NDE_Scanner_New(s);
+ db_setFieldStringW(s, MAINTABLE_ID_FILENAME, item->filename);
+ db_setFieldInt(s, MAINTABLE_ID_DATEADDED, (int)time(NULL));
+ }
+ }
+ if (ret == 1)
+ {
+ // update applicable members
+ if (item->title) db_setFieldStringW(s, MAINTABLE_ID_TITLE, item->title);
+ if (item->album) db_setFieldStringW(s, MAINTABLE_ID_ALBUM, item->album);
+ if (item->artist) db_setFieldStringW(s, MAINTABLE_ID_ARTIST, item->artist);
+ if (item->comment) db_setFieldStringW(s, MAINTABLE_ID_COMMENT, item->comment);
+ if (item->genre) db_setFieldStringW(s, MAINTABLE_ID_GENRE, item->genre);
+ if (item->albumartist) db_setFieldStringW(s, MAINTABLE_ID_ALBUMARTIST, item->albumartist);
+ if (item->replaygain_album_gain) db_setFieldStringW(s, MAINTABLE_ID_ALBUMGAIN, item->replaygain_album_gain);
+ if (item->replaygain_track_gain) db_setFieldStringW(s, MAINTABLE_ID_TRACKGAIN, item->replaygain_track_gain);
+ if (item->publisher) db_setFieldStringW(s, MAINTABLE_ID_PUBLISHER, item->publisher);
+ if (item->composer) db_setFieldStringW(s, MAINTABLE_ID_COMPOSER, item->composer);
+ if (item->category) db_setFieldStringW(s, MAINTABLE_ID_CATEGORY, item->category);
+ if (item->year >= 0) db_setFieldInt(s, MAINTABLE_ID_YEAR, item->year);
+ if (item->track >= 0) db_setFieldInt(s, MAINTABLE_ID_TRACKNB, item->track);
+ if (item->tracks >= 0) db_setFieldInt(s, MAINTABLE_ID_TRACKS, item->tracks);
+ if (item->length >= 0) db_setFieldInt(s, MAINTABLE_ID_LENGTH, item->length);
+ if (item->rating >= 0)
+ {
+ db_setFieldInt(s, MAINTABLE_ID_RATING, item->rating);
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (item->rating > 0)
+ {
+ wsprintfW(buf, L"%d", item->rating);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(item->filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+ if (item->lastplay >= 0) db_setFieldInt(s, MAINTABLE_ID_LASTPLAY, (int)item->lastplay);
+ if (item->lastupd >= 0) db_setFieldInt(s, MAINTABLE_ID_LASTUPDTIME, (int)item->lastupd);
+ if (item->filetime >= 0) db_setFieldInt(s, MAINTABLE_ID_FILETIME, (int)item->filetime);
+ if (item->filesize >= 0)
+ {
+ // changed in 5.64 to use the 'realsize' if it's available, otherwise map to filesize scaled to bytes (was stored as kb previously)
+ const wchar_t *realsize = getRecordExtendedItem(item, L"realsize");
+ if (realsize) db_setFieldInt64(s, MAINTABLE_ID_FILESIZE, _wtoi64(realsize));
+ else db_setFieldInt(s, MAINTABLE_ID_FILESIZE, item->filesize * 1024);
+ }
+ if (item->bitrate >= 0) db_setFieldInt(s, MAINTABLE_ID_BITRATE, item->bitrate);
+ if (item->type >= 0) db_setFieldInt(s, MAINTABLE_ID_TYPE, item->type);
+ if (item->disc >= 0) db_setFieldInt(s, MAINTABLE_ID_DISC, item->disc);
+ if (item->discs >= 0) db_setFieldInt(s, MAINTABLE_ID_DISCS, item->discs);
+ if (item->bpm >= 0) db_setFieldInt(s, MAINTABLE_ID_BPM, item->bpm);
+ // give a default playcnt of 0 if we're creating a new one and the caller
+ // didn't specify a playcnt
+ if (item->playcount >= 0)
+ db_setFieldInt(s, MAINTABLE_ID_PLAYCOUNT, item->playcount);
+ else
+ db_setFieldInt(s, MAINTABLE_ID_PLAYCOUNT, 0);
+
+ for (int x = 0; x < NUM_EXTRA_COLSW; x ++)
+ {
+ wchar_t *at = getRecordExtendedItem(item, extra_strsW[x]); // can't use _fast here because it's not our itemRecordW
+ if (at)
+ {
+ switch (extra_idsW[x])
+ {
+ case MAINTABLE_ID_LOSSLESS:
+ case MAINTABLE_ID_PODCASTPUBDATE:
+ case MAINTABLE_ID_ISPODCAST:
+ case MAINTABLE_ID_WIDTH:
+ case MAINTABLE_ID_HEIGHT:
+ if (*at)
+ db_setFieldInt(s, extra_idsW[x], _wtoi(at));
+ break;
+ case MAINTABLE_ID_GRACENOTEFILEID:
+ case MAINTABLE_ID_GRACENOTEEXTDATA:
+ case MAINTABLE_ID_PODCASTCHANNEL:
+ case MAINTABLE_ID_CODEC:
+ case MAINTABLE_ID_DIRECTOR:
+ case MAINTABLE_ID_PRODUCER:
+ db_setFieldStringW(s, extra_idsW[x], at);
+ break;
+ }
+ }
+ }
+
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ return ret;
+ }
+ return -1;
+
+ case ML_IPC_DB_UPDATEITEM:
+ case ML_IPC_DB_ADDORUPDATEITEM:
+ {
+ itemRecord *item = (itemRecord*)param;
+ if (!item || !item->filename)
+ return -1;
+ itemRecordW wideRecord;
+ memset(&wideRecord, 0, sizeof(wideRecord));
+ convertRecord(&wideRecord, item);
+ INT_PTR res = HandleIpcMessage(msg+0x1000, (INT_PTR)&wideRecord); // unicode message values are convienantly 0x1000 higher here
+ freeRecord(&wideRecord);
+ return res;
+ }
+
+ case ML_IPC_DB_UPDATEFILEW:
+ case ML_IPC_DB_ADDORUPDATEFILEW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ LMDB_FILE_ADD_INFOW *fi = (LMDB_FILE_ADD_INFOW *)param;
+ if (!fi || !fi->fileName) return -1;
+
+ int guess = (fi->gues_mode == -1) ? g_config->ReadInt(L"guessmode", 0) : fi->gues_mode;
+ int meta = (fi->meta_mode == -1) ? g_config->ReadInt(L"usemetadata", 1) : fi->meta_mode;
+ return addFileToDb(fi->fileName, msg == ML_IPC_DB_UPDATEFILEW ? 1 : 0, meta, guess);
+ }
+ return -1;
+ case ML_IPC_DB_UPDATEFILE:
+ case ML_IPC_DB_ADDORUPDATEFILE:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ LMDB_FILE_ADD_INFO *fi = (LMDB_FILE_ADD_INFO*)param;
+ if (!fi || !fi->fileName) return -1;
+
+ int guess = (fi->gues_mode == -1) ? g_config->ReadInt(L"guessmode", 0) : fi->gues_mode;
+ int meta = (fi->meta_mode == -1) ? g_config->ReadInt(L"usemetadata", 1) : fi->meta_mode;
+ return addFileToDb(AutoWide(fi->fileName), msg == ML_IPC_DB_UPDATEFILE ? 1 : 0, meta, guess);
+ }
+ return -1;
+ case ML_IPC_DB_SYNCDB:
+ if (!g_table) openDb();
+ if (g_table && g_table_dirty)
+ {
+ EnterCriticalSection(&g_db_cs);
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+ return -1;
+
+ case ML_IPC_GET_FILE_RATINGW: return IPC_GetFileRatingW(param);
+ case ML_IPC_SET_FILE_RATINGW: return IPC_SetFileRatingW(param);
+ case ML_IPC_GET_FILE_RATING: return IPC_GetFileRating(param);
+ case ML_IPC_SET_FILE_RATING: return IPC_SetFileRating(param);
+ case ML_IPC_FREEFILEINFO: return IPC_FreeFileInfo(param);
+ case ML_IPC_FREEFILEINFOW: return IPC_FreeFileInfoW(param);
+ case ML_IPC_GETFILEINFO: return IPC_GetFileInfo(param);
+ case ML_IPC_GETFILEINFOW: return IPC_GetFileInfoW(param);
+ case ML_IPC_PLAY_PLAYLIST:
+ case ML_IPC_LOAD_PLAYLIST:
+ {
+ // play/load the playlist passed as param
+ queryItem *item = m_query_list[param];
+ if (item)
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf, item->query, 0, msg == ML_IPC_PLAY_PLAYLIST);
+ }
+ }
+ break;
+ case ML_IPC_EDITVIEW: return IPC_EditView(param);
+ case ML_IPC_EDITQUERY: return IPC_EditQuery(param);
+ case ML_IPC_SMARTVIEW_COUNT:
+ return m_query_list.size();
+ case ML_IPC_SMARTVIEW_INFO:
+ if (param)
+ {
+ mlSmartViewInfo * v = (mlSmartViewInfo *)param;
+ if (v->size < sizeof(mlSmartViewInfo)) break;
+ if (v->smartViewNum >= m_query_list.size()) break;
+
+ auto it = m_query_list.begin();
+ while (v->smartViewNum--)
+ {
+ it++;
+ }
+ queryItem* q = it->second; //(m_query_list.begin()+v->smartViewNum)->second;
+
+ if (!q) break;
+ if (!q->name || !q->query) break;
+ v->treeItemId = it->first; //(m_query_list.begin()+v->smartViewNum)->first;
+ v->iconImgIndex = q->imgIndex;
+ v->mode = q->mode;
+ lstrcpynW(v->smartViewName,q->name,sizeof(v->smartViewName)/sizeof(wchar_t));
+ lstrcpynW(v->smartViewQuery,q->query,sizeof(v->smartViewQuery)/sizeof(wchar_t));
+ return 1;
+ }
+ break;
+ case ML_IPC_SMARTVIEW_ADD:
+ if (param)
+ {
+ mlSmartViewInfo * v = (mlSmartViewInfo *)param;
+ if (v->size < sizeof(mlSmartViewInfo)) break;
+ v->treeItemId = addQueryItem(v->smartViewName,v->smartViewQuery,v->mode,0,NULL,v->iconImgIndex, v->smartViewNum);
+ saveQueryTree();
+ return 1;
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/local_menu.cpp b/Src/Plugins/Library/ml_local/local_menu.cpp
new file mode 100644
index 00000000..0b55496a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/local_menu.cpp
@@ -0,0 +1,272 @@
+#include "main.h"
+#include "./local_menu.h"
+#include "./resource.h"
+#include "..\..\General\gen_ml/menu.h"
+#include "../nu/menushortcuts.h"
+
+#define RATING_MARKER MAKELONG(MAKEWORD('R','A'),MAKEWORD('T','E'))
+#define RATING_MINSPACECX 16
+
+#if 0
+static BOOL Menu_IsRatingStar(HMENU hMenu, INT itemId, INT *valueOut)
+{
+ WCHAR szBuffer[8] = {0};
+ INT cchBuffer = GetMenuStringW(hMenu, itemId, szBuffer, ARRAYSIZE(szBuffer), MF_BYCOMMAND);
+ if (cchBuffer < 1 || cchBuffer > 5)
+ return FALSE;
+
+ for (INT i = 1; i < cchBuffer; i++)
+ {
+ if (szBuffer[i -1] != szBuffer[i])
+ return FALSE;
+ }
+
+ if (NULL != valueOut)
+ *valueOut = cchBuffer;
+
+ return TRUE;
+}
+
+static BOOL Menu_MeasureRating(HMENU hMenu, HDC hdc, MEASUREITEMSTRUCT *pmis)
+{
+ if (NULL == hdc || !Menu_IsRatingStar(hMenu, pmis->itemID, NULL))
+ return FALSE;
+
+ RECT rect;
+ if (!MLRating_CalcRect(plugin.hwndLibraryParent, NULL, 5, &rect))
+ return FALSE;
+
+ pmis->itemHeight = rect.bottom - rect.top + 6;
+
+ TEXTMETRIC tm = {0};
+ if (GetTextMetrics(hdc, &tm) &&
+ (UINT)(tm.tmHeight + 2) > pmis->itemHeight)
+ {
+ pmis->itemHeight = tm.tmHeight + 2;
+ }
+
+ INT spaceCX = (pmis->itemHeight > RATING_MINSPACECX) ? pmis->itemHeight : RATING_MINSPACECX;
+ pmis->itemWidth = rect.right - rect.left + (2 * spaceCX) - (GetSystemMetrics(SM_CXMENUCHECK) - 1);
+ return TRUE;
+}
+
+static BOOL Menu_DrawRating(HMENU hMenu, HDC hdc, DRAWITEMSTRUCT *pdis)
+{
+ INT ratingValue;
+ if (NULL == hdc || !Menu_IsRatingStar(hMenu, pdis->itemID, &ratingValue))
+ return FALSE;
+
+ INT spaceCX = ((pdis->rcItem.bottom - pdis->rcItem.top) > RATING_MINSPACECX) ?
+ (pdis->rcItem.bottom - pdis->rcItem.top) :
+ RATING_MINSPACECX;
+
+ RATINGDRAWPARAMS rdp = {0};
+ rdp.cbSize = sizeof(RATINGDRAWPARAMS);
+ rdp.hdcDst = hdc;
+ rdp.rc = pdis->rcItem;
+ rdp.rc.left += spaceCX + WASABI_API_APP->getScaleX(13);
+ rdp.value = ratingValue;
+ rdp.maxValue = 5;
+
+ UINT menuState = GetMenuState(hMenu, pdis->itemID, MF_BYCOMMAND);
+ rdp.trackingValue = (0 != (ODS_SELECTED & pdis->itemState) &&
+ 0 == ((MF_DISABLED | MF_GRAYED) & menuState)) ?
+ rdp.value :
+ 0;
+
+ rdp.fStyle = RDS_LEFT | RDS_VCENTER | RDS_HOT;
+ rdp.hMLIL = NULL;
+ rdp.index = 0;
+
+ return MLRating_Draw(plugin.hwndLibraryParent, &rdp);
+}
+
+static BOOL CALLBACK Menu_CustomDrawProc(INT action, HMENU hMenu, HDC hdc, LPARAM param, ULONG_PTR user)
+{
+ switch(action)
+ {
+ case MLMENU_ACTION_MEASUREITEM:
+ if (hMenu == (HMENU)user)
+ return Menu_MeasureRating(hMenu, hdc, (MEASUREITEMSTRUCT*)param);
+ break;
+ case MLMENU_ACTION_DRAWITEM:
+ if (hMenu == (HMENU)user)
+ return MLMENU_WANT_DRAWPART;
+ break;
+ case MLMENU_ACTION_DRAWBACK:
+ break;
+ case MLMENU_ACTION_DRAWICON:
+ break;
+ case MLMENU_ACTION_DRAWTEXT:
+ if (hMenu == (HMENU)user)
+ return Menu_DrawRating(hMenu, hdc, (DRAWITEMSTRUCT*)param);
+ break;
+ }
+ return FALSE;
+}
+#endif
+
+static HMENU Menu_FindRatingMenuRecur(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii)
+{
+ if (GetMenuInfo(hMenu, pmi) && RATING_MARKER == pmi->dwMenuData)
+ return hMenu;
+
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii) && NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingMenuRecur(pmii->hSubMenu, pmi, pmii);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ return NULL;
+}
+
+HMENU Menu_FindRatingMenu(HMENU hMenu)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_SUBMENU;
+
+ MENUINFO mi;
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+
+ return Menu_FindRatingMenuRecur(hMenu, &mi, &mii);
+}
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue)
+{
+ if (NULL == ratingMenu) return FALSE;
+
+ INT ratingList[] = { ID_RATE_0, ID_RATE_1, ID_RATE_2,
+ ID_RATE_3, ID_RATE_4, ID_RATE_5};
+
+ /// set rating marker
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+ mi.dwMenuData = RATING_MARKER;
+ if (!SetMenuInfo(ratingMenu, &mi))
+ return FALSE;
+
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+
+ UINT type, state;
+ for (INT i = 0; i < ARRAYSIZE(ratingList); i++)
+ {
+ mii.fMask = MIIM_STATE | MIIM_FTYPE;
+ if (GetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii))
+ {
+ if (ratingValue == i)
+ {
+ type = mii.fType | MFT_RADIOCHECK;
+ state = mii.fState | MFS_CHECKED;
+ }
+ else
+ {
+ type = mii.fType & ~MFT_RADIOCHECK;
+ state = mii.fState & ~MFS_CHECKED;
+ }
+
+ mii.fMask = 0;
+ if (type != mii.fType)
+ {
+ mii.fType = type;
+ mii.fMask |= MIIM_FTYPE;
+ }
+
+ if (state != mii.fState)
+ {
+ mii.fState = state;
+ mii.fMask |= MIIM_STATE;
+ }
+
+ if (0 != mii.fMask)
+ SetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii);
+ }
+ }
+ return TRUE;
+}
+
+
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu)
+{
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(hwndDlg, szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendMenuShortcuts(menu, szAccel, c, MSF_REPLACE);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ int alt = 0;
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_MEDIAWND_PLAYSELECTEDFILES || id == ID_AUDIOWND_PLAYSELECTION || id == ID_QUERYWND_PLAYQUERY)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ if (id == ID_AUDIOWND_PLAYSELECTION) alt = 1;
+ else if (id == ID_QUERYWND_PLAYQUERY) alt = 2;
+ }
+ else if (id == ID_MEDIAWND_ENQUEUESELECTEDFILES || id == ID_AUDIOWND_ENQUEUESELECTION || id == ID_QUERYWND_ENQUEUEQUERY)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ if (id == ID_AUDIOWND_ENQUEUESELECTION) alt = 1;
+ else if (id == ID_QUERYWND_ENQUEUEQUERY) alt = 2;
+ }
+ }
+
+ playItem.wID = (alt == 1 ? ID_MEDIAWND_ENQUEUESELECTEDFILES : (alt == 2 ? ID_QUERYWND_ENQUEUEQUERY : ID_AUDIOWND_ENQUEUESELECTION));
+ enqueueItem.wID = (alt == 1 ? ID_MEDIAWND_PLAYSELECTEDFILES : (alt == 2 ? ID_QUERYWND_PLAYQUERY : ID_AUDIOWND_PLAYSELECTION));
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+void UpdateMenuItems(HWND hwndDlg, HMENU menu, UINT accel_id)
+{
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ if(!IsWindow(hwndDlg))
+ {
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(accel_id);
+ int size = CopyAcceleratorTable(accel,0,0);
+ AppendMenuShortcuts(menu, &accel, size, MSF_REPLACE);
+ }
+ else
+ {
+ SyncMenuWithAccelerators(hwndDlg, menu);
+ }
+
+ if (swapPlayEnqueue) SwapPlayEnqueueInMenu(menu);
+}
+
+INT DoTrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ return Menu_TrackPopupParam(plugin.hwndLibraryParent, hMenu, fuFlags, x, y,
+ hwnd, lptpm, (ULONG_PTR)Menu_FindRatingMenu(hMenu));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/local_menu.h b/Src/Plugins/Library/ml_local/local_menu.h
new file mode 100644
index 00000000..08907d70
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/local_menu.h
@@ -0,0 +1,15 @@
+#ifndef NULLOSFT_LOCALMEDIA_PLUGIN_MENU_HEADER
+#define NULLOSFT_LOCALMEDIA_PLUGIN_MENU_HEADER
+
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue);
+INT DoTrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm);
+void UpdateMenuItems(HWND hwndDlg, HMENU menu, UINT accel_id);
+
+#endif //NULLOSFT_LOCALMEDIA_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/metaRecord.h b/Src/Plugins/Library/ml_local/metaRecord.h
new file mode 100644
index 00000000..9dba63c2
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/metaRecord.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOT_LOCALMEDIA_METARECORD_H
+#define NULLSOT_LOCALMEDIA_METARECORD_H
+
+#include <map>
+#include <vector>
+#include <string>
+
+// Links db record to the metadata struct
+typedef struct
+{
+ int dbColumnId;
+ char *recordKey;
+} LM_RECORD_LINK;
+
+// cache size (records count)
+#define CACHE_SIZE 100;
+
+class MetaData
+{
+
+// construcotrs
+public:
+ MetaData();
+ ~MetaData();
+
+// methods
+private:
+ // gets record data from db to the cache
+ int GetDbColumnsCount();
+ void GetDBRecordToCache(int64 recordId);
+
+
+public:
+ // returns pointer to metadata
+ const char* GetMetaData(const char *metaKey);
+
+// fields
+private:
+
+
+}
+
+
+#endif //NULLSOT_LOCALMEDIA_METARECORD_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.cpp b/Src/Plugins/Library/ml_local/ml_local.cpp
new file mode 100644
index 00000000..b6e0dde7
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.cpp
@@ -0,0 +1,1394 @@
+// needed for handling the nde failed load error catching
+#pragma warning(disable:4530)
+#include "main.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include <time.h>
+#include <rpc.h>
+#include <assert.h>
+#include "resource.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/gaystring.h"
+
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+
+#include "../nde/nde.h"
+#include "../nde/nde_c.h"
+
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "..\..\General\gen_ml/MediaLibraryCOM.h"
+#include "api__ml_local.h"
+
+#include "../replicant/nu/AutoWide.h"
+
+#include "./scanfolderbrowser.h"
+#include "../replicant/nu/AutoChar.h"
+
+#include "api_mldb.h"
+
+
+int IPC_GET_ML_HMENU = -1, IPC_GET_CLOUD_HINST = -1, IPC_GET_CLOUD_ACTIVE = -1;
+
+HMENU wa_playlists_cmdmenu = NULL;
+HMENU wa_play_menu = 0;
+
+
+wchar_t *fieldTagFunc(wchar_t * tag, void * p); //return 0 if not found
+
+#define WA_MENUITEM_ID 23123
+
+
+WNDPROC wa_oldWndProc;
+BOOL myMenu = FALSE;
+
+HCURSOR hDragNDropCursor;
+
+C_Config *g_config;
+
+embedWindowState myWindowState;
+
+char g_burner_list[32] = "";
+
+int asked_for_playcount = 0;
+int g_bgrescan_int = 120, g_bgrescan_do = 0, g_bgrescan_force = 0, g_autochannel_do = 0;
+int g_guessifany = 0;
+int g_querydelay = 250;
+int g_viewnotplay = 0;
+
+wchar_t g_path[MAX_PATH] = {0};
+wchar_t g_tableDir[MAX_PATH] = {0};
+wchar_t g_viewsDir[MAX_PATH] = {0};
+
+//DB db;
+
+nde_database_t g_db=0;
+nde_table_t g_table=0;
+int g_table_dirty;
+
+CRITICAL_SECTION g_db_cs;
+
+HMENU g_context_menus = NULL, g_context_menus2 = NULL;
+HWND m_curview_hwnd = NULL;
+
+wchar_t *m_query = L"";
+int m_query_mode;
+
+C_Config *g_view_metaconf = NULL;
+
+static int m_query_moving;
+static HTREEITEM m_query_moving_dragplace;
+static HTREEITEM m_query_moving_item, m_query_moving_lastdest;
+static int m_query_moving_dragplaceisbelow;
+
+int m_query_tree;
+
+QueryList m_query_list;
+
+//xp theme disabling shit
+static HMODULE m_uxdll;
+HRESULT(__stdcall *SetWindowTheme)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList);
+HRESULT(__stdcall *IsAppThemed)(void);
+
+void db_setFieldStringW( nde_scanner_t s, unsigned char id, const wchar_t *data )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( !f )
+ f = NDE_Scanner_NewFieldByID( s, id );
+
+ NDE_StringField_SetString( f, data );
+}
+
+void db_setFieldInt( nde_scanner_t s, unsigned char id, int data )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( !f )
+ f = NDE_Scanner_NewFieldByID( s, id );
+
+ NDE_IntegerField_SetValue( f, data );
+}
+
+int db_getFieldInt( nde_scanner_t s, unsigned char id, int defaultVal )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( f )
+ return NDE_IntegerField_GetValue( f );
+ else
+ return defaultVal;
+}
+
+void db_setFieldInt64( nde_scanner_t s, unsigned char id, __int64 data )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( !f )
+ f = NDE_Scanner_NewFieldByID( s, id );
+
+ NDE_Int64Field_SetValue( f, data );
+}
+
+__int64 db_getFieldInt64( nde_scanner_t s, unsigned char id, __int64 defaultVal )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( f )
+ return NDE_Int64Field_GetValue( f );
+ else
+ return defaultVal;
+}
+
+void db_removeField( nde_scanner_t s, unsigned char id )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( f )
+ {
+ NDE_Scanner_DeleteField( s, f );
+ }
+}
+
+int pluginHandleIpcMessage(int msg, int param)
+{
+ return SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
+}
+
+void TAG_FMT_EXT(const wchar_t *filename, void *f, void *ff, void *p, wchar_t *out, int out_len, int extended)
+{
+ waFormatTitleExtended fmt;
+ fmt.filename = filename;
+ fmt.useExtendedInfo = extended;
+ fmt.out = out;
+ fmt.out_len = out_len;
+ fmt.p = p;
+ fmt.spec = 0;
+
+ *(void **)&fmt.TAGFUNC = f;
+ *(void **)&fmt.TAGFREEFUNC = ff;
+
+ *out = 0;
+
+ int oldCallingGetFileInfo = m_calling_getfileinfo;
+ m_calling_getfileinfo = 1;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+ m_calling_getfileinfo = oldCallingGetFileInfo;
+}
+
+void main_playItemRecordList(itemRecordListW *obj, int enqueue, int startplaying)
+{
+ assert(enqueue != -1); // benski> i'm pretty sure this isn't used anymore
+ if (obj->Size && !enqueue)
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+
+ int x;
+ for (x = 0; x < obj->Size; x ++)
+ {
+ if (obj->Items[x].filename && *obj->Items[x].filename)
+ {
+ wchar_t title[2048] = {0};
+ TAG_FMT_EXT(obj->Items[x].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&obj->Items[x], title, 2047, 0);
+
+ enqueueFileWithMetaStructW s;
+ s.filename = obj->Items[x].filename;
+ s.title = title;
+ s.ext = NULL;
+ s.length = obj->Items[x].length;
+#ifndef _DEBUG
+ ndestring_retain(obj->Items[x].filename);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW_NDE);
+#else
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+#endif
+ }
+ }
+
+ if (obj->Size && !enqueue && startplaying) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+}
+
+void main_playQuery(C_Config *viewconf, const wchar_t *query, int enqueue, int startplaying)
+{
+ // if enqueue is -1, we do it to the playlist
+ if (!g_table) return ;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query);
+ itemRecordListW obj = {0, };
+ // no need to have this provide compatible kb
+ // based filesizes as we never use the values
+ saveQueryToListW(viewconf, s, &obj, 0, 0, 0);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ main_playItemRecordList(&obj, enqueue, startplaying);
+
+ freeRecordList(&obj);
+}
+
+time_t g_bgscan_last_rescan;
+int g_bgscan_scanning;
+
+HWND updateCurrentView(HWND hwndDlg)
+{
+ if (m_curview_hwnd) DestroyWindow(m_curview_hwnd);
+ m_curview_hwnd = NULL;
+
+ delete g_view_metaconf;
+ g_view_metaconf = 0;
+
+ int id = -1;
+ DLGPROC proc = 0;
+
+ if (!g_table)
+ {
+ // try to show something better than a blank view
+ id = IDD_VIEW_DB_ERROR;
+ proc = view_errorinfoDialogProc;
+ }
+ else
+ {
+ switch (m_query_mode)
+ {
+ case 0:
+ id = IDD_VIEW_MEDIA; proc = view_mediaDialogProc;
+ break;
+ default:
+ id = IDD_VIEW_AUDIO; proc = view_audioDialogProc;
+ break;
+ }
+ }
+
+ if (id == -1)
+ proc = NULL;
+
+ if (id != -1)
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, m_query_metafile);
+ g_view_metaconf = new C_Config(configDir);
+
+ LPARAM lParam = 0;
+ INT_PTR parms[2] = {0};
+
+ if (g_config->ReadInt(L"useminiinfo2", 0) && (proc == view_audioDialogProc || proc == view_mediaDialogProc))
+ {
+ parms[0] = (INT_PTR)proc;
+ parms[1] = (INT_PTR)id;
+ lParam = (LPARAM) & parms;
+
+ id = IDD_VIEW_MINIINFO;
+ proc = view_miniinfoDialogProc;
+ }
+
+ if (proc)
+ m_curview_hwnd = WASABI_API_CREATEDIALOGPARAMW(id, hwndDlg, proc, lParam);
+ }
+
+ return m_curview_hwnd;
+}
+
+void makemetafn(wchar_t *filename, wchar_t **out)
+{
+ int x = 0;
+ for (;;)
+ {
+ GetTempFileNameW(g_viewsDir, L"meta", GetTickCount() + x*4050, filename);
+ if (wcslen(filename) > 4) wcscpy(filename + wcslen(filename) - 4, L".vmd");
+ HANDLE h = CreateFileW(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW, 0, 0);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(h);
+ *out = _wcsdup(filename);
+ PathStripPathW(*out);
+ return ;
+ }
+ if (++x > 4096)
+ {
+ *out = _wcsdup(L"meta-error.vmd");
+ filename[0] = 0;
+ return ;
+ }
+ }
+}
+
+int ImageGuessFilter( int mode, const wchar_t *val, int index )
+{
+ if ( index != -1 )
+ return index;
+
+ if ( !lstrcmpiW( val, L"lastupd > [3 days ago]" ) )
+ return TREE_IMAGE_LOCAL_RECENTLYMODIFIED;
+ if ( !lstrcmpiW( val, L"dateadded > [3 days ago]" ) )
+ return TREE_IMAGE_LOCAL_RECENTLYADDED;
+ if ( !lstrcmpiW( val, L"playcount > 0" ) )
+ return TREE_IMAGE_LOCAL_MOSTPLAYED;
+ if ( !lstrcmpiW( val, L"rating >= 3" ) )
+ return TREE_IMAGE_LOCAL_TOPRATED;
+ if ( !lstrcmpiW( val, L"playcount = 0 | playcount isempty" ) )
+ return TREE_IMAGE_LOCAL_NEVERPLAYED;
+ if ( !lstrcmpiW( val, L"lastplay > [2 weeks ago]" ) )
+ return TREE_IMAGE_LOCAL_RECENTLYPLAYED;
+ if ( !lstrcmpiW( val, L"type = 0" ) )
+ return TREE_IMAGE_LOCAL_AUDIO;
+ if ( !lstrcmpiW( val, L"type = 1" ) )
+ return TREE_IMAGE_LOCAL_VIDEO;
+
+ return index;
+}
+
+int addQueryItem( const wchar_t *name, const wchar_t *val, int mode, int select, const wchar_t *metafn, int imageIndex, int num )
+{
+ MLTREEITEMW newItem = { 0 };
+ imageIndex = ImageGuessFilter( mode, val, imageIndex );
+
+ newItem.size = sizeof( newItem );
+ newItem.parentId = m_query_tree;
+ newItem.title = const_cast<wchar_t *>( name );
+ newItem.hasChildren = 0;
+ newItem.id = 0;
+ newItem.imageIndex = imageIndex;
+
+ if ( num <= 0 )
+ mediaLibrary.AddTreeItem( newItem );
+ else
+ {
+ for ( QueryList::iterator iter = m_query_list.begin(); iter != m_query_list.end(); iter++ )
+ {
+ if ( iter->second && iter->second->index == num - 1 )
+ newItem.id = iter->first; break;
+ }
+
+ mediaLibrary.InsertTreeItem( newItem );
+ }
+
+ queryItem *qi = (queryItem *)calloc( 1, sizeof( queryItem ) );
+ qi->name = _wcsdup( name );
+ qi->query = _wcsdup( val );
+ qi->mode = mode;
+ qi->imgIndex = imageIndex;
+ qi->index = m_query_list.size();
+
+ if ( !metafn || !metafn[ 0 ] )
+ {
+ wchar_t filename[ 1024 + 256 ] = { 0 };
+ makemetafn( filename, &qi->metafn );
+ }
+ else qi->metafn = _wcsdup( metafn );
+
+ m_query_list.insert({ newItem.id, qi });
+
+ return newItem.id;
+}
+
+void replaceQueryItem(int n, const wchar_t *name, const wchar_t *val, int mode, int imageIndex)
+{
+ queryItem *qi;
+
+ if ( mode == 32 )
+ return;
+
+ qi = m_query_list[n];
+ free(qi->name);
+ qi->name = _wcsdup(name);
+
+ if (val)
+ {
+ free(qi->query);
+ qi->query = _wcsdup(val);
+ }
+
+ qi->mode = mode;
+ qi->imgIndex = imageIndex;
+
+ MLTREEITEMW item;
+ item.hasChildren = 0;
+ item.id = n;
+ item.title = const_cast<wchar_t *>(name);
+ item.parentId = m_query_tree;
+ item.imageIndex = imageIndex;
+
+ mediaLibrary.SetTreeItem(item);
+ mediaLibrary.SelectTreeItem(n - 1);
+ mediaLibrary.SelectTreeItem(n);
+}
+
+wchar_t *def_names[] = { L"Audio",
+ L"Video",
+ L"Most Played",
+ L"Recently Added",
+ L"Recently Played",
+ L"Never Played",
+ L"Top Rated",
+ L"Recently Modified", };
+int def_str_ids[] = {IDS_AUDIO, IDS_VIDEO, IDS_MOST_PLAYED, IDS_RECENTLY_ADDED,
+ IDS_RECENTLY_PLAYED, IDS_NEVER_PLAYED, IDS_TOP_RATED,
+ IDS_RECENTLY_MODIFIED};
+
+void loadQueryTree()
+{
+ int meta_add_dirty = 0;
+ int nb = g_config->ReadInt(L"query_num", 0);
+ int fix = g_config->ReadInt(L"query_fix", 0);
+ int mig = g_config->ReadInt(L"query_mig", 0);
+
+ g_config->WriteInt(L"query_fix", 1);
+ g_config->WriteInt(L"query_mig", 1);
+
+ // helps to migrate existing vmd files to plugins\ml\views
+ wchar_t metafnold[MAX_PATH] = {0}, metafnnew[MAX_PATH] = {0};
+ if (!mig)
+ {
+ PathCombineW(metafnold, g_tableDir, L"default.vmd");
+ PathCombineW(metafnnew, g_viewsDir, L"default.vmd");
+ if (!PathFileExistsW(metafnnew) && PathFileExistsW(metafnold))
+ {
+ MoveFileW(metafnold, metafnnew);
+ }
+ }
+
+ for (int i = 0; i < nb; i++)
+ {
+ wchar_t qm[128] = {0}, qbmp[128] = {0}, qmet[128] = {0};
+ char qn[128] = {0}, qv[128] = {0}, name[1024] = {0}, val[1024] = {0};
+ wsprintfA(qn, "query%i_name", i + 1);
+ UINT codePage = CP_ACP;
+
+ if (!g_config->ReadString(qn, NULL, name, 1024) || !*name)
+ {
+ wsprintfA(qn, "query%i_name_utf8", i + 1);
+ g_config->ReadString(qn, NULL, name, 1024);
+ codePage = CP_UTF8;
+ if (!name)
+ continue;
+ }
+
+ wchar_t unicodeNameLoc[256] = {0};
+ AutoWide unicodeName(name, codePage);
+
+ wsprintfA(qv, "query%i_val", i + 1);
+ codePage = CP_ACP;
+ if (!g_config->ReadString(qv, NULL, val, 1024) || !*val)
+ {
+ wsprintfA(qv, "query%i_val_utf8", i + 1);
+ g_config->ReadString(qv, NULL, val, 1024);
+ codePage = CP_UTF8;
+ }
+
+ // this will convert 'lastupd > [3 days ago]' to 'dateadded > [3 days ago]'
+ // on older client installs so we're making use of the new dateadded column
+ if (!fix && val[0])
+ {
+ if (!_stricmp("lastupd > [3 days ago]", val))
+ {
+ lstrcpynA(val, "dateadded > [3 days ago]", sizeof(val));
+ g_config->WriteString(qv, val);
+ }
+ }
+
+ AutoWide unicodeVal(val, codePage);
+
+ wsprintfW(qm, L"query%i_mode", i + 1);
+ wsprintfW(qmet, L"query%i_meta", i + 1);
+ wsprintfW(qbmp, L"query%i_image", i + 1);
+
+ int mode = g_config->ReadInt(qm, -1);
+ if (mode == 32 || mode == -1)
+ continue; // old playlist or empty
+
+ wchar_t metafn[MAX_PATH] = {0};
+ g_config->ReadString(qmet, L"", metafn, MAX_PATH);
+
+ // helps to migrate existing vmd files to plugins\ml\views
+ if (!mig)
+ {
+ metafnold[0] = metafnnew[0] = 0;
+ PathCombineW(metafnold, g_tableDir, metafn);
+ PathCombineW(metafnnew, g_viewsDir, metafn);
+ if (!PathFileExistsW(metafnnew) && PathFileExistsW(metafnold))
+ {
+ MoveFileW(metafnold, metafnnew);
+ }
+ }
+
+ // see if we've got a name match to one of the defaults...
+ for(int j = 0; j < sizeof(def_names)/sizeof(def_names[0]); j++)
+ {
+ if(!lstrcmpiW(unicodeName, def_names[j]))
+ {
+ WASABI_API_LNGSTRINGW_BUF(def_str_ids[j], unicodeNameLoc, 256);
+ break;
+ }
+ }
+
+ addQueryItem((unicodeNameLoc[0]?unicodeNameLoc:unicodeName), unicodeVal, mode, 0, metafn, max(g_config->ReadInt(qbmp, -1), -1));
+ }
+
+ int aapos = g_config->ReadInt(L"view_autoadd_pos", 0);
+
+ if (aapos < 1)
+ {
+ if (!nb) // lame defaults added
+ {
+ meta_add_dirty = 1;
+ addQueryItem( WASABI_API_LNGSTRINGW( IDS_AUDIO ), L"type = 0", 1, 0, L"", TREE_IMAGE_LOCAL_AUDIO ); // new defaults
+ }
+
+ typedef struct
+ {
+ int title;
+ wchar_t *query;
+ char sort_by;
+ char sort_dir;
+ char *columns; //xff terminated list :)
+ int imageIndex;
+ }
+ addstruct;
+
+ addstruct m[] =
+ {
+ {IDS_VIDEO, L"type = 1", 10, 0, "\x7\1\5\x1E\6\3\x20\x8\x9\xA\xff", TREE_IMAGE_LOCAL_VIDEO},
+ {IDS_MOST_PLAYED, L"playcount > 0", 9, 0, "\x9\0\1\2\3\xA\xff", TREE_IMAGE_LOCAL_MOSTPLAYED},
+ {IDS_RECENTLY_ADDED, L"dateadded > [3 days ago]", 33, 0, "\x21\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYADDED},
+ {IDS_RECENTLY_MODIFIED, L"lastupd > [3 days ago]", 11, 0, "\xB\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYMODIFIED},
+ {IDS_RECENTLY_PLAYED, L"lastplay > [2 weeks ago]", 10, 0, "\xA\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYPLAYED},
+ {IDS_NEVER_PLAYED, L"playcount = 0 | playcount isempty", 0, 0, "\0\1\2\3\xff", TREE_IMAGE_LOCAL_NEVERPLAYED},
+ {IDS_TOP_RATED, L"rating >= 3", 8, 0, "\x8\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_TOPRATED},
+ };
+ if (aapos < 1)
+ {
+ int x;
+ for (x = 0; x < sizeof(m) / sizeof(m[0]); x++)
+ {
+ if (!x && nb) continue;
+
+ wchar_t filename[1024 + 256] = {0}, *ptr = 0;
+ makemetafn(filename, &ptr);
+
+ if (filename[0])
+ {
+ C_Config foo(filename);
+ foo.WriteInt(L"mv_sort_by", m[x].sort_by);
+ foo.WriteInt(L"mv_sort_dir", m[x].sort_dir);
+ int cnt = 0;
+ while ((unsigned char)m[x].columns[cnt] != 0xff)
+ {
+ wchar_t buf[32] = {0};
+ StringCchPrintfW(buf, 32, L"column%d", cnt);
+ foo.WriteInt(buf, (unsigned char)m[x].columns[cnt]);
+ cnt++;
+ }
+
+ foo.WriteInt(L"nbcolumns", cnt);
+ meta_add_dirty = 1;
+ addQueryItem(WASABI_API_LNGSTRINGW(m[x].title), m[x].query, 0, 0, ptr, m[x].imageIndex);
+ }
+
+ free(ptr);
+ }
+ }
+
+ g_config->WriteInt(L"view_autoadd_pos", 1);
+ }
+
+ if (meta_add_dirty)
+ saveQueryTree();
+}
+
+void saveQueryTree()
+{
+ int nb = g_config->ReadInt( L"query_num", 0 );
+ QueryList::iterator iter;
+ int i = 1;
+ wchar_t qm[ 128 ] = { 0 }, qmet[ 128 ] = { 0 }, qbmp[ 128 ] = { 0 };
+ char qn[ 128 ] = { 0 }, qv[ 128 ] = { 0 };
+
+ for ( int curId = mediaLibrary.GetChildId( m_query_tree ); curId != 0; curId = mediaLibrary.GetNextId( curId ), i++ )
+ {
+ iter = m_query_list.find( curId );
+ if ( iter == m_query_list.end() )
+ continue;
+
+ if ( i <= nb )
+ {
+ do
+ {
+ wsprintfW( qm, L"query%i_mode", i );
+ } while ( g_config->ReadInt( qm, -1 ) == 32 && i++ );
+ }
+
+ wsprintfA( qn, "query%i_name", i );
+ wsprintfA( qv, "query%i_val", i );
+ g_config->WriteString( qn, 0 ); // erase these old config items
+ g_config->WriteString( qv, 0 ); // erase these old config items
+
+ wsprintfA( qn, "query%i_name_utf8", i );
+ wsprintfA( qv, "query%i_val_utf8", i );
+ wsprintfW( qm, L"query%i_mode", i );
+ wsprintfW( qmet, L"query%i_meta", i );
+ wsprintfW( qbmp, L"query%i_image", i );
+
+ queryItem *thisitem = iter->second;
+ if ( thisitem == NULL ) continue;
+
+ wchar_t charNameLoc[ 256 ] = { 0 };
+ // see if we've got a name match to one of the defaults...
+ for ( int j = 0; j < sizeof( def_names ) / sizeof( def_names[ 0 ] ); j++ )
+ {
+ if ( !lstrcmpiW( thisitem->name, WASABI_API_LNGSTRINGW( def_str_ids[ j ] ) ) )
+ {
+ lstrcpyn( charNameLoc, def_names[ j ], ARRAYSIZE( charNameLoc ) );
+ break;
+ }
+ }
+
+ g_config->WriteString( qn, ( charNameLoc[ 0 ] ? charNameLoc : AutoChar( thisitem->name, CP_UTF8 ) ) );
+ g_config->WriteString( qv, AutoChar( thisitem->query, CP_UTF8 ) );
+ g_config->WriteInt( qm, thisitem->mode );
+ g_config->WriteString( qmet, thisitem->metafn );
+ g_config->WriteInt( qbmp, max( thisitem->imgIndex, -1 ) );
+ }
+
+ --i;
+ if ( i < nb )
+ {
+ for ( int k = i + 1; k <= nb; k++ )
+ {
+ wsprintfW( qm, L"query%i_mode", k );
+
+ int mode = g_config->ReadInt( qm, -1 );
+ if ( mode == 32 )
+ i++;
+ else
+ {
+ wsprintfA( qn, "query%i_name", k );
+ wsprintfA( qv, "query%i_val", k );
+ wsprintfW( qm, L"query%i_mode", k );
+ wsprintfW( qmet, L"query%i_meta", k );
+ wsprintfW( qbmp, L"query%i_image", k );
+
+ g_config->WriteString( qn, NULL );
+ g_config->WriteString( qv, NULL );
+ g_config->WriteString( qm, NULL );
+ g_config->WriteString( qmet, NULL );
+ g_config->WriteString( qbmp, NULL );
+ }
+ }
+ }
+
+ g_config->WriteInt( L"query_num", i );
+}
+
+HTREEITEM g_treedrag_lastSel;
+
+HWND onTreeViewSelectChange(HWND hwnd)
+{
+ if ( !g_table )
+ openDb();
+
+ bgQuery_Stop();
+
+ int par = mediaLibrary.GetSelectedTreeItem();
+
+ // defaults
+ m_query_mode = par;
+ m_query = L"";
+
+ m_query_metafile = L"";
+ m_query_mode = 0;
+
+ if (par == m_query_tree) // set up default media view
+ {
+ m_query_metafile = L"default.vmd";
+ }
+ else
+ {
+ QueryList::iterator iter;
+ iter = m_query_list.find(par);
+ if (iter != m_query_list.end())
+ {
+ m_query = iter->second->query;
+ m_query_mode = iter->second->mode;
+ m_query_metafile = iter->second->metafn;
+ }
+ else
+ {
+ m_query_metafile = L"default.vmd";
+ }
+ }
+
+ return updateCurrentView(hwnd);
+}
+
+void add_pledit_to_library()
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITEPLAYLIST);
+ wchar_t *m3udir = (wchar_t *) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETM3UDIRECTORYW);
+ wchar_t filename[MAX_PATH] = {0};
+ PathCombineW(filename, m3udir, L"winamp.m3u8");
+
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(filename, &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(filename);
+ }
+}
+
+void add_to_library(HWND wndparent)
+{
+ ScanFolderBrowser browser;
+ browser.SetBckScanChecked(g_config->ReadInt(L"addinbg", 0));
+
+ if (browser.Browse(wndparent))
+ {
+ wchar_t path[MAX_PATH] = {0};
+ g_config->WriteInt(L"addinbg", browser.GetBckScanChecked());
+ SHGetPathFromIDListW(browser.GetPIDL(), path);
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(path, &guess, &meta, &rec, 0);
+
+ if (guess == -1)
+ guess = g_config->ReadInt(L"guessmode", 0);
+
+ if (meta == -1)
+ meta = g_config->ReadInt(L"usemetadata", 1);
+
+ if (g_config->ReadInt(L"addinbg", 0))
+ {
+ Scan_ScanFolderBackground(path, guess, meta, rec); // add our dir to scan :)
+ }
+ else
+ {
+ Scan_ScanFolder(wndparent, path, guess, meta, rec);
+
+ int par = mediaLibrary.GetSelectedTreeItem();
+ mediaLibrary.SelectTreeItem(par - 1);
+ mediaLibrary.SelectTreeItem(par);
+
+ if (IsWindow(m_curview_hwnd))
+ SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ }
+ }
+}
+
+void nukeLibrary(HWND hwndDlg)
+{
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(hwndDlg, WASABI_API_LNGSTRINGW(IDS_REMOVE_ALL_ITEMS_IN_LIBRARY),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,titleStr,32),
+ MB_YESNO | MB_ICONQUESTION) == IDYES)
+ {
+ wchar_t *last_query = NULL;
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ bgQuery_Stop();
+ Scan_Cancel();
+
+ int count = 0;
+ wchar_t **filenames = 0;
+ nde_scanner_t clearedScanner = NDE_Table_CreateScanner(g_table);
+ if (clearedScanner)
+ {
+ filenames = new wchar_t * [(count = NDE_Scanner_GetRecordsCount(clearedScanner))];
+ int i = 0;
+ for (NDE_Scanner_First(clearedScanner); !NDE_Scanner_EOF(clearedScanner); NDE_Scanner_Next(clearedScanner))
+ {
+ nde_field_t fileName = NDE_Scanner_GetFieldByID(clearedScanner, MAINTABLE_ID_FILENAME);
+ if (fileName)
+ {
+ filenames[i] = NDE_StringField_GetString(fileName);
+ ndestring_retain(filenames[i]);
+ i++;
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, clearedScanner);
+
+ closeDb();
+
+ wchar_t tmp[MAX_PATH] = {0};
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\main.dat", g_tableDir);
+ DeleteFileW(tmp);
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\main.idx", g_tableDir);
+ DeleteFileW(tmp);
+
+ openDb();
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner)
+ {
+ m_media_scanner = NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+ DumpArtCache();
+ if (IsWindow(m_curview_hwnd))
+ SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+
+ // Wasabi event callback when the media library is cleared
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_CLEARED, (size_t)filenames, count);
+ if (filenames) delete[] filenames;
+
+ // trigger a refresh of the current view
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+}
+
+extern int main_sendto_mode;
+
+extern HMENU main_sendto_hmenu;
+
+
+wchar_t *itemrecordWTagFunc(wchar_t *tag, void * p) //return 0 if not found
+{
+ // TODO we can put more tags in here
+ itemRecordW *t = (itemRecordW *)p;
+ //bool copy=false;
+ wchar_t buf[128] = {0};
+ wchar_t *value = NULL;
+
+ if ( !_wcsicmp( tag, DB_FIELDNAME_artist ) )
+ value = t->artist;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_album ) )
+ value = t->album;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_filename ) )
+ value = t->filename;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_title ) )
+ value = t->title;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_year ) )
+ {
+ if ( t->year > 0 )
+ {
+ wsprintfW( buf, L"%04d", t->year );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_genre ) )
+ value = t->genre;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_comment ) )
+ value = t->comment;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_tracknumber ) || !_wcsicmp( tag, DB_FIELDNAME_track ) )
+ {
+ if ( t->track > 0 )
+ {
+ if ( t->tracks > 0 )
+ wsprintfW( buf, L"%02d/%02d", t->track, t->tracks );
+ else
+ wsprintfW( buf, L"%02d", t->track );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_disc ) )
+ {
+ if ( t->disc > 0 )
+ {
+ if ( t->discs > 0 )
+ wsprintfW( buf, L"%d/%d", t->disc, t->discs );
+ else
+ wsprintfW( buf, L"%d", t->disc );
+
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_rating ) )
+ {
+ if ( t->rating > 0 )
+ {
+ wsprintfW( buf, L"%d", t->rating );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_playcount ) )
+ {
+ if ( t->playcount > 0 )
+ {
+ wsprintfW( buf, L"%d", t->playcount );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bitrate ) )
+ {
+ if ( t->bitrate > 0 )
+ {
+ wsprintfW( buf, L"%d", t->bitrate );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bpm ) )
+ {
+ if ( t->bpm > 0 )
+ {
+ wsprintfW( buf, L"%d", t->bpm );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_albumartist ) )
+ value = t->albumartist;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_publisher ) )
+ value = t->publisher;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_composer ) )
+ value = t->composer;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_album_gain ) )
+ value = t->replaygain_album_gain;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_track_gain ) )
+ value = t->replaygain_track_gain;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteFileID ) )
+ value = getRecordExtendedItem_fast( t, extended_fields.GracenoteFileID );
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteExtData ) )
+ value = getRecordExtendedItem_fast( t, extended_fields.GracenoteExtData );
+ else
+ return 0;
+
+ if (!value)
+ return reinterpret_cast<wchar_t *>(-1);
+ else
+ {
+ if (/*copy || */value == buf)
+ return ndestring_wcsdup(value);
+ else
+ {
+ ndestring_retain(value);
+ return value;
+ }
+ }
+}
+
+static bool TagNameToFieldID(const wchar_t *tag, int *id)
+{
+ if ( !_wcsicmp( tag, DB_FIELDNAME_artist ) )
+ *id = MAINTABLE_ID_ARTIST;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_album ) )
+ *id = MAINTABLE_ID_ALBUM;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_filename ) )
+ *id = MAINTABLE_ID_FILENAME;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_title ) )
+ *id = MAINTABLE_ID_TITLE;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_year ) )
+ *id = MAINTABLE_ID_YEAR;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_genre ) )
+ *id = MAINTABLE_ID_GENRE;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_comment ) )
+ *id = MAINTABLE_ID_COMMENT;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_tracknumber ) || !_wcsicmp( tag, DB_FIELDNAME_track ) )
+ *id = MAINTABLE_ID_TRACKNB;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_rating ) )
+ *id = MAINTABLE_ID_RATING;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_playcount ) )
+ *id = MAINTABLE_ID_PLAYCOUNT;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bitrate ) )
+ *id = MAINTABLE_ID_BITRATE;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_disc ) )
+ *id = MAINTABLE_ID_DISC;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bpm ) )
+ *id = MAINTABLE_ID_BPM;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_albumartist ) )
+ *id = MAINTABLE_ID_ALBUMARTIST;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_publisher ) )
+ *id = MAINTABLE_ID_PUBLISHER;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_composer ) )
+ *id = MAINTABLE_ID_COMPOSER;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_album_gain ) )
+ *id = MAINTABLE_ID_ALBUMGAIN;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_track_gain ) )
+ *id = MAINTABLE_ID_TRACKGAIN;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteFileID ) )
+ *id = MAINTABLE_ID_GRACENOTEFILEID;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteExtData ) )
+ *id = MAINTABLE_ID_GRACENOTEEXTDATA;
+ //else if (!_wcsicmp(tag, DB_FIELDNAME_lossless)) *id = MAINTABLE_ID_LOSSLESS;
+ else
+ return false;
+
+ return true;
+}
+
+wchar_t *fieldTagFunc(wchar_t * tag, void * p) //return 0 if not found
+{
+ nde_scanner_t s = (nde_scanner_t)p;
+ int id = -1;
+
+ if (!TagNameToFieldID(tag, &id))
+ return 0;
+
+ if (id >= 0)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (f)
+ switch (id)
+ {
+ case MAINTABLE_ID_YEAR:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0) return reinterpret_cast<wchar_t *>(-1);
+ wsprintfW(buf, L"%04d", l);
+ return ndestring_wcsdup(buf);
+ }
+ case MAINTABLE_ID_TRACKNB:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0) return reinterpret_cast<wchar_t *>(-1);
+ int tracks = db_getFieldInt(s, MAINTABLE_ID_TRACKS, -1);
+ if (tracks > 0)
+ wsprintfW(buf, L"%02d/%02d", l, tracks);
+ else
+ wsprintfW(buf, L"%02d", l);
+ return ndestring_wcsdup(buf);
+ }
+ case MAINTABLE_ID_DISC:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0) return reinterpret_cast<wchar_t *>(-1);
+ int discs = db_getFieldInt(s, MAINTABLE_ID_DISCS, -1);
+ if (discs > 0)
+ wsprintfW(buf, L"%d/%d", l, discs);
+ else
+ wsprintfW(buf, L"%d", l);
+
+ return ndestring_wcsdup(buf);
+ }
+ case MAINTABLE_ID_PLAYCOUNT:
+ asked_for_playcount = 1;
+ // fall through :)
+ case MAINTABLE_ID_BPM:
+ case MAINTABLE_ID_RATING:
+ case MAINTABLE_ID_BITRATE:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0)
+ return reinterpret_cast<wchar_t *>(-1);
+
+ wsprintfW(buf, L"%d", l);
+ return ndestring_wcsdup(buf);
+ }
+ default:
+ {
+ wchar_t *p = NDE_StringField_GetString(f);
+ if (!p || !*p)
+ return reinterpret_cast<wchar_t *>(-1);;
+
+ ndestring_retain(p);
+ return p;
+ }
+ }
+
+ }
+ return 0;
+}
+
+void ndeTagFuncFree(wchar_t * tag, void * p)
+{
+ ndestring_release(tag);
+}
+
+void RetypeFilename(nde_table_t table);
+void RefreshFileSizeAndDateAddedTable(nde_table_t table);
+void ReindexTable(nde_table_t table);
+
+static void CreateFields( nde_table_t table )
+{
+ // create defaults
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_FILENAME, DB_FIELDNAME_filename, FIELD_FILENAME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TITLE, DB_FIELDNAME_title, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ARTIST, DB_FIELDNAME_artist, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ALBUM, DB_FIELDNAME_album, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_YEAR, DB_FIELDNAME_year, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GENRE, DB_FIELDNAME_genre, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_COMMENT, DB_FIELDNAME_comment, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TRACKNB, DB_FIELDNAME_trackno, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LENGTH, DB_FIELDNAME_length, FIELD_LENGTH );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TYPE, DB_FIELDNAME_type, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LASTUPDTIME, DB_FIELDNAME_lastupd, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LASTPLAY, DB_FIELDNAME_lastplay, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_RATING, DB_FIELDNAME_rating, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GRACENOTE_ID, DB_FIELDNAME_tuid2, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PLAYCOUNT, DB_FIELDNAME_playcount, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_FILETIME, DB_FIELDNAME_filetime, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_FILESIZE, DB_FIELDNAME_filesize, FIELD_INT64 );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_BITRATE, DB_FIELDNAME_bitrate, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DISC, DB_FIELDNAME_disc, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ALBUMARTIST, DB_FIELDNAME_albumartist, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ALBUMGAIN, DB_FIELDNAME_replaygain_album_gain, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TRACKGAIN, DB_FIELDNAME_replaygain_track_gain, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PUBLISHER, DB_FIELDNAME_publisher, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_COMPOSER, DB_FIELDNAME_composer, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_BPM, DB_FIELDNAME_bpm, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DISCS, DB_FIELDNAME_discs, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TRACKS, DB_FIELDNAME_tracks, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ISPODCAST, DB_FIELDNAME_ispodcast, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PODCASTCHANNEL, DB_FIELDNAME_podcastchannel, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PODCASTPUBDATE, DB_FIELDNAME_podcastpubdate, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GRACENOTEFILEID, DB_FIELDNAME_GracenoteFileID, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GRACENOTEEXTDATA, DB_FIELDNAME_GracenoteExtData, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LOSSLESS, DB_FIELDNAME_lossless, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_CATEGORY, DB_FIELDNAME_category, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_CODEC, DB_FIELDNAME_codec, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DIRECTOR, DB_FIELDNAME_director, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PRODUCER, DB_FIELDNAME_producer, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_WIDTH, DB_FIELDNAME_width, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_HEIGHT, DB_FIELDNAME_height, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_MIMETYPE, DB_FIELDNAME_mimetype, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DATEADDED, DB_FIELDNAME_dateadded, FIELD_DATETIME );
+
+ NDE_Table_PostColumns(table);
+ NDE_Table_AddIndexByIDW(table, MAINTABLE_ID_FILENAME, DB_FIELDNAME_filename );
+}
+
+int openDb()
+{
+ // TODO: fix!! this is a Double-Checked Lock Pattern and can have strange results
+ // in weird conditions because g_table is assigned before fully initialized
+ if ( g_table )
+ return 0;
+
+ EnterCriticalSection(&g_db_cs);
+
+ // benski> i know this looks redundant, but we might have sat and blocked at the above Critical Section for a while
+ if (g_table)
+ {
+ LeaveCriticalSection(&g_db_cs);
+
+ return 0;
+ }
+
+ if ( !g_db )
+ {
+ __try
+ {
+ g_db = NDE_CreateDatabase( plugin.hDllInstance );
+ }
+ __except ( EXCEPTION_EXECUTE_HANDLER )
+ {
+ g_db = NULL;
+ LeaveCriticalSection( &g_db_cs );
+
+ return 0;
+ }
+ }
+
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(indexName, inidir, L"Plugins\\ml");
+ PathCombineW(tableName, indexName, L"main.dat");
+ PathAppendW(indexName, L"main.idx");
+
+ g_table = NDE_Database_OpenTable(g_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
+ if (g_table)
+ {
+ ((Table *)g_table)->EnableRowCache(); // TODO: don't use c++ NDE API
+ CreateFields(g_table);
+ RetypeFilename(g_table);
+
+ #define REINDEX_KEY L"reindex_561"
+ if ( !g_config->ReadInt( REINDEX_KEY, 0 ) ) // do we need to reindex?
+ ReindexTable( g_table );
+
+ g_config->WriteInt(REINDEX_KEY, 1);
+
+ #undef REINDEX_KEY
+ #define REINDEX_KEY L"reindex_564"
+ if ( g_config->ReadInt( REINDEX_KEY, 0 ) != 2 ) // do we need to update the filesizes and date added?
+ RefreshFileSizeAndDateAddedTable( g_table );
+
+ g_config->WriteInt(REINDEX_KEY, 2);
+
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, NDE_Table_GetRecordsCount(g_table), IPC_STATS_LIBRARY_ITEMCNT);
+ }
+
+ LeaveCriticalSection(&g_db_cs);
+
+ return (g_table != 0);
+}
+
+// TODO make sure we're only ever saving if there was an actual change!!
+void closeDb()
+{
+ if ( g_db )
+ {
+ __try
+ {
+ if ( g_table )
+ {
+ if ( g_table_dirty )
+ NDE_Table_Sync( g_table );
+
+ NDE_Database_CloseTable( g_db, g_table );
+ }
+
+ NDE_DestroyDatabase( g_db );
+ }
+ __except ( EXCEPTION_EXECUTE_HANDLER )
+ {
+ }
+ }
+
+ g_db = NULL;
+ g_table = NULL;
+}
+
+LPCWSTR WINAMP_INI = NULL;
+WNDPROC ml_oldWndProc = NULL;
+LARGE_INTEGER freq;
+
+int init()
+{
+ QueryPerformanceFrequency(&freq);
+
+ g_table = NULL;
+ g_db = NULL;
+ g_bgscan_last_rescan = time( NULL );
+
+ LPCWSTR dir = (LPCWSTR )SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW);
+ if ( (INT_PTR)( dir ) < 65536 )
+ return 1;
+
+ PathCombineW(g_path, dir, L"Plugins");
+ CreateDirectoryW(g_path, NULL);
+
+ WINAMP_INI = (LPCWSTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIFILEW);
+
+ wchar_t configName[MAX_PATH] = {0};
+ PathCombineW(configName, g_path, L"gen_ml.ini");
+ g_config = new C_Config(configName);
+
+ g_bgrescan_int = g_config->ReadInt(L"bgrescan_int", g_bgrescan_int);
+ g_bgrescan_do = g_config->ReadInt(L"bgrescan_do", g_bgrescan_do);
+ g_bgrescan_force = g_config->ReadInt(L"bgrescan_startup", 0); // temporarily used
+ g_guessifany = g_config->ReadInt(L"guessifany", g_guessifany);
+ g_viewnotplay = g_config->ReadInt(L"viewnotplay", g_viewnotplay);
+
+ // this allows an override of the delay from making a change in the search box
+ // in the views to when the search will be run - sometimes needs tweaking for
+ // either older machines or some of the less powerful 'portable' type machines
+ g_querydelay = g_config->ReadInt(L"querydelay", g_querydelay);
+ if ( g_querydelay < 1 || g_querydelay > 5000 )
+ g_querydelay = 250;
+
+ PathCombineW(g_tableDir, g_path, L"ml");
+ PathCombineW(g_viewsDir, g_tableDir, L"views");
+
+ if (!g_config->ReadInt(L"artdbmig", 0))
+ {
+ MigrateArtCache();
+ g_config->WriteInt(L"artdbmig", 1);
+ }
+
+ wa_oldWndProc = (WNDPROC) SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc);
+
+ if ( g_bgrescan_force || g_config->ReadInt( L"dbloadatstart", 1 ) )
+ openDb();
+
+ HMENU wa_plcontext_menu = GetSubMenu((HMENU)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)-1, IPC_GET_HMENU), 2);
+ if ( wa_plcontext_menu )
+ wa_playlists_cmdmenu = GetSubMenu( wa_plcontext_menu, 4 );
+
+ wa_play_menu = GetSubMenu((HMENU)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_GET_HMENU), 2);
+
+ // lets extend menu that called on button press
+ IPC_GET_ML_HMENU = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibraryGetHmenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ g_context_menus = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ g_context_menus2 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+
+ HMENU rate_hmenu = GetSubMenu(GetSubMenu(g_context_menus,1),4);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ HMENU context_menu = (HMENU) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_ML_HMENU);
+
+ if (context_menu)
+ {
+ HMENU btnMenu = GetSubMenu(context_menu, 0);
+ if (btnMenu)
+ {
+ MENUITEMINFOW mii = {sizeof(MENUITEMINFOW)};
+
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ mii.fState = MFS_ENABLED;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.fMask = MIIM_TYPE | MIIM_ID;
+ mii.fType = MFT_STRING;
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_NEW_SMART_VIEW);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_DOSHITMENU_ADDNEWVIEW;
+ InsertMenuItemW(btnMenu, 1, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_RESCAN_WATCH_FOLDERS);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_RESCANFOLDERSNOW;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_ADD_PLEDIT_TO_LOCAL_MEDIA);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_ADD_PLEDIT;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_ADD_MEDIA_TO_LIBRARY);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_ADD_DIRS;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_REMOVE_MISSING_FILES_FROM_ML);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_REMOVE_UNUSED_FILES;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+ }
+ }
+
+ IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_GET_CLOUD_ACTIVE = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudActive", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ ml_oldWndProc = (WNDPROC) SetWindowLongPtrW(plugin.hwndLibraryParent, GWLP_WNDPROC, (LONG_PTR)ml_newWndProc);
+
+ HookPlaylistEditor();
+ hDragNDropCursor = LoadCursor(GetModuleHandle(L"gen_ml.dll"), MAKEINTRESOURCE(ML_IDC_DRAGDROP));
+
+ // rescan timer
+ SetTimer( plugin.hwndLibraryParent, 200, 1000, NULL );
+
+ return 0;
+}
+
+int OnLocalMediaItemClick( int action, int item, HWND parent )
+{
+ switch ( action )
+ {
+ case ML_ACTION_ENTER:
+ case ML_ACTION_DBLCLICK:
+ {
+ queryItem *qitem = m_query_list[ item ];
+ if ( qitem != NULL )
+ {
+ wchar_t configDir[ MAX_PATH ] = { 0 };
+ PathCombineW( configDir, g_viewsDir, qitem->metafn );
+ C_Config viewconf( configDir );
+ main_playQuery( &viewconf, qitem->query, ( ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ) ^ ( !!g_config->ReadInt( L"enqueuedef", 0 ) ) ) );
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+int OnLocalMediaClick(int action, HWND parent)
+{
+ switch (action)
+ {
+ case ML_ACTION_ENTER:
+ case ML_ACTION_DBLCLICK:
+ return 1;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.h b/Src/Plugins/Library/ml_local/ml_local.h
new file mode 100644
index 00000000..b607607c
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.h
@@ -0,0 +1,403 @@
+#ifndef ML_LOCAL_HEADER
+#define ML_LOCAL_HEADER
+
+#include <windows.h>
+#include <commctrl.h>
+
+#include <iostream> // for std::wstring
+
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/config.h"
+
+#include <map>
+#include "../nde/nde_c.h"
+#include <vector>
+
+struct ExtendedFields
+{
+ const wchar_t *ispodcast;
+ const wchar_t *podcastchannel;
+ const wchar_t *podcastpubdate;
+ const wchar_t *GracenoteFileID;
+ const wchar_t *GracenoteExtData;
+ const wchar_t *lossless;
+ const wchar_t *codec;
+ const wchar_t *director;
+ const wchar_t *producer;
+ const wchar_t *width;
+ const wchar_t *height;
+ const wchar_t *mimetype;
+ const wchar_t *realsize;
+ const wchar_t *dateadded;
+ const wchar_t *cloud;
+};
+extern const ExtendedFields extended_fields;
+
+extern const int NUM_EXTRA_COLSW;
+extern const unsigned char extra_idsW[]; // defined in view_media.cpp
+extern const wchar_t *extra_strsW[];
+
+extern HCURSOR hDragNDropCursor;
+wchar_t *getRecordExtendedItem_fast(const itemRecordW *item, const wchar_t *name);
+void setRecordExtendedItem_fast(itemRecordW *item, const wchar_t *name, const wchar_t *value);
+
+int OnLocalMediaItemClick(int action, int item, HWND parent);
+int OnLocalMediaClick(int action, HWND parent);
+BOOL IPC_HookExtInfo(INT_PTR param);
+BOOL IPC_HookExtInfoW(INT_PTR param);
+BOOL IPC_HookTitleInfo(INT_PTR param);
+
+#include "../winamp/wa_ipc.h"
+
+
+#define INT_ENTRY_MAX_NUM 20
+#define INT_ENTRY_MAX_PATHSIZE 512
+#define INT_ENTRY_MAX_TEXTSIZE 128
+
+#define TREE_IMAGE_LOCAL_AUDIO 101
+#define TREE_IMAGE_LOCAL_VIDEO 102
+#define TREE_IMAGE_LOCAL_MOSTPLAYED 103
+#define TREE_IMAGE_LOCAL_RECENTLYADDED 104
+#define TREE_IMAGE_LOCAL_RECENTLYPLAYED 105
+#define TREE_IMAGE_LOCAL_NEVERPLAYED 106
+#define TREE_IMAGE_LOCAL_TOPRATED 107
+#define TREE_IMAGE_LOCAL_PODCASTS 108
+#define TREE_IMAGE_LOCAL_RECENTLYMODIFIED 109
+
+
+#define MAINTABLE_ID_FILENAME 0
+#define MAINTABLE_ID_TITLE 1
+#define MAINTABLE_ID_ARTIST 2
+#define MAINTABLE_ID_ALBUM 3
+#define MAINTABLE_ID_YEAR 4
+#define MAINTABLE_ID_GENRE 5
+#define MAINTABLE_ID_COMMENT 6
+#define MAINTABLE_ID_TRACKNB 7
+#define MAINTABLE_ID_LENGTH 8 // in seconds
+#define MAINTABLE_ID_TYPE 9 // 0=audio, 1=video
+#define MAINTABLE_ID_LASTUPDTIME 10 // last time (seconds since 1970) of db update of this item
+#define MAINTABLE_ID_LASTPLAY 11 // last time (seconds since 1970) of last play
+#define MAINTABLE_ID_RATING 12
+#define MAINTABLE_ID_GRACENOTE_ID 14 // OLD Gracenote ID's. Don't use this anymore!!!
+#define MAINTABLE_ID_PLAYCOUNT 15 // play count
+#define MAINTABLE_ID_FILETIME 16 // file time
+#define MAINTABLE_ID_FILESIZE 17 // file size, bytes (was kilobytes until 5.7)
+#define MAINTABLE_ID_BITRATE 18 // file bitratea, kbps
+#define MAINTABLE_ID_DISC 19 // disc #
+#define MAINTABLE_ID_ALBUMARTIST 20 // album artist
+#define MAINTABLE_ID_ALBUMGAIN 21 // album gain (replaygain)
+#define MAINTABLE_ID_TRACKGAIN 22 // track gain (replaygain)
+#define MAINTABLE_ID_PUBLISHER 23 // publisher (record label)
+#define MAINTABLE_ID_COMPOSER 24 // composer
+#define MAINTABLE_ID_BPM 25 // beats per minute (tempo)
+#define MAINTABLE_ID_DISCS 26 // number of discs total
+#define MAINTABLE_ID_TRACKS 27 // number of tracks total
+#define MAINTABLE_ID_ISPODCAST 28
+#define MAINTABLE_ID_PODCASTCHANNEL 29
+#define MAINTABLE_ID_PODCASTPUBDATE 30
+#define MAINTABLE_ID_GRACENOTEFILEID 31
+#define MAINTABLE_ID_GRACENOTEEXTDATA 32
+#define MAINTABLE_ID_LOSSLESS 33
+#define MAINTABLE_ID_CATEGORY 34
+#define MAINTABLE_ID_CODEC 35
+#define MAINTABLE_ID_DIRECTOR 36
+#define MAINTABLE_ID_PRODUCER 37
+#define MAINTABLE_ID_WIDTH 38
+#define MAINTABLE_ID_HEIGHT 39
+#define MAINTABLE_ID_MIMETYPE 40
+#define MAINTABLE_ID_DATEADDED 41 // time file was added to the db
+
+// menu command id
+#define IDM_DOSHITMENU_ADDNEWVIEW 40030
+#define IDM_RESCANFOLDERSNOW 4066
+#define IDM_ADD_DIRS 4067
+#define IDM_REMOVE_UNUSED_FILES 4068
+#define IDM_ADD_PLEDIT 4069
+
+
+
+static const std::wstring _DB_FIELDNAME_tracknumber = L"tracknumber"; static const wchar_t *DB_FIELDNAME_tracknumber = _DB_FIELDNAME_tracknumber.c_str();
+static const std::wstring _DB_FIELDNAME_track = L"track"; static const wchar_t *DB_FIELDNAME_track = _DB_FIELDNAME_track.c_str();
+
+static const std::wstring _DB_FIELDNAME_filename = L"filename"; static const wchar_t *DB_FIELDNAME_filename = _DB_FIELDNAME_filename.c_str();
+static const std::wstring _DB_FIELDNAME_title = L"title"; static const wchar_t *DB_FIELDNAME_title = _DB_FIELDNAME_title.c_str();
+static const std::wstring _DB_FIELDNAME_artist = L"artist"; static const wchar_t *DB_FIELDNAME_artist = _DB_FIELDNAME_artist.c_str();
+static const std::wstring _DB_FIELDNAME_album = L"album"; static const wchar_t *DB_FIELDNAME_album = _DB_FIELDNAME_album.c_str();
+static const std::wstring _DB_FIELDNAME_year = L"year"; static const wchar_t *DB_FIELDNAME_year = _DB_FIELDNAME_year.c_str();
+static const std::wstring _DB_FIELDNAME_genre = L"genre"; static const wchar_t *DB_FIELDNAME_genre = _DB_FIELDNAME_genre.c_str();
+static const std::wstring _DB_FIELDNAME_comment = L"comment"; static const wchar_t *DB_FIELDNAME_comment = _DB_FIELDNAME_comment.c_str();
+static const std::wstring _DB_FIELDNAME_trackno = L"trackno"; static const wchar_t *DB_FIELDNAME_trackno = _DB_FIELDNAME_trackno.c_str();
+static const std::wstring _DB_FIELDNAME_length = L"length"; static const wchar_t *DB_FIELDNAME_length = _DB_FIELDNAME_length.c_str();
+static const std::wstring _DB_FIELDNAME_type = L"type"; static const wchar_t *DB_FIELDNAME_type = _DB_FIELDNAME_type.c_str();
+static const std::wstring _DB_FIELDNAME_lastupd = L"lastupd"; static const wchar_t *DB_FIELDNAME_lastupd = _DB_FIELDNAME_lastupd.c_str();
+static const std::wstring _DB_FIELDNAME_lastplay = L"lastplay"; static const wchar_t *DB_FIELDNAME_lastplay = _DB_FIELDNAME_lastplay.c_str();
+static const std::wstring _DB_FIELDNAME_rating = L"rating"; static const wchar_t *DB_FIELDNAME_rating = _DB_FIELDNAME_rating.c_str();
+static const std::wstring _DB_FIELDNAME_tuid2 = L"tuid2"; static const wchar_t *DB_FIELDNAME_tuid2 = _DB_FIELDNAME_tuid2.c_str();
+static const std::wstring _DB_FIELDNAME_playcount = L"playcount"; static const wchar_t *DB_FIELDNAME_playcount = _DB_FIELDNAME_playcount.c_str();
+static const std::wstring _DB_FIELDNAME_filetime = L"filetime"; static const wchar_t *DB_FIELDNAME_filetime = _DB_FIELDNAME_filetime.c_str();
+static const std::wstring _DB_FIELDNAME_filesize = L"filesize"; static const wchar_t *DB_FIELDNAME_filesize = _DB_FIELDNAME_filesize.c_str();
+static const std::wstring _DB_FIELDNAME_bitrate = L"bitrate"; static const wchar_t *DB_FIELDNAME_bitrate = _DB_FIELDNAME_bitrate.c_str();
+static const std::wstring _DB_FIELDNAME_disc = L"disc"; static const wchar_t *DB_FIELDNAME_disc = _DB_FIELDNAME_disc.c_str();
+static const std::wstring _DB_FIELDNAME_albumartist = L"albumartist"; static const wchar_t *DB_FIELDNAME_albumartist = _DB_FIELDNAME_albumartist.c_str();
+static const std::wstring _DB_FIELDNAME_replaygain_album_gain = L"replaygain_album_gain"; static const wchar_t *DB_FIELDNAME_replaygain_album_gain = _DB_FIELDNAME_replaygain_album_gain.c_str();
+static const std::wstring _DB_FIELDNAME_replaygain_track_gain = L"replaygain_track_gain"; static const wchar_t *DB_FIELDNAME_replaygain_track_gain = _DB_FIELDNAME_replaygain_track_gain.c_str();
+static const std::wstring _DB_FIELDNAME_publisher = L"publisher"; static const wchar_t *DB_FIELDNAME_publisher = _DB_FIELDNAME_publisher.c_str();
+static const std::wstring _DB_FIELDNAME_composer = L"composer"; static const wchar_t *DB_FIELDNAME_composer = _DB_FIELDNAME_composer.c_str();
+static const std::wstring _DB_FIELDNAME_bpm = L"bpm"; static const wchar_t *DB_FIELDNAME_bpm = _DB_FIELDNAME_bpm.c_str();
+static const std::wstring _DB_FIELDNAME_discs = L"discs"; static const wchar_t *DB_FIELDNAME_discs = _DB_FIELDNAME_discs.c_str();
+static const std::wstring _DB_FIELDNAME_tracks = L"tracks"; static const wchar_t *DB_FIELDNAME_tracks = _DB_FIELDNAME_tracks.c_str();
+static const std::wstring _DB_FIELDNAME_ispodcast = L"ispodcast"; static const wchar_t *DB_FIELDNAME_ispodcast = _DB_FIELDNAME_ispodcast.c_str();
+static const std::wstring _DB_FIELDNAME_podcastchannel = L"podcastchannel"; static const wchar_t *DB_FIELDNAME_podcastchannel = _DB_FIELDNAME_podcastchannel.c_str();
+static const std::wstring _DB_FIELDNAME_podcastpubdate = L"podcastpubdate"; static const wchar_t *DB_FIELDNAME_podcastpubdate = _DB_FIELDNAME_podcastpubdate.c_str();
+static const std::wstring _DB_FIELDNAME_GracenoteFileID = L"GracenoteFileID"; static const wchar_t *DB_FIELDNAME_GracenoteFileID = _DB_FIELDNAME_GracenoteFileID.c_str();
+static const std::wstring _DB_FIELDNAME_GracenoteExtData = L"GracenoteExtData"; static const wchar_t *DB_FIELDNAME_GracenoteExtData = _DB_FIELDNAME_GracenoteExtData.c_str();
+static const std::wstring _DB_FIELDNAME_lossless = L"lossless"; static const wchar_t *DB_FIELDNAME_lossless = _DB_FIELDNAME_lossless.c_str();
+static const std::wstring _DB_FIELDNAME_category = L"category"; static const wchar_t *DB_FIELDNAME_category = _DB_FIELDNAME_category.c_str();
+static const std::wstring _DB_FIELDNAME_codec = L"codec"; static const wchar_t *DB_FIELDNAME_codec = _DB_FIELDNAME_codec.c_str();
+static const std::wstring _DB_FIELDNAME_director = L"director"; static const wchar_t *DB_FIELDNAME_director = _DB_FIELDNAME_director.c_str();
+static const std::wstring _DB_FIELDNAME_producer = L"producer"; static const wchar_t *DB_FIELDNAME_producer = _DB_FIELDNAME_producer.c_str();
+static const std::wstring _DB_FIELDNAME_width = L"width"; static const wchar_t *DB_FIELDNAME_width = _DB_FIELDNAME_width.c_str();
+static const std::wstring _DB_FIELDNAME_height = L"height"; static const wchar_t *DB_FIELDNAME_height = _DB_FIELDNAME_height.c_str();
+static const std::wstring _DB_FIELDNAME_mimetype = L"mimetype"; static const wchar_t *DB_FIELDNAME_mimetype = _DB_FIELDNAME_mimetype.c_str();
+static const std::wstring _DB_FIELDNAME_dateadded = L"dateadded"; static const wchar_t *DB_FIELDNAME_dateadded = _DB_FIELDNAME_dateadded.c_str();
+
+
+extern BOOL myMenu;
+
+int init(void);
+void config(void);
+
+int treeGetParam(HTREEITEM h);
+
+class Table;
+class C_Config;
+extern CRITICAL_SECTION g_db_cs;
+extern nde_database_t g_db;
+extern nde_table_t g_table;
+extern int g_table_dirty;
+extern const wchar_t *WINAMP_INI;
+
+extern HWND m_curview_hwnd;
+
+extern wchar_t g_path[], g_tableDir[], g_viewsDir[];
+extern C_Config *g_config;
+
+extern HMENU g_context_menus, g_context_menus2;
+
+typedef struct
+{
+ wchar_t *name;
+ wchar_t *query;
+ wchar_t *metafn; //filename, without path, of meta file
+ int mode;
+ int imgIndex;
+ int index;
+} queryItem;
+
+typedef std::map <int, queryItem*> QueryList;
+extern QueryList m_query_list;
+extern C_Config *g_view_metaconf;
+extern int g_guessifany;
+extern int g_querydelay;
+extern int g_viewnotplay;
+
+void loadQueryTree();
+extern int m_query_tree;
+extern int m_query_mode;
+static wchar_t *m_query_metafile;
+HWND onTreeViewSelectChange(HWND hwnd);
+
+void db_setFieldStringW(nde_scanner_t s, unsigned char id, const wchar_t *data);
+void db_setFieldInt(nde_scanner_t s, unsigned char id, int data);
+void db_setFieldInt64(nde_scanner_t s, unsigned char id, __int64 data);
+int db_getFieldInt(nde_scanner_t s, unsigned char id, int defaultVal);
+void db_removeField(nde_scanner_t s, unsigned char id);
+
+void main_playQuery(C_Config *metaconf, const wchar_t *query, int enqueue, int startplaying=1); // enqueue =-1 sends it to the playlist
+void main_playItemRecordList (itemRecordListW *obj, int enqueue, int startplaying=1);
+int addQueryItem(const wchar_t *name, const wchar_t *val, int mode, int select, const wchar_t *metafn, int imageIndex, int num=-1);
+void replaceQueryItem(int n, const wchar_t *name, const wchar_t *val, int mode, int imageIndex);
+void saveQueryTree();
+
+int pluginHandleIpcMessage(int msg, int param);
+
+int openDb();
+void closeDb();
+
+void nukeLibrary(HWND hwndDlg);
+
+//add.cpp
+int addFileToDb(const wchar_t *filename, int onlyupdate, int use_metadata, int guess_mode, int playcnt=0, int lastplay=0, bool force=false); // adds a file to the db, gets info, etc.
+int RemoveFileFromDB(/*const Table *table, */const wchar_t *filename); // removes a file from the DB
+void makeFilename2(const char *filename, char *filename2, int filename2_len);
+void makeFilename2W(const wchar_t *filename, wchar_t *filename2, int filename2_len);
+
+//gracenote.cpp
+void gracenoteInit();
+int gracenoteQueryFile(const wchar_t *filename);
+void gracenoteCancelRequest();
+int gracenoteDoTimerStuff();
+void gracenoteSetValues(const wchar_t *artist, const wchar_t *album, const wchar_t *title);
+const wchar_t *gracenoteGetTuid();
+int gracenoteIsWorking();
+
+//guess.cpp
+wchar_t *guessTitles(const wchar_t *filename,
+ int *tracknum,
+ wchar_t **artist,
+ wchar_t **album,
+ wchar_t **title); // should free the result of this function after using artist/album/title
+
+//prefs.cpp
+INT_PTR CALLBACK PrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int autoscan_add_directory(const wchar_t *path, int *guess, int *meta, int *recurse, int noaddjustcheck);// if we return 1, guess and meta will be filled in
+void refreshPrefs(int screen);
+
+//util.cpp
+extern "C" {
+ void process_substantives(wchar_t* dest);
+ void ConvertRatingMenuStar(HMENU menu, UINT menu_id);
+};
+
+//view_audio.cpp
+INT_PTR CALLBACK view_audioDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+//view_miniinfo.cpp
+INT_PTR CALLBACK view_miniinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+//view_errorinfo.cpp
+INT_PTR CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+/* bgscan.cpp */
+void Scan_ScanFolderBackground(const wchar_t *path, int guess, int meta, int recurse);
+void Scan_ScanFolder(HWND parent, const wchar_t *path, int guess, int meta, int recurse);
+// When you call Scan_ScanFolders, it will own the memory and release it with free()
+void Scan_ScanFolders(HWND parent, size_t count, wchar_t **paths, int *guess, int *meta, int *recurse);
+void Scan_BackgroundScan();
+void Scan_Cancel();
+void Scan_Kill();
+// remove missing files
+void Scan_RemoveFiles(HWND parent);
+
+//view_media.cpp
+void makeQueryStringFromText(GayStringW *query, wchar_t *text, int nf=8);
+inline BOOL WINAPI IsCharSpaceA(char c) { return (c == ' ' || c == '\t'); }
+inline BOOL WINAPI IsCharSpaceW(wchar_t c) { return (c == L' ' || c == L'\t'); }
+inline bool IsThe(const char *str) { if (str && (str[0] == 't' || str[0] == 'T') && (str[1] == 'h' || str[1] == 'H') && (str[2] == 'e' || str[2] == 'E') && (str[3] == ' ')) return true; else return false; }
+__forceinline static bool IsTheW(const wchar_t *str)
+{
+ if ((str[0] & ~0x20) == L'T'
+ && (str[1] & ~0x20) == L'H'
+ && (str[2] & ~0x20) == L'E'
+ && str[3] == L' ')
+ return true;
+ else
+ return false;
+}
+#define SKIP_THE_AND_WHITESPACE(x) { char *save##x=(char*)x; while (IsCharSpaceA(*x) && *x) x++; if (IsThe(x)) x+=4; while (IsCharSpaceA(*x)) x++; if (!*x) x=save##x; }
+#define SKIP_THE_AND_WHITESPACEW(x) { wchar_t *save##x=(wchar_t*)x; while ((*x == L' ' || *x == L'\t') && *x) x++; if (IsTheW(x)) x+=4; while ((*x == L' ' || *x == L'\t')) x++; if (!*x) x=save##x; }
+//wherever this goes is fine
+
+#define UPDATE_QUERY_TIMER_ID 505
+#define UPDATE_RESULT_LIST_TIMER_ID 506
+INT_PTR CALLBACK view_mediaDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+int WCSCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+
+typedef void (*resultsniff_funcW)(itemRecordW *items, int numitems, int user32, int *killswitch);
+
+void bgQuery_Stop();
+extern nde_scanner_t m_media_scanner;
+typedef std::vector<wchar_t*> CloudFiles;
+
+// returns length in seconds (high bit on means could be much more), or -1 if killed
+int saveQueryToListW(C_Config *viewconf, nde_scanner_t s, itemRecordListW *obj,
+ CloudFiles *uploaded, CloudFiles *uploading,
+ resultsniff_funcW cb=0, int user32=0, int *killswitch=0, __int64 *total_bytes=0);
+
+// queries.cpp
+void view_queryContextMenu(INT_PTR param1, HWND hHost, POINTS pts, int item);
+void queriesContextMenu(INT_PTR param1, HWND hHost, POINTS pts);
+void queryEditItem(int n);
+void addNewQuery(HWND parent);
+void queryDeleteItem(HWND parent, int n);
+BOOL windowOffScreen(HWND hwnd, POINT pt);
+
+// handleMessage.cpp
+INT_PTR HandleIpcMessage(INT_PTR msg, INT_PTR param);
+extern "C" extern int (*warand)(void);
+
+extern WNDPROC wa_oldWndProc;
+
+wchar_t *itemrecordWTagFunc(wchar_t * tag, void * p);
+wchar_t *fieldTagFunc(wchar_t * tag, void * p); //return 0 if not found
+void ndeTagFuncFree(wchar_t * tag, void * p); // for NDE strings
+DWORD doGuessProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam );
+void TAG_FMT_EXT(const wchar_t *filename, void *f, void *ff, void *p, wchar_t *out, int out_len, int extended);
+extern int asked_for_playcount;
+LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+extern int m_calling_getfileinfo;
+extern HMENU wa_play_menu ;
+void add_pledit_to_library();
+void add_to_library(HWND wndparent);
+extern int g_bgscan_scanning, g_bgrescan_force, g_bgrescan_do, g_bgrescan_int;
+extern WNDPROC ml_oldWndProc;
+extern time_t g_bgscan_last_rescan;
+int runBGscan(int ms);
+void compactRecordList(itemRecordListW *obj);
+
+LRESULT APIENTRY ml_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+int FLOATCMP_NULLOK(const char *pa, const char *pb);
+int FLOATWCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+void ClearTitleHookCache();
+
+int FindFileInDatabase(nde_scanner_t s, int fieldId, const wchar_t *filename, wchar_t alternate[MAX_PATH]);
+
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordListW *obj, bool compat);
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordW *obj, bool compat);
+int updateFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *data);
+void sortResults(C_Config *viewconf, itemRecordListW *obj);
+void queryStrEscape(const char *raw, GayString &str);
+void queryStrEscape(const wchar_t *raw, GayStringW &str);
+void ParseIntSlashInt(wchar_t *string, int *part, int *parts);
+
+HWND updateCurrentView(HWND hwndDlg);
+
+#define MEDIAVIEW_COL_ARTIST 0
+#define MEDIAVIEW_COL_TITLE 1
+#define MEDIAVIEW_COL_ALBUM 2
+#define MEDIAVIEW_COL_LENGTH 3
+#define MEDIAVIEW_COL_TRACK 4
+#define MEDIAVIEW_COL_GENRE 5
+#define MEDIAVIEW_COL_YEAR 6
+#define MEDIAVIEW_COL_FILENAME 7
+#define MEDIAVIEW_COL_RATING 8
+#define MEDIAVIEW_COL_PLAYCOUNT 9
+#define MEDIAVIEW_COL_LASTPLAY 10
+#define MEDIAVIEW_COL_LASTUPD 11
+#define MEDIAVIEW_COL_FILETIME 12
+#define MEDIAVIEW_COL_COMMENT 13
+#define MEDIAVIEW_COL_FILESIZE 14
+#define MEDIAVIEW_COL_BITRATE 15
+#define MEDIAVIEW_COL_TYPE 16
+#define MEDIAVIEW_COL_DISC 17
+#define MEDIAVIEW_COL_ALBUMARTIST 18
+#define MEDIAVIEW_COL_FULLPATH 19
+#define MEDIAVIEW_COL_ALBUMGAIN 20
+#define MEDIAVIEW_COL_TRACKGAIN 21
+#define MEDIAVIEW_COL_PUBLISHER 22
+#define MEDIAVIEW_COL_COMPOSER 23
+#define MEDIAVIEW_COL_EXTENSION 24
+#define MEDIAVIEW_COL_ISPODCAST 25
+#define MEDIAVIEW_COL_PODCASTCHANNEL 26
+#define MEDIAVIEW_COL_PODCASTPUBDATE 27
+#define MEDIAVIEW_COL_BPM 28
+#define MEDIAVIEW_COL_CATEGORY 29
+#define MEDIAVIEW_COL_DIRECTOR 30
+#define MEDIAVIEW_COL_PRODUCER 31
+#define MEDIAVIEW_COL_DIMENSION 32
+#define MEDIAVIEW_COL_DATEADDED 33
+#define MEDIAVIEW_COL_CLOUD 34
+
+#define MEDIAVIEW_COL_NUMS 35 // number of columns
+#endif // ML_LOCAL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.rc b/Src/Plugins/Library/ml_local/ml_local.rc
new file mode 100644
index 00000000..0391a01a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.rc
@@ -0,0 +1,1244 @@
+// 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_VIEW_MEDIA DIALOGEX 0, 0, 289, 274
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Search:",IDC_SEARCHCAPTION,1,2,25,8,SS_CENTERIMAGE
+ EDITTEXT IDC_QUICKSEARCH,28,1,209,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_CLEAR,"Button",BS_OWNERDRAW | WS_TABSTOP,240,0,49,11
+ CONTROL "List4",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,14,289,246
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,35,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,38,261,35,11
+ CONTROL "MusicIP Mix",IDC_BUTTON_MIX,"Button",BS_OWNERDRAW | BS_CENTER | WS_TABSTOP,76,261,47,11
+ CONTROL "Generate Playlist",IDC_BUTTON_CREATEPLAYLIST,"Button",BS_OWNERDRAW | WS_TABSTOP,126,261,65,11
+ CONTROL "",IDC_MEDIASTATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,187,262,61,8
+ CONTROL "",IDC_MIXABLE,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,223,262,25,8
+ CONTROL "Show Info",IDC_BUTTON_INFOTOGGLE,"Button",BS_OWNERDRAW | WS_TABSTOP,249,261,40,11
+END
+
+IDD_VIEW_AUDIO DIALOGEX 0, 0, 291, 279
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "",IDC_BUTTON_ARTMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,0,1,14,11
+ CONTROL "",IDC_BUTTON_VIEWMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,16,1,20,11
+ CONTROL "",IDC_BUTTON_COLUMNS,"Button",BS_OWNERDRAW | WS_TABSTOP,37,1,20,11
+ EDITTEXT IDC_QUICKSEARCH,92,1,145,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_CLEAR,"Button",BS_OWNERDRAW | WS_TABSTOP,240,0,49,11
+ CONTROL "List1",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,14,83,89
+ CONTROL "List3",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,89,14,100,89
+ CONTROL "",IDC_LIST3,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,195,14,94,89
+ LTEXT "Search:",IDC_SEARCHCAPTION,64,2,25,8
+ CONTROL "",IDC_DIV1,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,82,14,6,91
+ CONTROL "",IDC_HDELIM,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,0,106,289,6
+ CONTROL "",IDC_DIV2,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,188,14,6,91
+END
+
+IDD_PREFS1 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Library Display Settings",IDC_STATIC,4,3,256,40
+ CONTROL "Display 'Show Info' in media and album views",IDC_CHECK3,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,15,162,10
+ CONTROL "Remember search filters in media and album views",IDC_REMEMBER_SEARCH,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,28,178,10
+ GROUPBOX "Recently Played",IDC_STATIC,4,46,256,57
+ CONTROL "Enable 'Recently Played' in the Library",IDC_CHECK2,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,58,159,10
+ PUSHBUTTON "Info...",IDC_BUTTON2,218,58,36,13
+ CONTROL "Wait",IDC_CHECK7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,72,30,8
+ EDITTEXT IDC_EDIT2,48,70,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "seconds before tracking items",IDC_STATIC4,76,72,101,8
+ CONTROL "Wait",IDC_CHECK8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,88,30,8
+ EDITTEXT IDC_EDIT3,48,86,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "percent of playback before tracking items",IDC_STATIC5,76,88,137,8
+ GROUPBOX "Advanced Library Preferences",IDC_STATIC,4,107,256,82
+ CONTROL "Use Library title information for Playlist Item Formatting",IDC_CHECK_ATF,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,120,193,10
+ CONTROL "Display 'Refine' search field in album views",IDC_CHECK5,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,132,151,10
+ CONTROL "Do not load the Library database at Winamp start-up",IDC_CHECK6,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,145,206,10
+ CONTROL "Use Artist as Album Artist if not available",IDC_ARTIST_AS_ALBUMARTIST,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,158,146,10
+ LTEXT "Search query delay:",IDC_STATIC_QUERYDELAY,10,173,71,10
+ EDITTEXT IDC_EDIT_QUERYDELAY,78,171,27,13,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "ms (default: 250)",IDC_STATIC,109,173,104,10
+ GROUPBOX "Clear Library",IDC_STATIC,4,190,256,35
+ LTEXT "Use this to remove all items from your Winamp Library. This will not physically remove any files from disk.",IDC_STATIC,10,202,176,18
+ PUSHBUTTON "Clear Library...",IDC_BUTTON1,190,202,64,18
+END
+
+IDD_VIEW_DB_ERROR DIALOGEX 0, 0, 194, 166
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CTEXT "",IDC_DB_ERROR,10,10,174,129,0x2000
+ CONTROL "Reset Database",IDC_RESET_DB_ON_ERROR,"Button",BS_OWNERDRAW | WS_TABSTOP,61,144,70,12
+END
+
+IDD_PREFSFR DIALOGEX 0, 0, 272, 246
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Tab1",IDC_TAB1,"SysTabControl32",WS_TABSTOP,0,0,271,246
+END
+
+IDD_VIEW_MINIINFO DIALOGEX 0, 0, 291, 279
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+END
+
+IDD_PREFS3 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Watch Folder Settings",IDC_STATIC,4,3,256,187
+ LTEXT "New media found in the following folders will be automatically added to the Library:",IDC_STATIC,10,14,243,16
+ CONTROL "List1",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,10,34,242,74
+ PUSHBUTTON "Add folder...",IDC_BUTTON1,10,111,60,13
+ PUSHBUTTON "Edit selected",IDC_BUTTON4,74,111,53,13
+ PUSHBUTTON "Remove selected",IDC_BUTTON3,188,111,64,13
+ CONTROL "Automatically add played files",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,128,107,10
+ CONTROL "Rescan folders at startup",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,140,95,10
+ CONTROL "Automatically remove missing files",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,152,119,10
+ CONTROL "Rescan folders every",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,164,80,11
+ EDITTEXT IDC_EDIT3,92,164,32,12,ES_AUTOHSCROLL | ES_NUMBER
+ CONTROL "",IDC_SPIN1,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,116,164,7,12
+ LTEXT "minutes",IDC_MINUTES,127,165,26,9,SS_CENTERIMAGE
+ PUSHBUTTON "Rescan now",IDC_RESCAN,188,163,64,13
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_PATHELLIPSIS | WS_GROUP,9,177,244,8
+ GROUPBOX "Metadata Reading Settings",IDC_STATIC,4,192,256,33
+ LTEXT "Click ""Configure"" to modify how title information is read when importing media files",IDC_STATIC,11,203,171,16
+ PUSHBUTTON "Configure",IDC_CONFMETA,188,205,64,13
+END
+
+IDD_EDITDIR DIALOGEX 0, 0, 262, 146
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Edit folder properties"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Folder path:",IDC_STATIC,7,8,38,8
+ EDITTEXT IDC_EDIT1,7,17,198,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BUTTON1,208,17,43,14
+ CONTROL "Add subfolders",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,35,63,10
+ LTEXT "Metadata / filename detection override options:",IDC_STATIC,7,46,165,8
+ CONTROL "Read file metadata tags if available (grey is default)",IDC_CHECK1,
+ "Button",BS_AUTO3STATE | WS_TABSTOP,24,56,197,10
+ GROUPBOX "Filename metadata detection mode (when no tags available):",IDC_STATIC,24,66,231,54
+ CONTROL "Default",IDC_RADIO6,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,30,76,39,10
+ CONTROL "Smart (Detects ""Artist - Album\\TrackNum - Artist - Title"" etc)",IDC_RADIO1,
+ "Button",BS_AUTORADIOBUTTON,30,86,207,10
+ CONTROL "Simple (Detects ""Artist\\Album\\Title"")",IDC_RADIO2,
+ "Button",BS_AUTORADIOBUTTON,30,96,137,10
+ CONTROL "No guessing (title is filename, other fields empty)",IDC_RADIO8,
+ "Button",BS_AUTORADIOBUTTON,30,105,165,10
+ DEFPUSHBUTTON "OK",IDOK,7,125,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,64,125,50,14
+END
+
+IDD_EDIT_QUERY DIALOGEX 0, 0, 322, 151
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Query Builder"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Builder",IDC_STATIC_GENERAL,7,7,308,102
+ GROUPBOX "Field :",IDC_STATIC,13,16,181,70
+ LISTBOX IDC_LIST_FIELDS,20,26,52,55,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Equal",IDC_RADIO_EQUAL,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,75,26,34,8
+ CONTROL "Above",IDC_RADIO_ABOVE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,34,37,8
+ CONTROL "Below",IDC_RADIO_BELOW,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,42,35,8
+ CONTROL "Above or Equal",IDC_RADIO_ABOVEOREQUAL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,50,61,8
+ CONTROL "Below or Equal",IDC_RADIO_BELOWOREQUAL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,58,61,8
+ CONTROL "Is not set",IDC_RADIO_ISEMPTY,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,141,25,44,8
+ CONTROL "Is like",IDC_RADIO_ISLIKE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,141,33,34,8
+ CONTROL "Begins with",IDC_RADIO_BEGINS,"Button",BS_AUTORADIOBUTTON,141,41,52,8
+ CONTROL "Ends in",IDC_RADIO_ENDS,"Button",BS_AUTORADIOBUTTON,141,49,39,8
+ CONTROL "Contains",IDC_RADIO_CONTAINS,"Button",BS_AUTORADIOBUTTON,141,57,39,8
+ CONTROL "Not",IDC_CHECK_NOT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,76,71,27,10
+ GROUPBOX "Compare to string",IDC_STATIC_STRING,198,16,112,29
+ EDITTEXT IDC_EDIT_STRING,202,27,104,13,ES_AUTOHSCROLL
+ GROUPBOX "Compare to date/time",IDC_STATIC_DATETIME,198,47,112,40
+ EDITTEXT IDC_EDIT_DATETIME,202,58,78,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit",IDC_BUTTON_EDITDATETIME,284,58,22,13
+ CTEXT "N/A",IDC_STATIC_CURDATETIME,202,74,104,9,SS_SUNKEN
+ LTEXT "Resulting expression :",IDC_STATIC,14,93,70,8
+ EDITTEXT IDC_EDIT_RESULT,85,90,180,14,ES_AUTOHSCROLL | ES_READONLY
+ PUSHBUTTON ">>",ID_BUTTON_SENDTOQUERY,268,90,41,14
+ PUSHBUTTON "AND",IDC_BUTTON_AND,268,90,19,14
+ PUSHBUTTON "OR",IDC_BUTTON_OR,290,90,19,14
+ LTEXT "Query :",IDC_STATIC_QUERY,7,115,24,8
+ EDITTEXT IDC_EDIT_QUERY,33,112,282,14,ES_AUTOHSCROLL
+ DEFPUSHBUTTON "OK",IDOK,211,130,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,265,130,50,14
+END
+
+IDD_EDIT_QUERY_PICK DIALOGEX 0, 0, 178, 78
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Pick date/time"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "DateTimePicker2",IDC_DATETIMEPICKER,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_SHOWNONE | DTS_LONGDATEFORMAT | WS_TABSTOP,20,13,145,15
+ CONTROL "DateTimePicker1",IDC_DATETIMEPICKER1,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_UPDOWN | DTS_SHOWNONE | WS_TABSTOP | 0x8,43,32,105,15
+ DEFPUSHBUTTON "OK",IDOK,67,57,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,121,57,50,14
+END
+
+IDD_CUSTCOLUMNS DIALOGEX 0, 0, 342, 154
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Customize columns"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Hidden Columns",IDC_STATIC,7,7,126,120
+ LISTBOX IDC_LIST2,13,17,114,104,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ DEFPUSHBUTTON "OK",IDOK,7,133,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,62,133,50,14
+ PUSHBUTTON "Add -->",IDC_BUTTON2,138,41,66,14,WS_DISABLED
+ PUSHBUTTON "<-- Remove",IDC_BUTTON3,138,59,66,14,WS_DISABLED
+ PUSHBUTTON "Restore Defaults",IDC_DEFS,138,107,66,14
+ GROUPBOX "Visible Columns",IDC_STATIC,208,7,126,140
+ LISTBOX IDC_LIST1,214,17,114,110,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Move up",IDC_BUTTON4,239,130,43,12,WS_DISABLED
+ PUSHBUTTON "Move down",IDC_BUTTON5,285,130,43,12,WS_DISABLED
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_NDE_RECOVERY$(DISABLED) DIALOGEX 0, 0, 261, 107
+#else
+IDD_NDE_RECOVERY DIALOGEX 0, 0, 261, 107
+#endif
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION
+CAPTION "Winamp5 Library Recovery"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Inconsistencies were detected while loading your Library database, we are now trying to correct them.",IDC_STATIC,13,8,235,18
+ CONTROL "Progress1",IDC_PROGRESS_PERCENT,"msctls_progress32",WS_BORDER,23,32,214,14
+ CTEXT "0%",IDC_STATIC_PERCENT,116,47,30,8
+ LTEXT "Total number of records:",IDC_STATIC,23,61,78,8
+ LTEXT "0",IDC_STATIC_TOTAL,106,61,47,8
+ LTEXT "Records recovered:",IDC_STATIC,23,72,64,8
+ LTEXT "0",IDC_STATIC_RECOVERED,106,72,47,8
+ LTEXT "Records lost:",IDC_STATIC,24,83,42,8
+ LTEXT "0",IDC_STATIC_LOST,106,83,47,8
+ DEFPUSHBUTTON "OK",IDOK,204,86,50,14
+END
+#endif
+
+IDD_NEEDADDFILES DIALOGEX 0, 0, 201, 105
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Add Media to Library"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "You currently have nothing in your library. To add media, select from the options below.",IDC_TEXT,7,7,187,17
+ PUSHBUTTON "Add Media to Library",ID_ADD_FILES,52,31,96,14
+ PUSHBUTTON "Import from iTunes",IDC_IMPORT_ITUNES,52,49,96,14,WS_DISABLED
+ CONTROL "Do not show me this again",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,70,99,10
+ PUSHBUTTON "Close",IDOK,7,85,50,13
+ CONTROL "Learn More About Winamp",IDC_BTN_LINK_PROMO,"Button",BS_OWNERDRAW | BS_RIGHT | BS_BOTTOM | WS_TABSTOP,73,85,121,13
+END
+
+IDD_SCROLLCHILD DIALOGEX 0, 0, 310, 17
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ PUSHBUTTON "+",IDC_BUTTON1,1,2,13,11
+END
+
+IDD_SCROLLCHILDHOST DIALOGEX 0, 0, 310, 101
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+END
+
+IDD_SCROLLCHILDFILTER DIALOGEX 0, 0, 310, 24
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPCHILDREN
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ PUSHBUTTON "-",IDC_BUTTON1,1,2,13,12
+ COMBOBOX IDC_COMBO1,17,2,72,183,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ COMBOBOX IDC_COMBO2,92,2,80,172,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ EDITTEXT IDC_EDIT1,173,2,128,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Choose Time",IDC_BUTTON2,251,1,50,12,NOT WS_VISIBLE
+ PUSHBUTTON "Query Builder",IDC_BUTTON_QUERYBUILD,251,1,50,12,NOT WS_VISIBLE
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDHORZ,1,19,12,1
+ CONTROL "and",IDC_AND,"Button",BS_AUTORADIOBUTTON,14,15,27,10,WS_EX_TRANSPARENT
+ CONTROL "or",IDC_OR,"Button",BS_AUTORADIOBUTTON,41,15,23,10,WS_EX_TRANSPARENT
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDHORZ,63,19,240,1
+END
+
+IDD_TIMEEDITOR DIALOGEX 0, 0, 281, 229
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Choose Time"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "",IDC_STATIC_ABSOLUTE,7,7,267,37
+ CONTROL "Absolute",IDC_CHECK_ABSOLUTE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,13,7,42,10,WS_EX_TRANSPARENT
+ CONTROL "DateTimePicker2",IDC_DATETIMEPICKER,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_SHOWNONE | WS_TABSTOP,13,20,145,15
+ CONTROL "DateTimePicker1",IDC_DATETIMEPICKER1,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_UPDOWN | DTS_SHOWNONE | WS_TABSTOP | 0x8,164,20,105,15
+ GROUPBOX "",IDC_STATIC_RELATIVE,7,46,267,141
+ CONTROL "Relative",IDC_CHECK_RELATIVE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,13,46,40,8,WS_EX_TRANSPARENT
+ GROUPBOX "",IDC_STATIC_TIMEAGO,13,55,255,32,WS_GROUP
+ CONTROL "Time ago",IDC_CHECK_TIMEAGO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,55,43,10,WS_EX_TRANSPARENT
+ EDITTEXT IDC_EDIT_TIMEAGO,19,66,29,14,ES_AUTOHSCROLL
+ CONTROL "Year(s)",IDC_RADIO_TIMEAGO_Y,"Button",BS_AUTORADIOBUTTON | WS_GROUP,53,65,37,8
+ CONTROL "Hour(s)",IDC_RADIO_TIMEAGO_H,"Button",BS_AUTORADIOBUTTON,53,74,37,8
+ CONTROL "Month(s)",IDC_RADIO_TIMEAGO_M,"Button",BS_AUTORADIOBUTTON,92,65,45,8
+ CONTROL "Minute(s)",IDC_RADIO_TIMEAGO_MIN,"Button",BS_AUTORADIOBUTTON,92,74,46,8
+ CONTROL "Week(s)",IDC_RADIO_TIMEAGO_W,"Button",BS_AUTORADIOBUTTON,137,65,41,8
+ CONTROL "Second(s)",IDC_RADIO_TIMEAGO_S,"Button",BS_AUTORADIOBUTTON,137,74,52,8
+ CONTROL "Day(s)",IDC_RADIO_TIMEAGO_D,"Button",BS_AUTORADIOBUTTON,180,65,33,8
+ GROUPBOX "From Origin",IDC_STATIC_DIRECTION,215,55,53,32
+ CONTROL "After",IDC_RADIO_AFTER,"Button",BS_AUTORADIOBUTTON | WS_GROUP,221,65,31,8
+ CONTROL "Before",IDC_RADIO_BEFORE,"Button",BS_AUTORADIOBUTTON,221,74,34,8
+ GROUPBOX "",IDC_STATIC_SELECTIVE,13,90,255,79,WS_GROUP
+ CONTROL "Origin",IDC_CHECK_SELECTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,90,32,10,WS_EX_TRANSPARENT
+ GROUPBOX "",IDC_STATIC_YEAR,17,98,79,34,WS_GROUP
+ CONTROL "This year",IDC_RADIO_THISYEAR,"Button",BS_AUTORADIOBUTTON | WS_GROUP,23,106,45,9
+ CONTROL "Year :",IDC_RADIO_YEAR,"Button",BS_AUTORADIOBUTTON,23,116,30,10
+ EDITTEXT IDC_EDIT_YEAR,56,116,36,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_STATIC_MONTH,98,98,88,34
+ CONTROL "This month",IDC_RADIO_THISMONTH,"Button",BS_AUTORADIOBUTTON | WS_GROUP,104,106,51,9
+ CONTROL "Month :",IDC_RADIO_MONTH,"Button",BS_AUTORADIOBUTTON,104,116,35,10
+ PUSHBUTTON "September",IDC_BUTTON_MONTH,143,116,40,11
+ GROUPBOX "",IDC_STATIC_DAY,188,98,75,34,WS_GROUP
+ CONTROL "This day",IDC_RADIO_THISDAY,"Button",BS_AUTORADIOBUTTON | WS_GROUP,194,106,43,9
+ CONTROL "Day :",IDC_RADIO_DAY,"Button",BS_AUTORADIOBUTTON,194,116,28,10
+ EDITTEXT IDC_EDIT_DAY,224,116,36,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_STATIC_TIME,17,130,191,34,WS_GROUP
+ CONTROL "This time",IDC_RADIO_THISTIME,"Button",BS_AUTORADIOBUTTON | WS_GROUP,23,138,45,9
+ CONTROL "Noon",IDC_RADIO_NOON,"Button",BS_AUTORADIOBUTTON,23,148,33,10
+ CONTROL "Midnight",IDC_RADIO_MIDNIGHT,"Button",BS_AUTORADIOBUTTON,84,138,39,10
+ CONTROL "Time :",IDC_RADIO_TIME,"Button",BS_AUTORADIOBUTTON,84,148,32,10
+ CONTROL "DateTimePicker1",IDC_DATETIMEPICKER2,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_UPDOWN | WS_TABSTOP | 0x8,131,146,72,13
+ PUSHBUTTON "Now",IDC_BUTTON_NOW,212,135,51,13
+ PUSHBUTTON "Pick",IDC_BUTTON_PICK,212,150,51,13
+ LTEXT "Resulting date/time :",IDC_STATIC_RESULT,14,172,66,8
+ CTEXT "N/A",IDC_STATIC_QUERYTIME,85,172,183,10,SS_SUNKEN
+ LTEXT "Formatted date/time :",IDC_STATIC,7,191,68,8
+ EDITTEXT IDC_EDIT_RESULT,77,189,197,12,ES_AUTOHSCROLL | ES_READONLY
+ DEFPUSHBUTTON "OK",IDOK,169,206,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,224,206,50,14
+END
+
+IDD_PREFS_METADATA DIALOGEX 0, 0, 278, 86
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Metadata Reading Settings"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Read media information on import if available",IDC_CHECK1,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,5,197,10
+ LTEXT "Use the following detection logic when",IDC_STATIC1,16,20,122,8
+ COMBOBOX IDC_COMBO1,140,18,31,142,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "media information is missing:",IDC_STATIC2,174,20,97,8
+ CONTROL "Smart (Detects ""Artist - Album\\TrackNum - Artist - Title"" etc)",IDC_RADIO1,
+ "Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,19,32,217,10
+ CONTROL "Simple (Detects ""Artist\\Album\\Title"")",IDC_RADIO2,
+ "Button",BS_AUTORADIOBUTTON,19,43,189,10
+ CONTROL "No guessing (title is filename, other fields empty)",IDC_RADIO3,
+ "Button",BS_AUTORADIOBUTTON,19,54,211,10
+ DEFPUSHBUTTON "OK",IDOK,167,68,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,221,68,50,13
+END
+
+IDD_MONITOR_SMALL DIALOGEX 0, 0, 146, 35
+STYLE DS_SETFONT | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP
+EXSTYLE WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+END
+
+IDD_MONITOR_HIDE DIALOGEX 0, 0, 285, 54
+STYLE DS_SETFONT | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Confirmation Dialog"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Are you sure you want to hide local media monitor window?\n This will not stop monitor.",IDC_STATIC,7,7,271,22
+ DEFPUSHBUTTON "Yes",IDOK,174,34,50,13
+ PUSHBUTTON "No",IDCANCEL,228,34,50,13
+END
+
+IDD_REFRESH_METADATA DIALOGEX 0, 0, 249, 28
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Reading Metadata"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Progress:",IDC_STATIC,7,7,32,8
+ LTEXT "",IDC_REFRESHMETADATA_STATUS,52,7,133,8
+ PUSHBUTTON "Cancel",IDCANCEL,192,7,50,14
+END
+
+IDD_REINDEX DIALOGEX 0, 0, 269, 30
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION
+CAPTION "Reindexing Library"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,7,7,255,16
+END
+
+IDD_ADD_VIEW_2 DIALOGEX 0, 0, 332, 255
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Smart View"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Choose a Preset:",IDC_STATIC,5,8,56,9,SS_CENTERIMAGE
+ COMBOBOX IDC_COMBO_PRESETS,70,7,151,162,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Advanced mode >>",IDC_BUTTON_MODE,247,7,80,13
+ GROUPBOX "",IDC_STATIC,5,21,322,154
+ CONTROL "",IDC_CHILDFRAME,"Static",SS_BLACKRECT | NOT WS_VISIBLE,11,30,310,139
+ CONTROL "Don't show me this again",IDC_CHECK_SHOWINFO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,111,95,10
+ GROUPBOX "",IDC_STATIC,5,176,322,43
+ LTEXT "Filters:",IDC_STATIC_FILTER,17,187,23,9,SS_CENTERIMAGE
+ CONTROL "",IDC_RADIO_SIMPLE,"Button",BS_AUTORADIOBUTTON | WS_GROUP,46,187,10,10,WS_EX_TRANSPARENT
+ CONTROL 157,IDC_IMAGE_SIMPLE,"Static",SS_BITMAP | SS_NOTIFY,56,187,11,10,WS_EX_TRANSPARENT
+ LTEXT "Simple",IDC_STATIC_SIMPLE,69,188,21,8,SS_NOTIFY
+ CONTROL "",IDC_RADIO_SIMPLEALBUM,"Button",BS_AUTORADIOBUTTON,105,187,10,10,WS_EX_TRANSPARENT
+ CONTROL 160,IDC_IMAGE_SIMPLEALBUM,"Static",SS_BITMAP | SS_NOTIFY,115,187,11,10
+ LTEXT "Simple Album",IDC_STATIC_SIMPLEALBUM,129,188,42,8,SS_NOTIFY
+ CONTROL "",IDC_RADIO_TWOFILTERS,"Button",BS_AUTORADIOBUTTON,181,187,10,8,WS_EX_TRANSPARENT
+ CONTROL 159,IDC_IMAGE_TWOFILTERS,"Static",SS_BITMAP | SS_NOTIFY,191,187,11,10
+ LTEXT "Two",IDC_STATIC_TWOFILTERS,205,188,14,8,SS_NOTIFY
+ CONTROL "",IDC_RADIO_THREEFILTERS,"Button",BS_AUTORADIOBUTTON,233,187,10,8,WS_EX_TRANSPARENT
+ CONTROL 158,IDC_IMAGE_THREEFILTERS,"Static",SS_BITMAP | SS_NOTIFY,243,187,11,10
+ LTEXT "Three",IDC_STATIC_THREEFILTERS,257,188,20,8,SS_NOTIFY
+ COMBOBOX IDC_COMBO_FILTER1,46,200,80,87,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER2,127,202,8,8
+ COMBOBOX IDC_COMBO_FILTER2,131,200,80,87,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER3,212,202,8,8
+ COMBOBOX IDC_COMBO_FILTER3,216,200,80,87,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Name:",IDC_STATIC,5,225,19,9,SS_CENTERIMAGE
+ EDITTEXT IDC_NAME,33,223,130,13,ES_AUTOHSCROLL
+ CONTROL "Hide extra info pane",IDC_HIDE_EXTINFO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,240,223,81,10
+ PUSHBUTTON "OK",IDOK,223,237,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,277,237,50,13
+ LTEXT "",IDC_STATIC_INFO,11,30,310,75
+END
+
+IDD_ADD_VIEW_2_NF DIALOGEX 0, 0, 332, 197
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Smart View"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Choose a Preset:",-1,5,8,56,9,SS_CENTERIMAGE
+ COMBOBOX IDC_COMBO_PRESETS,70,7,151,162,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "",-1,5,21,322,154
+ CONTROL "",IDC_CHILDFRAME,"Static",SS_BLACKRECT | NOT WS_VISIBLE,11,30,310,139
+ CONTROL "Don't show me this again",IDC_CHECK_SHOWINFO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,111,95,10
+ LTEXT "Name:",-1,5,181,25,9,SS_CENTERIMAGE
+ EDITTEXT IDC_NAME,33,179,130,13,ES_AUTOHSCROLL
+ PUSHBUTTON "OK",IDOK,223,179,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,277,179,50,13
+ LTEXT "",IDC_STATIC_INFO,11,30,310,75
+ PUSHBUTTON "Advanced mode >>",IDC_BUTTON_MODE,247,7,80,13
+END
+
+IDD_ADD_VIEW_CHILD_ADVANCED DIALOGEX 0, 0, 310, 138
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_VISIBLE | WS_SYSMENU
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Query:",IDC_STATIC,0,2,22,9,SS_CENTERIMAGE
+ EDITTEXT IDC_QUERY,25,1,236,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Query Builder",IDC_EDIT,262,1,47,12
+ GROUPBOX "Query Language Documentation",IDC_STATIC,0,17,310,121
+ EDITTEXT IDC_EDIT1,6,28,297,104,ES_MULTILINE | ES_READONLY | WS_VSCROLL
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_VIEW_AUDIO, DIALOG
+ BEGIN
+ RIGHTMARGIN, 289
+ END
+
+ IDD_PREFS1, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 259
+ END
+
+ IDD_VIEW_DB_ERROR, DIALOG
+ BEGIN
+ LEFTMARGIN, 10
+ RIGHTMARGIN, 184
+ TOPMARGIN, 10
+ BOTTOMMARGIN, 156
+ END
+
+ IDD_PREFSFR, DIALOG
+ BEGIN
+ RIGHTMARGIN, 191
+ BOTTOMMARGIN, 191
+ END
+
+ IDD_EDITDIR, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 255
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 139
+ END
+
+ IDD_EDIT_QUERY, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 315
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 144
+ END
+
+ IDD_EDIT_QUERY_PICK, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 171
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 71
+ END
+
+ IDD_CUSTCOLUMNS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 335
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 147
+ END
+
+ "IDD_NDE_RECOVERY$(DISABLED)", DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 254
+ TOPMARGIN, 8
+ BOTTOMMARGIN, 100
+ END
+
+ IDD_NEEDADDFILES, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 194
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 98
+ END
+
+ IDD_SCROLLCHILD, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 14
+ END
+
+ IDD_SCROLLCHILDHOST, DIALOG
+ BEGIN
+ RIGHTMARGIN, 295
+ END
+
+ IDD_TIMEEDITOR, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 274
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 220
+ END
+
+ IDD_PREFS_METADATA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 271
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 81
+ END
+
+ IDD_MONITOR_HIDE, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 278
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 47
+ END
+
+ IDD_REFRESH_METADATA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 242
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 21
+ END
+
+ IDD_REINDEX, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 262
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 23
+ END
+
+ IDD_ADD_VIEW_2, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 327
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 250
+ END
+
+ IDD_ADD_VIEW_2_NF, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 327
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 192
+ END
+
+ IDD_ADD_VIEW_CHILD_ADVANCED, DIALOG
+ BEGIN
+ RIGHTMARGIN, 309
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "MediaWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_MEDIAWND_PLAYSELECTEDFILES
+ MENUITEM "Enqueue selection\tShift+Enter", ID_MEDIAWND_ENQUEUESELECTEDFILES
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_MEDIAWND_ADDTOPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", ID_MEDIAWND_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Edit metadata for selection...\tCtrl+E", ID_EDITITEMINFOS
+ MENUITEM "View f&ile info...\tAlt+3", ID_PE_ID3
+ MENUITEM "Read metadata on selected items\tCtrl+R", IDC_REFRESH_METADATA
+ POPUP "Rate items"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Explore item folder\tCtrl+F", ID_MEDIAWND_EXPLOREFOLDER
+ MENUITEM SEPARATOR
+ POPUP "Remove..."
+ BEGIN
+ MENUITEM "Remove all dead files", ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES
+ MENUITEM "Physically remove selected item(s)", ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS
+ END
+ MENUITEM "Remove from library\tDel", ID_MEDIAWND_REMOVEFROMLIBRARY
+ END
+ POPUP "AudioWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_AUDIOWND_PLAYSELECTION
+ MENUITEM "Enqueue selection\tShift+Enter", ID_AUDIOWND_ENQUEUESELECTION
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_MEDIAWND_ADDTOPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ POPUP "Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Play random item", ID_AUDIOWND_PLAYRANDOMITEM
+ MENUITEM "Enqueue random item", ID_AUDIOWND_ENQUEUERANDOMITEM
+ END
+ POPUP "QueryWnd"
+ BEGIN
+ MENUITEM "Play\tEnter", ID_QUERYWND_PLAYQUERY
+ MENUITEM "Enqueue\tShift+Enter", ID_QUERYWND_ENQUEUEQUERY
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_MEDIAWND_ADDTOPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Edit View...\tF2", ID_QUERYWND_EDIT
+ MENUITEM "Add Smart View...\tIns", ID_QUERYMENU_ADDNEWQUERY
+ MENUITEM SEPARATOR
+ MENUITEM "Delete View\tDel", ID_QUERYWND_DELETE
+ END
+ POPUP "QueryMenu"
+ BEGIN
+ MENUITEM "Add Smart View...\tIns", ID_QUERYMENU_ADDNEWQUERY
+ MENUITEM SEPARATOR
+ MENUITEM "&Preferences", ID_QUERYMENU_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_QUERYMENU_HELP
+ END
+ POPUP "HeaderWnd"
+ BEGIN
+ MENUITEM "Customize columns...", ID_HEADERWND_CUSTOMIZECOLUMNS
+ END
+ POPUP "FilterHeaderWnd"
+ BEGIN
+ MENUITEM "Customize columns...", ID_HEADERWND_CUSTOMIZECOLUMNS
+ MENUITEM "Show Horizontal Scrollbar", ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR
+ END
+ POPUP "ArtHeaderWnd"
+ BEGIN
+ MENUITEM "Small Icon", ID_ARTHEADERWND_SMALLICON
+ MENUITEM "Medium Icon", ID_ARTHEADERWND_MEDIUMICON
+ MENUITEM "Large Icon", ID_ARTHEADERWND_LARGEICON
+ MENUITEM "Extra Large Icon", ID_ARTHEADERWND_EXTRALARGEICON
+ MENUITEM "Show Album Title", ID_ARTHEADERWND_SHOWTEXT
+ MENUITEM SEPARATOR
+ MENUITEM "Small Details", ID_ARTHEADERWND_SMALLDETAILS
+ MENUITEM "Medium Details", ID_ARTHEADERWND_MEDIUMDETAILS
+ MENUITEM "Large Details", ID_ARTHEADERWND_LARGEDETAILS
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_AUDIO BITMAP "resources\\ti_audio_16x16x16.bmp"
+IDB_TREEITEM_MOSTPLAYED BITMAP "resources\\ti_most_played_16x16x16.bmp"
+IDB_TREEITEM_NEVERPLAYED BITMAP "resources\\ti_never_played_16x16x16.bmp"
+IDB_TREEITEM_RECENTLYADDED BITMAP "resources\\ti_recently_added_16x16x16.bmp"
+IDB_TREEITEM_RECENTLYPLAYED BITMAP "resources\\ti_recently_played_16x16x16.bmp"
+IDB_TREEITEM_TOPRATED BITMAP "resources\\ti_top_rated_16x16x16.bmp"
+IDB_TREEITEM_VIDEO BITMAP "resources\\ti_video_16x16x16.bmp"
+IDB_TREEITEM_PODCASTS BITMAP "resources\\ti_podcasts_16x16x16.bmp"
+IDB_NEWFILTER_SIMPLE BITMAP "resources\\nf_simple.bmp"
+IDB_NEWFILTER_THREEFILTERS BITMAP "resources\\nf_threefilters.bmp"
+IDB_NEWFILTER_TWOFILTERS BITMAP "resources\\nf_twofilters.bmp"
+IDB_NEWFILTER_SIMPLEALBUM BITMAP "resources\\nf_simplealbum.bmp"
+IDB_TOOL_MODE BITMAP "resources\\icn_view_mode.bmp"
+IDB_TOOL_ART BITMAP "resources\\icn_alb_art.bmp"
+IDB_TOOL_COLS BITMAP "resources\\icn_columns.bmp"
+IDB_TREEITEM_RECENTLYMODIFIED BITMAP "resources\\ti_recently_modified_16x16x16.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog Info
+//
+
+IDD_ADD_VIEW_2 DLGINIT
+BEGIN
+ IDC_COMBO_PRESETS, 0x403, 7, 0
+0x6473, 0x6466, 0x6673, "\000"
+ 0
+END
+
+IDD_ADD_VIEW_2_NF DLGINIT
+BEGIN
+ IDC_COMBO_PRESETS, 0x403, 7, 0
+0x6473, 0x6466, 0x6673, "\000"
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXT
+//
+
+IDR_QUERIES_TEXT TEXT "queries.txt"
+IDR_DB_ERROR TEXT "db_error.txt"
+IDR_NDE_ERROR TEXT "nde_error.txt"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_ACCELERATORS ACCELERATORS
+BEGIN
+ "3", ID_PE_ID3, VIRTKEY, ALT, NOINVERT
+ "A", ID_MEDIAWND_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "E", ID_EDITITEMINFOS, VIRTKEY, CONTROL, NOINVERT
+ "F", ID_MEDIAWND_EXPLOREFOLDER, VIRTKEY, CONTROL, NOINVERT
+ "R", IDC_REFRESH_METADATA, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_MEDIAWND_REMOVEFROMLIBRARY, VIRTKEY, NOINVERT
+ VK_RETURN, ID_AUDIOWND_PLAYSELECTION, VIRTKEY, NOINVERT
+ VK_RETURN, ID_MEDIAWND_PLAYSELECTEDFILES, VIRTKEY, NOINVERT
+ VK_RETURN, IDC_BUTTON_PLAY, VIRTKEY, NOINVERT
+ VK_RETURN, ID_AUDIOWND_ENQUEUESELECTION, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_MEDIAWND_ENQUEUESELECTEDFILES, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_MIX, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+IDR_QUERY_ACCELERATORS ACCELERATORS
+BEGIN
+ VK_INSERT, ID_QUERYMENU_ADDNEWQUERY, VIRTKEY, NOINVERT
+ VK_DELETE, ID_QUERYWND_DELETE, VIRTKEY, NOINVERT
+ VK_F2, ID_QUERYWND_EDIT, VIRTKEY, NOINVERT
+ VK_RETURN, ID_QUERYWND_ENQUEUEQUERY, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_QUERYWND_PLAYQUERY, VIRTKEY, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// PNG
+//
+
+IDR_IMAGE_NOTFOUND PNG "resources\\notfound.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_LOCAL_MEDIA "Nullsoft Library v%s"
+ 65535 "{06A3F81D-043D-4b5c-B341-590ED7053492}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_LOCAL_MEDIA "Local Library"
+ IDS_SEARCHING_X_FILES_FOUND "Searching... (%i files found)"
+ IDS_GETTING_INFO_FROM_FILES_PERCENT
+ "Getting information from files... (%i%% done)"
+ IDS_SCANNING_DIR "Scanning dir: "
+ IDS_SCANNING_FILE "Scanning file: "
+ IDS_CHECKING_FOR_FILE "Checking for file: "
+ IDS_COMPACTING "Compacting"
+ IDS_REMOVING_FILES_NOT_EXISTING "Removing files that do not exist..."
+ IDS_INITIALIZING "Initializing..."
+ IDS_SCANNING_X_OF_X_X_REMOVED "Scanning %d of %d files (%d removed)"
+ IDS_SCANNED_X_FILES_X_REMOVED
+ "Scanned %d files (%d removed) - Cleaning up..."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DATE_TIME_IS_TOO_COMPLEX
+ "This date/time is too complex for the editor to handle, drop the unsupported keywords?"
+ IDS_DATE_TIME_EDITOR_QUESTION "Date/Time Editor Question"
+ IDS_COMPARE_TO_STRING "Compare to string"
+ IDS_AFTER "After"
+ IDS_BEFORE "Before"
+ IDS_SINCE "Since"
+ IDS_UNTIL "Until"
+ IDS_COMPARE_TO_LENGTH "Compare to length (HH:MM:SS)"
+ IDS_ABOVE "Above"
+ IDS_BELOW "Below"
+ IDS_ABOVE_OR_EQUAL "Above or Equal"
+ IDS_BELOW_OR_EQUAL "Below or Equal"
+ IDS_COMPARE_TO_NUMBER "Compare to number"
+ IDS_OFFSET_BY "Offset by"
+ IDS_TIME_AGO "Time ago"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_SPACE_AFTER " after"
+ IDS_SPACE_BEFORE " before"
+ IDS_THE "the "
+ IDS_1ST "st"
+ IDS_2ND "nd"
+ IDS_3RD "rd"
+ IDS_4TH "th"
+ IDS_OF_THIS_MONTH " of this month"
+ IDS_NOW "now"
+ IDS_THIS_DATE "this date"
+ IDS_THIS_MONTH "this month"
+ IDS_THIS_DAY "this day"
+ IDS_THIS_TIME "this time"
+ IDS_ON_SPACE "on "
+ IDS_IN_SPACE "in "
+ IDS_OF_SPACE "of "
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AT_SPACE "at "
+ IDS_NOON "noon"
+ IDS_MIDNIGHT "midnight"
+ IDS_AGO " ago"
+ IDS_QUERY_FIELD_IS_EMPTY
+ "The query field is empty, use expression field instead of resetting the query?"
+ IDS_EMPTY_QUERY "Empty query"
+ IDS_NO_CHANGES_MADE_TO_QUERY
+ "You have not made any modification to the query, use the expression field instead of the old query?"
+ IDS_QUERY_NOT_CHANGED "Query not changed"
+ IDS_REGISTERED "Registered:%s"
+ IDS_ADD_PLEDIT_TO_LOCAL_MEDIA "Add current &playlist to Library"
+ IDS_ERROR "Error"
+ IDS_ADD_TO_LOCAL_MEDIA "Add to Library"
+ IDS_AUDIO "Audio"
+ IDS_VIDEO "Video"
+ IDS_MOST_PLAYED "Most Played"
+ IDS_RECENTLY_ADDED "Recently Added"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RECENTLY_PLAYED "Recently Played"
+ IDS_NEVER_PLAYED "Never Played"
+ IDS_TOP_RATED "Top Rated"
+ IDS_REMOVE_ALL_ITEMS_IN_LIBRARY
+ "Are you sure you want to remove ALL the items in your Library?"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_NEW_SMART_VIEW "New Smart &View...\tInsert"
+ IDS_RESCAN_WATCH_FOLDERS
+ "&Rescan Watch Folders (in background)\tCtrl+Insert"
+ IDS_ADD_MEDIA_TO_LIBRARY "&Add media to Library..."
+ IDS_REMOVE_MISSING_FILES_FROM_ML "Remove missing files from Library..."
+ IDS_ANY "Any"
+ IDS_ALL "All"
+ IDS_OPTIONS "Options"
+ IDS_DIRECTORY "Directory"
+ IDS_STOP_SCAN "Stop scan"
+ IDS_RESCAN_NOW "Rescan now"
+ IDS_DEFAULT "Default"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RESCAN_ABORTED "Rescan aborted"
+ IDS_WATCH_FOLDERS "Watch Folders"
+ IDS_EDIT_VIEW "Edit View"
+ IDS_MY_NEW_VIEW "My New View"
+ IDS_SIMPLE_VIEW_EDITOR "Simple View Editor"
+ IDS_ADVANCED_EDITOR "Advanced Editor"
+ IDS_FILTERS "Filters"
+ IDS_MUST_ENTER_A_NAME "You must enter a name!"
+ IDS_DAY "day"
+ IDS_VIEW_QUERY_MAY_HAVE_ERRORS "This view query may have errors."
+ IDS_VIEW_QUERY_IS_TOO_COMPLEX "This view query is too complex."
+ IDS_TEXT_AFTER_THE_FIRST_ERROR "text after the first error"
+ IDS_SOME_OF_THE_QUERY_LOGIC "some of the query logic"
+ IDS_VIEW_EDITOR_QUESTION "View Editor Question"
+ IDS_DELETE_THIS_VIEW "Delete this view?"
+ IDS_REFINE "Refine:"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLEAR_REFINE "Clear refine"
+ IDS_ARTIST "Artist"
+ IDS_TITLE "Title"
+ IDS_ALBUM "Album"
+ IDS_LENGTH "Length"
+ IDS_TRACK_NUMBER "Track #"
+ IDS_GENRE "Genre"
+ IDS_YEAR "Year"
+ IDS_FILENAME "Filename"
+ IDS_RATING "Rating"
+ IDS_PLAY_COUNT "Play Count"
+ IDS_PLAYED_LAST "Played Last"
+ IDS_LAST_UPDATED "Last Updated"
+ IDS_FILE_TIME "File Time"
+ IDS_COMMENT "Comment"
+ IDS_FILE_SIZE "File Size"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_BITRATE "Bitrate"
+ IDS_TYPE "Type"
+ IDS_DISC "Disc"
+ IDS_ALBUM_ARTIST "Album Artist"
+ IDS_FILE_PATH "File Path"
+ IDS_ALBUM_GAIN "Album Gain"
+ IDS_TRACK_GAIN "Track Gain"
+ IDS_PUBLISHER "Publisher"
+ IDS_COMPOSER "Composer"
+ IDS_EXTENSION "Extension"
+ IDS_IS_PODCAST "Is Podcast"
+ IDS_PODCAST_CHANNEL "Podcast Channel"
+ IDS_PODCAST_PUBLISH_DATE "Podcast Publish Date"
+ IDS_SCANNING "Scanning..."
+ IDS_ERROR_DELETING_FILES "Error deleting files"
+ IDS_ERROR_DELETING_X "Error deleting %s"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY
+ "Are you sure you want to remove the selected item(s) from the library?"
+ IDS_HIDE_INFO "Hide Info"
+ IDS_SHOW_INFO "Show Info"
+ IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY
+ "There are now %d items in the library. Click 'Add More' to add more directories, or click Close to continue."
+ IDS_ADD_MORE "Add More..."
+ IDS_PLAY_ALL_FILES_BY "Play all files by %s"
+ IDS_PLAY_ALL_FILES_FROM "Play all files from %s"
+ IDS_PODCAST "Podcast"
+ IDS_NON_PODCAST "Non-Podcast"
+ IDS_MIXABLE "Mixable"
+ IDS_X_ITEM "%d item%s"
+ IDS_ALBUM_ART "Album Art"
+ IDS_LOOKING_UP_MEDIA_INFO "...looking up media info..."
+ IDS_CLICK_AN_ITEM_TO_GET_ITS_INFO "click an item to get its info"
+ IDS_PLEASE_CONNECT_TO_THE_INTERNET_TO_USE_THIS_FEATURE
+ "please connect to the internet to use this feature"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM "All (%d %s, %d without album)"
+ IDS_ALL_X_ALBUMS "All (%d %s)"
+ IDS_NO_ALBUM "(no album)"
+ IDS_OPEN_MEDIA_LIBRARY_VIEW_RESULTS "Open Library &view results"
+ IDS_MEDIA_LIBRARY_VIEW_RESULTS "Library &view results"
+ IDS_ADD_MEDIA_TO_LIBRARY_ "Add media to Library"
+ IDS_SELECT_FOLDER_TO_ADD_TO_WINAMP_MEDIA_LIBRARY
+ "Select folder to add to the Winamp Library."
+ IDS_ADD "Add"
+ IDS_SCAN_FOLDER_IN_BACKGROUND "Scan folder in background"
+ IDS_FOLDER_NAME_IS_INCORRECT
+ "Folder Name '%s' is incorrect. Please enter correct folder name or select 'Cancel'."
+ IDS_INCORRECT_FOLDER_NAME "Incorrect Folder Name"
+ IDS_WILLS_UBER_STRING "Welcome to the new Smart View creation dialog. It's totally super duper and overhauled to the limit, pretty much.\n\nTo get started, just choose a preset. If you know what you're doing choose the ""Custom"" preset. Even though it isn't really a ""preset"" per se, this is what we decided on! :)\n\nWhen the form appears (filled in with your preset info), tweak it to your liking and once you're done give your smart view a name. Choose to hide the extra info pane if you'd like and hit ""OK"" and you're all set!"
+ IDS_EDIT_SMART_VIEW "Edit Smart View"
+ IDS_ADVANCED_MODE "Advanced mode >>"
+ IDS_SIMPLE_MODE "<< Simple mode"
+ IDS_DAYS "days"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_LENGTH_DURATION_STRING "[%s%u %s+%u:%02u:%02u]"
+ IDS_ITEM "item"
+ IDS_ITEMS "items"
+ IDS_SCANNING_PLAIN "Scanning"
+ IDS_ALL_ARTISTS "All Artists"
+ IDS_ARTIST_INDEX "Artist Index"
+ IDS_ALBUM_ARTIST_INDEX "Album Artist Index"
+ IDS_CUSTOM "Custom"
+ IDS_IS_VIDEO "Is video"
+ IDS_FILE_SIZE_KB "File size (KB)"
+ IDS_BITRATE_KBPS "Bitrate (KBPS)"
+ IDS_DISC_NUMBER "Disc #"
+ IDS_DISCS "Discs"
+ IDS_TRACKS "Tracks"
+ IDS_EQUALS "Equals"
+ IDS_DOES_NOT_EQUAL "Does not equal"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CONTAINS "Contains"
+ IDS_DOES_NOT_CONTAIN "Does not contain"
+ IDS_IS_ABOVE "Is above"
+ IDS_IS_BELOW "Is below"
+ IDS_EQUALS_OR_IS_ABOVE "Equals or is above"
+ IDS_EQUALS_OR_IS_BELOW "Equals or is below"
+ IDS_IS_EMPTY "Is empty"
+ IDS_IS_NOT_EMPTY "Is not empty"
+ IDS_BEGINS_WITH "Begins with"
+ IDS_ENDS_WITH "Ends with"
+ IDS_IS_SIMILAR_TO "Is similar to"
+ IDS_AT "At"
+ IDS_NOT_AT "Not at"
+ IDS_STRING189 "After"
+ IDS_PODCASTS "Podcasts"
+ IDS_AUDIO_BY_GENRE "Audio by Genre"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AUDIO_BY_INDEX "Audio by Index"
+ IDS_60s_MUSIC "'60s Music"
+ IDS_70s_MUSIC "'70s Music"
+ IDS_80s_MUSIC "'80s Music"
+ IDS_90s_MUSIC "'90s Music"
+ IDS_00s_MUSIC "'00s Music"
+ IDS_ROCK_MUSIC "Rock Music"
+ IDS_CLASSICAL_MUSIC "Classical Music"
+ IDS_RECORD_LABELS "Record Labels"
+ IDS_ALBUMS "Albums"
+ IDS_ALBUM_ARTISTS "Album Artists"
+ IDS_ARTIST_S "Artists"
+ IDS_COMPOSERS "Composers"
+ IDS_GENRES "Genres"
+ IDS_PUBLISHERS "Publishers"
+ IDS_YEARS "Years"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ALBUM_ARTIST_INDEXES "Album Artist Indexes"
+ IDS_ARTIST_INDEXES "Artist Indexes"
+ IDS_PODCAST_CHANNELS "Podcast Channels"
+ IDS_ALL_X_S "All (%d %s)"
+ IDS_NO_S "(no %s)"
+ IDS_SIZE "Size"
+ IDS_NO_IMAGE "No image"
+ IDS_AVAILABLE "available"
+ IDS_NO_ARTIST "No Artist"
+ IDS_NO_GENRE "No Genre"
+ IDS_OTHER2 "Other..."
+ IDS_SIMPLE_ALBUM "Simple Album"
+ IDS_AUDIO_BUTTON_TT1 "Toggle Album Art View"
+ IDS_AUDIO_BUTTON_TT2 "Select Filters & Panes"
+ IDS_AUDIO_BUTTON_TT3 "Pane Options"
+ IDS_GET_ALBUM_ART "Get Album Art"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_REFRESH_ALBUM_ART "Refresh Album Art"
+ IDS_OPEN_FOLDER "Open Folder"
+ IDS_BPM "BPM"
+ IDS_META_STR "Meta"
+ IDS_SMART_STR "Smart"
+ IDS_GUESS_STR "Guess"
+ IDS_RECURSE_STR "Recurse"
+ IDS_MORE_ARTIST_INFO "Get Artist Information"
+ IDS_PLAY_RANDOM_ITEM "Play random %s"
+ IDS_ENQUEUE_RANDOM_ITEM "Enqueue random %s"
+ IDS_LOSSLESS "Lossless"
+ IDS_CATEGORY "Category"
+ IDS_CATEGORIES "Categories"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_GENRE_ALT "genre"
+ IDS_YEAR_ALT "year"
+ IDS_ALBUM_ARTIST_ALT "album artist"
+ IDS_ALBUM_GAIN_ALT "album gain"
+ IDS_PUBLISHER_ALT "publisher"
+ IDS_COMPOSER_ALT "composer"
+ IDS_ARTIST_INDEX_ALT "artist index"
+ IDS_ALBUM_ARTIST_INDEX_ALT "album artist index"
+ IDS_PODCAST_CHANNEL_ALT "podcast channel"
+ IDS_CATEGORY_ALT "category"
+ IDS_ARTIST_ALT "artist"
+ IDS_ALBUM_ALT "album"
+ IDS_KBPS "kbps"
+ IDS_RECENTLY_PLAYED_TEXT
+ "When 'Recently Played' is enabled, Winamp will keep track of when\nand how many times items in the Library are played.\n\nNote: When the first match against any of the selected option(s)\nis met then the playing item will be tracked."
+ IDS_PRODUCER "Producer"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DIRECTOR "Director"
+ IDS_PRODUCER_ALT "producer"
+ IDS_DIRECTOR_ALT "director"
+ IDS_CODEC "Codec"
+ IDS_WIDTH "Width"
+ IDS_HEIGHT "Height"
+ IDS_DIMENSION "Dimension"
+ IDS_IN_X_SEC "in %.03f sec."
+ IDS_TRACKS_MENU "Tracks"
+ IDS_ERROR_PLG_SELECT_TRACKS
+ "Please select one or more tracks to use as a seed for the playlist generator."
+ IDS_NULLSOFT_PLAYLIST_GENERATOR "Nullsoft Playlist Generator"
+ IDS_VIEW_ALL_FILES_BY "All tracks by artist ""%s"""
+ IDS_VIEW_ALL_FILES_FROM "All tracks from album ""%s"""
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DATE_ADDED "Date Added"
+ IDS_REFRESH_FILESIZE_DATEADDED
+ "Refreshing 'Filesize' & Populating 'Date Added'"
+ IDS_RECENTLY_MODIFIED "Recently Modified"
+ IDS_FINISHED "Finished"
+ IDS_REFRESH_MESSAGE "%u of %u files"
+ IDS_CLOUD "Cloud"
+ IDS_CLOUD_SOURCES "Cloud Sources:"
+ IDS_CLOUD_HIDDEN "Cloud (Hidden)"
+ IDS_TRACK_AVAILABLE "Track available in "
+ IDS_UPLOAD_TO_SOURCE "Upload track to source"
+ IDS_UPLOADING_TO_SOURCE "Uploading track to source"
+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_local/ml_local.sln b/Src/Plugins/Library/ml_local/ml_local.sln
new file mode 100644
index 00000000..b7f892ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.sln
@@ -0,0 +1,128 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_local", "ml_local.vcxproj", "{D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tataki", "..\tataki\tataki.vcxproj", "{255B68B5-7EF8-45EF-A675-2D6B88147909}"
+ ProjectSection(ProjectDependencies) = postProject
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
+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
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|Win32.Build.0 = Debug|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|x64.ActiveCfg = Debug|x64
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|x64.Build.0 = Debug|x64
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|Win32.ActiveCfg = Release|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|Win32.Build.0 = Release|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|x64.ActiveCfg = Release|x64
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|x64.Build.0 = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.ActiveCfg = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.Build.0 = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.ActiveCfg = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.Build.0 = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.ActiveCfg = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.Build.0 = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.ActiveCfg = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.Build.0 = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.Build.0 = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.Build.0 = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_local/ml_local.vcxproj b/Src/Plugins/Library/ml_local/ml_local.vcxproj
new file mode 100644
index 00000000..847d5b6d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.vcxproj
@@ -0,0 +1,416 @@
+<?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>{D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}</ProjectGuid>
+ <RootNamespace>ml_local</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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4244;4267;4302;4311;4312;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ShowIncludes>false</ShowIncludes>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4244;4267;4302;4311;4312;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ShowIncludes>false</ShowIncludes>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\tataki\tataki.vcxproj">
+ <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="add.cpp" />
+ <ClCompile Include="AlbumArtCache.cpp" />
+ <ClCompile Include="AlbumArtContainer.cpp" />
+ <ClCompile Include="AlbumArtFilter.cpp" />
+ <ClCompile Include="AlbumFilter.cpp" />
+ <ClCompile Include="api_mldb.cpp" />
+ <ClCompile Include="bgscan.cpp" />
+ <ClCompile Include="DBitemrecord.cpp" />
+ <ClCompile Include="editinfo.cpp" />
+ <ClCompile Include="editquery.cpp" />
+ <ClCompile Include="FolderBrowseEx.cpp" />
+ <ClCompile Include="guess.cpp" />
+ <ClCompile Include="handleMessage.cpp" />
+ <ClCompile Include="LocalMediaCOM.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="MD5.cpp" />
+ <ClCompile Include="mldbApi.cpp" />
+ <ClCompile Include="mldbApiFactory.cpp" />
+ <ClCompile Include="ml_local.cpp" />
+ <ClCompile Include="ml_subclass.cpp" />
+ <ClCompile Include="nde_itemRecord.cpp" />
+ <ClCompile Include="pe_subclass.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="queries.cpp" />
+ <ClCompile Include="ReIndexUI.cpp" />
+ <ClCompile Include="remove.cpp" />
+ <ClCompile Include="SaveQuery.cpp" />
+ <ClCompile Include="ScanFolderBrowser.cpp" />
+ <ClCompile Include="SimpleFilter.cpp" />
+ <ClCompile Include="TitleInfo.cpp" />
+ <ClCompile Include="util.cpp" />
+ <ClCompile Include="ViewFilter.cpp" />
+ <ClCompile Include="view_audio.cpp" />
+ <ClCompile Include="view_errorinfo.cpp" />
+ <ClCompile Include="view_media.cpp" />
+ <ClCompile Include="view_miniinfo.cpp" />
+ <ClCompile Include="wa_subclass.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\ml_cloud\CloudCallback.h" />
+ <ClInclude Include="..\..\..\replicant\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="AlbumArtCache.h" />
+ <ClInclude Include="AlbumArtContainer.h" />
+ <ClInclude Include="AlbumArtFilter.h" />
+ <ClInclude Include="AlbumFilter.h" />
+ <ClInclude Include="api__ml_local.h" />
+ <ClInclude Include="api_mldb.h" />
+ <ClInclude Include="editquery.h" />
+ <ClInclude Include="FolderBrowseEx.h" />
+ <ClInclude Include="LocalMediaCOM.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="MD5.h" />
+ <ClInclude Include="mldbApi.h" />
+ <ClInclude Include="mldbApiFactory.h" />
+ <ClInclude Include="MLString.h" />
+ <ClInclude Include="ml_local.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="ScanFolderBrowser.h" />
+ <ClInclude Include="SimpleFilter.h" />
+ <ClInclude Include="ViewFilter.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="db_error.txt" />
+ <Text Include="nde_error.txt" />
+ <Text Include="queries.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\icn_alb_art.bmp" />
+ <Image Include="resources\icn_columns.bmp" />
+ <Image Include="resources\icn_view_mode.bmp" />
+ <Image Include="resources\nf_simple.bmp" />
+ <Image Include="resources\nf_simplealbum.bmp" />
+ <Image Include="resources\nf_threefilters.bmp" />
+ <Image Include="resources\nf_twofilters.bmp" />
+ <Image Include="resources\notfound.png" />
+ <Image Include="resources\ti_audio_16x16x16.bmp" />
+ <Image Include="resources\ti_most_played_16x16x16.bmp" />
+ <Image Include="resources\ti_never_played_16x16x16.bmp" />
+ <Image Include="resources\ti_podcasts_16x16x16.bmp" />
+ <Image Include="resources\ti_recently_added_16x16x16.bmp" />
+ <Image Include="resources\ti_recently_modified_16x16x16.bmp" />
+ <Image Include="resources\ti_recently_played_16x16x16.bmp" />
+ <Image Include="resources\ti_top_rated_16x16x16.bmp" />
+ <Image Include="resources\ti_video_16x16x16.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_local.rc" />
+ </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_local/ml_local.vcxproj.filters b/Src/Plugins/Library/ml_local/ml_local.vcxproj.filters
new file mode 100644
index 00000000..d5a1349e
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.vcxproj.filters
@@ -0,0 +1,328 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="add.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumArtCache.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumArtContainer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumArtFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="api_mldb.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="bgscan.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DBitemrecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="editinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="editquery.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FolderBrowseEx.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="guess.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="handleMessage.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="local_menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LocalMediaCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MD5.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_local.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="mldbApi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="mldbApiFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="nde_itemRecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pe_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="queries.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ReIndexUI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="remove.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SaveQuery.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ScanFolderBrowser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SimpleFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wa_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ViewFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_miniinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_media.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_errorinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_audio.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TitleInfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="AlbumArtCache.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AlbumArtContainer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AlbumArtFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AlbumFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_local.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_mldb.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="editquery.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FolderBrowseEx.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="local_menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LocalMediaCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MD5.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ml_local.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="mldbApi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="mldbApiFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MLString.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ViewFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SimpleFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ScanFolderBrowser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\ml_cloud\CloudCallback.h">
+ <Filter>Header Files\ml_cloud</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\replicant\nu\menushortcuts.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\icn_view_mode.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\icn_columns.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\icn_alb_art.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_simple.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_simplealbum.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_threefilters.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_twofilters.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\notfound.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_audio_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_most_played_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_never_played_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_podcasts_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_recently_added_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_recently_modified_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_recently_played_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_top_rated_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_video_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="db_error.txt" />
+ <Text Include="nde_error.txt" />
+ <Text Include="queries.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{f6d09751-2ba8-4112-b488-dc1f0ea05c94}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{3c287e55-bc08-4d03-824d-ee4d32647054}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{13873f78-eb7e-495e-beaa-a25de0940e15}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{35f126e7-3751-48fb-b7e5-f4aad512a831}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{d65e9d81-15db-4230-8fa3-a293d35217b3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{21c5748c-8e27-4395-80cf-865cb7d06a76}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{4dd5349f-8664-4615-8297-acc4d62c215f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\ml_cloud">
+ <UniqueIdentifier>{d3b5c0d1-2830-46e2-a1fe-d83606b83427}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{a811db7d-f845-4bb5-9437-1c7ac6b27f2e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{e9155744-9138-4cbe-84e6-413d3b07daa4}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{aaff6d40-e86b-4dc0-aa3c-02a2046a8d6a}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_local.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_subclass.cpp b/Src/Plugins/Library/ml_local/ml_subclass.cpp
new file mode 100644
index 00000000..4020aeb1
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_subclass.cpp
@@ -0,0 +1,80 @@
+#include "main.h"
+extern void AccessingGracenoteHack(int);
+extern HWND subWnd;
+
+// TODO: benski> a lot of things don't need to be part of gen_ml window - they could easily be done with a hidden window
+LRESULT APIENTRY ml_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_USER+641:
+ {
+ AccessingGracenoteHack(wParam);
+ break;
+ }
+ case WM_ML_IPC:
+ {
+ INT_PTR ret = HandleIpcMessage((INT_PTR)lParam, (INT_PTR)wParam);
+ if (ret != 0)
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, ret);
+ return ret; // supposed to return TRUE but thus is not working for me :(
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDM_DOSHITMENU_ADDNEWVIEW:
+ addNewQuery(hwndDlg);
+ return 0;
+ case IDM_ADD_PLEDIT:
+ add_pledit_to_library();
+ return 0;
+ case IDM_ADD_DIRS:
+ add_to_library(hwndDlg);
+ return 0;
+ case IDM_REMOVE_UNUSED_FILES:
+ Scan_RemoveFiles(hwndDlg);
+ if (m_curview_hwnd) SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ return 0;
+ case IDM_RESCANFOLDERSNOW:
+ if (!g_bgscan_scanning) SendMessage(hwndDlg, WM_USER + 575, 0xffff00dd, 0);
+ return 0;
+ }
+ break;
+ case WM_USER + 575: //sent by prefs to start scanning
+ if (wParam == 0xffff00dd && !lParam)
+ {
+ if (!g_bgscan_scanning)
+ {
+ Scan_BackgroundScan();
+ }
+ }
+ break;
+ case WM_TIMER:
+ {
+ static int in_timer;
+ if (in_timer) return 0;
+ in_timer = 1;
+ if (wParam == 200) // decide if it is time to scan yet
+ {
+ if (!g_bgscan_scanning)
+ {
+ if (g_bgrescan_force || (g_bgrescan_do && (time(NULL) - g_bgscan_last_rescan) > g_bgrescan_int*60))
+ {
+ // send to the prefs page so it'll show the status if it's open
+ // (makes it easier to see if things are working with the rescan every x option)
+ if (IsWindow(subWnd)) SendMessage(subWnd, WM_USER+101, 0, 0);
+ Scan_BackgroundScan();
+ }
+ }
+ in_timer = 0;
+ return 0;
+ }
+ in_timer = 0;
+ }
+ break;
+ }
+ return CallWindowProc(ml_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/mldbApi.cpp b/Src/Plugins/Library/ml_local/mldbApi.cpp
new file mode 100644
index 00000000..661c8631
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApi.cpp
@@ -0,0 +1,399 @@
+#include "mldbApi.h"
+#include "main.h"
+#include <strsafe.h>
+
+itemRecordW *MLDBAPI::GetFile(const wchar_t *filename)
+{
+ itemRecordW *result = 0;
+ if (filename)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (s)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ result = (itemRecordW *)_aligned_malloc(sizeof(itemRecordW), 16);
+ if (result)
+ {
+ result->filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+itemRecordW *MLDBAPI::GetFileIf(const wchar_t *filename, const wchar_t *query)
+{
+ itemRecordW *result = 0;
+ if (filename)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (s)
+ {
+ NDE_Scanner_Query(s, query);
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ result = (itemRecordW *)_aligned_malloc(sizeof(itemRecordW), 16);
+ if (result)
+ {
+ result->filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+itemRecordListW *MLDBAPI::GetAlbum(const wchar_t *albumname, const wchar_t *albumartist)
+{
+ wchar_t query[4096] = {0}; // hope it's big enough
+ if (albumartist && albumname)
+ {
+ StringCchPrintfW(query, 4096, L"((albumartist isempty and artist=\"%s\") or albumartist=\"%s\") and album=\"%s\"", albumartist, albumartist, albumname);
+ return Query(query);
+ }
+ else if (albumname)
+ {
+ StringCchPrintfW(query, 4096, L"album=\"%s\"", albumname);
+ return Query(query);
+ }
+ return 0;
+}
+
+itemRecordListW *MLDBAPI::Query(const wchar_t *query)
+{
+ itemRecordListW *result = 0;
+
+ if (query)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ NDE_Scanner_Query(s, query);
+ NDE_Scanner_First(s);
+
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+
+ if (!result)
+ result = (itemRecordListW *)calloc(1, sizeof(itemRecordListW));
+
+ if (!result)
+ break;
+
+ allocRecordList(result, result->Size + 1);
+ if (!result->Alloc) break;
+
+ result->Items[result->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->Items[result->Size].filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ while (NDE_Scanner_Next(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+itemRecordListW *MLDBAPI::QueryLimit(const wchar_t *query, unsigned int limit)
+{
+ itemRecordListW *result = 0;
+ if (limit == 0)
+ return 0;
+ if (query)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ NDE_Scanner_Query(s, query);
+ NDE_Scanner_First(s);
+
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+
+ if (!result)
+ {
+ result = (itemRecordListW *)calloc(1, sizeof(itemRecordListW));
+ if (!result)
+ break;
+
+ allocRecordList(result, limit);
+ if (!result->Alloc)
+ break;
+ }
+
+ result->Items[result->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->Items[result->Size].filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ while (result->Size < (int)limit && NDE_Scanner_Next(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+void MLDBAPI::FreeRecord(itemRecordW *record)
+{
+ freeRecord(record);
+}
+
+void MLDBAPI::FreeRecordList(itemRecordListW *recordList)
+{
+ freeRecordList(recordList);
+}
+
+bool FindFileInDB(nde_scanner_t s, const wchar_t *filename);
+void MLDBAPI::SetField(const wchar_t *filename, const char *field, const wchar_t *value)
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ if (FindFileInDB(s, filename))
+ {
+ if (value && value[0])
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (!f)
+ f=NDE_Scanner_NewFieldByName(s, field);
+
+ if (f)
+ NDE_StringField_SetString(f, value);
+ }
+ else
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (f)
+ NDE_Scanner_DeleteField(s, f);
+ }
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void MLDBAPI::SetFieldInteger(const wchar_t *filename, const char *field, int value)
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ if (FindFileInDB(s, filename))
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (!f)
+ f=NDE_Scanner_NewFieldByName(s, field);
+
+ if (f)
+ NDE_IntegerField_SetValue(f, value);
+
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void MLDBAPI::SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16])
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ if (FindFileInDB(s, filename))
+ {
+ if (value)
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (!f)
+ f=NDE_Scanner_NewFieldByName(s, field);
+
+ if (f)
+ NDE_Int128Field_SetValue(f, value);
+ }
+ else
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (f)
+ NDE_Scanner_DeleteField(s, f);
+ }
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void MLDBAPI::Sync()
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+int MLDBAPI::AddFile(const wchar_t *filename)
+{
+ int guess = g_config->ReadInt(L"guessmode", 0);
+ int meta = g_config->ReadInt(L"usemetadata", 1);
+ return addFileToDb(filename, 0, meta, guess);
+}
+
+int MLDBAPI::RemoveFile(const wchar_t *filename)
+{
+ return RemoveFileFromDB(filename);
+}
+
+void MLDBAPI::RetainString(wchar_t *str)
+{
+ ndestring_retain(str);
+}
+
+void MLDBAPI::ReleaseString(wchar_t *str)
+{
+ ndestring_release(str);
+}
+
+wchar_t *MLDBAPI::DuplicateString(const wchar_t *str)
+{
+ return ndestring_wcsdup(str);
+}
+
+int MLDBAPI::GetMaxInteger(const char *field, int *max)
+{
+ openDb();
+ if (!g_table)
+ return 1;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_field_t f = NDE_Table_GetColumnByName(g_table, field);
+ if (!f)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+
+ unsigned char field_id = NDE_ColumnField_GetFieldID(f);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ int maximum_so_far=0;
+ NDE_Scanner_Query(s, L"");
+ NDE_Scanner_First(s);
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, field_id);
+ if (f)
+ {
+ int this_value = NDE_IntegerField_GetValue(f);
+ if (this_value > maximum_so_far)
+ maximum_so_far = this_value;
+ }
+
+ } while (NDE_Scanner_Next(s) && !NDE_Scanner_EOF(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ *max=maximum_so_far;
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ else
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+}
+
+#define CBCLASS MLDBAPI
+START_DISPATCH;
+CB(API_MLDB_GETFILE, GetFile)
+CB(API_MLDB_GETFILEIF, GetFileIf)
+CB(API_MLDB_GETALBUM, GetAlbum)
+CB(API_MLDB_QUERY, Query)
+CB(API_MLDB_QUERYLIMIT, QueryLimit)
+VCB(API_MLDB_FREERECORD, FreeRecord)
+VCB(API_MLDB_FREERECORDLIST, FreeRecordList)
+VCB(API_MLDB_SETFIELD, SetField)
+VCB(API_MLDB_SETFIELDINT, SetFieldInteger)
+VCB(API_MLDB_SETFIELDINT128, SetFieldInt128)
+VCB(API_MLDB_SYNC, Sync)
+CB(API_MLDB_ADDFILE, AddFile)
+CB(API_MLDB_REMOVEFILE, RemoveFile)
+VCB(API_MLDB_RETAINSTRING, RetainString)
+VCB(API_MLDB_RELEASESTRING, ReleaseString)
+CB(API_MLDB_DUPLICATESTRING, DuplicateString)
+CB(API_MLDB_GETMAXINTEGER, GetMaxInteger)
+END_DISPATCH;
+#undef CBCLASS
+
diff --git a/Src/Plugins/Library/ml_local/mldbApi.h b/Src/Plugins/Library/ml_local/mldbApi.h
new file mode 100644
index 00000000..3ee0e2f7
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApi.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "api_mldb.h"
+
+class MLDBAPI : public api_mldb
+{
+public:
+ itemRecordW *GetFile(const wchar_t *filename);
+ itemRecordW *GetFileIf(const wchar_t *filename, const wchar_t *query);
+ itemRecordListW *GetAlbum(const wchar_t *albumname, const wchar_t *albumartist);
+ itemRecordListW *Query(const wchar_t *query);
+ itemRecordListW *QueryLimit(const wchar_t *query, unsigned int limit);
+
+ void SetField(const wchar_t *filename, const char *field, const wchar_t *value);
+ void SetFieldInteger(const wchar_t *filename, const char *field, int value);
+ void SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16]);
+ void Sync();
+
+ int AddFile(const wchar_t *filename);
+
+ void FreeRecord(itemRecordW *record);
+ void FreeRecordList(itemRecordListW *recordList);
+ int RemoveFile(const wchar_t *filename);
+
+ /* wrappers around ndestring */
+ void RetainString(wchar_t *str);
+ void ReleaseString(wchar_t *str);
+ wchar_t *DuplicateString(const wchar_t *str);
+
+ int GetMaxInteger(const char *field, int *max);
+protected:
+ RECVS_DISPATCH;
+};
+
diff --git a/Src/Plugins/Library/ml_local/mldbApiFactory.cpp b/Src/Plugins/Library/ml_local/mldbApiFactory.cpp
new file mode 100644
index 00000000..a2cc2cc9
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApiFactory.cpp
@@ -0,0 +1,61 @@
+#include "api__ml_local.h"
+#include "mldbApiFactory.h"
+#include "mldbApi.h"
+MLDBAPI mldbApi;
+static const char serviceName[] = "Media Library Database API";
+
+FOURCC MLDBAPIFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *MLDBAPIFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID MLDBAPIFactory::GetGUID()
+{
+ return mldbApiGuid;
+}
+
+void *MLDBAPIFactory::GetInterface(int global_lock)
+{
+ return &mldbApi;
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+}
+
+int MLDBAPIFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int MLDBAPIFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *MLDBAPIFactory::GetTestString()
+{
+ return NULL;
+}
+
+int MLDBAPIFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS MLDBAPIFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_local/mldbApiFactory.h b/Src/Plugins/Library/ml_local/mldbApiFactory.h
new file mode 100644
index 00000000..620b961a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApiFactory.h
@@ -0,0 +1,23 @@
+#ifndef NULLSOFT_MLDBFACTORY_H
+#define NULLSOFT_MLDBFACTORY_H
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class MLDBAPIFactory: public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/nde_error.txt b/Src/Plugins/Library/ml_local/nde_error.txt
new file mode 100644
index 00000000..e36baa49
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/nde_error.txt
@@ -0,0 +1,4 @@
+
+There was an error loading 'nde.dll' which is required for all of the local database functions.
+
+This could be due to a failed Winamp update or 'nde.dll' having been removed by mistake. \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/nde_itemRecord.cpp b/Src/Plugins/Library/ml_local/nde_itemRecord.cpp
new file mode 100644
index 00000000..7af7f6fc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/nde_itemRecord.cpp
@@ -0,0 +1,976 @@
+/*
+** Copyright © 2003-2014 Winamp SA
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include "../nde/nde.h"
+#include "../nu/sort.h"
+
+void freeRecord(itemRecord *p)
+{
+ if (p)
+ {
+ free(p->title);
+ free( p->artist );
+ free( p->ext );
+ free(p->comment);
+ free(p->album);
+ free(p->genre);
+ free(p->filename);
+ if (p->extended_info)
+ {
+ for (size_t x = 0; p->extended_info[x]; x ++)
+ free(p->extended_info[x]);
+ free(p->extended_info);
+ p->extended_info = 0;
+ }
+ }
+}
+
+void freeRecordList(itemRecordList *obj)
+{
+ if (obj)
+ {
+ emptyRecordList(obj);
+ _aligned_free(obj->Items);
+ obj->Items=0;
+ obj->Alloc=obj->Size=0;
+ }
+}
+
+void emptyRecordList(itemRecordList *obj)
+{
+ if (obj)
+ {
+ itemRecord *p=obj->Items;
+ while (obj->Size-->0)
+ {
+ freeRecord(p);
+ p++;
+ }
+ obj->Size=0;
+ }
+}
+
+void allocRecordList(itemRecordList *obj, int newsize, int granularity)
+{
+ if (newsize < obj->Alloc || newsize < obj->Size) return;
+
+ int old_Alloc = obj->Alloc;
+ obj->Alloc=newsize+granularity;
+
+ itemRecord *data = (itemRecord*)_aligned_realloc(obj->Items, sizeof(itemRecord) * obj->Alloc, 16);
+ if (!data)
+ {
+ data = (itemRecord*)_aligned_malloc(sizeof(itemRecord) * obj->Alloc, 16);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(itemRecord) * old_Alloc);
+ _aligned_free(obj->Items);
+ obj->Items = data;
+ }
+ else
+ obj->Alloc = old_Alloc;
+ }
+ else
+ obj->Items = data;
+
+ if (!obj->Items) obj->Alloc=0;
+}
+
+void compactRecordList(itemRecordListW *obj)
+{
+ if (obj->Size && obj->Size < obj->Alloc - 1024)
+ {
+ size_t old_Alloc = obj->Size;
+ obj->Alloc = obj->Size;
+
+ itemRecordW *data = (itemRecordW *)_aligned_realloc(obj->Items, sizeof(itemRecordW) * obj->Alloc, 16);
+ if (!data)
+ {
+ data = (itemRecordW *)_aligned_malloc(sizeof(itemRecordW) * obj->Alloc, 16);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(itemRecordW) * old_Alloc);
+ _aligned_free(obj->Items);
+ obj->Items = data;
+ }
+ else
+ obj->Alloc = old_Alloc;
+ }
+ else
+ obj->Items = data;
+ }
+}
+
+void copyRecord(itemRecord *out, const itemRecord *in)
+{
+#define COPYSTR(FOO) out->FOO = in->FOO ? _strdup(in->FOO) : 0;
+ COPYSTR(filename)
+ COPYSTR(title)
+ COPYSTR(ext)
+ COPYSTR(album)
+ COPYSTR(artist)
+ COPYSTR(comment)
+ COPYSTR(genre)
+ out->year=in->year;
+ out->track=in->track;
+ out->length=in->length;
+#undef COPYSTR
+ out->extended_info=0;
+
+ if (in->extended_info)
+ {
+ for (int y = 0; in->extended_info[y]; y ++)
+ {
+ char *p=in->extended_info[y];
+ if (*p) setRecordExtendedItem(out,p,p+strlen(p)+1);
+ }
+ }
+}
+
+void copyRecordList(itemRecordList *out, const itemRecordList *in)
+{
+ allocRecordList(out,out->Size+in->Size,0);
+ if (!out->Items) return;
+ for (int x = 0; x < in->Size; x ++)
+ {
+ copyRecord(&out->Items[out->Size++],&in->Items[x]);
+ }
+}
+
+char *getRecordExtendedItem(const itemRecord *item, const char *name)
+{
+ if (item->extended_info)
+ {
+ for (size_t x = 0; item->extended_info[x]; x ++)
+ {
+ if (!_stricmp(item->extended_info[x],name))
+ return item->extended_info[x]+strlen(name)+1;
+ }
+ }
+ return NULL;
+}
+
+void setRecordExtendedItem(itemRecord *item, const char *name, char *value)
+{
+ if (!item || !name) return;
+
+ size_t x = 0;
+ if (item->extended_info)
+ {
+ for (x = 0; item->extended_info[x]; x ++)
+ {
+ if (!_stricmp(item->extended_info[x],name))
+ {
+ size_t name_len = strlen(name), value_len = strlen(value);
+ if (value_len>strlen(item->extended_info[x]+name_len+1))
+ {
+ free(item->extended_info[x]);
+ item->extended_info[x]=(char*)malloc(name_len+value_len+2);
+ }
+ strncpy(item->extended_info[x], name, name_len);
+ strncpy(item->extended_info[x]+strlen(name)+1, value, value_len);
+ return;
+ }
+ }
+ }
+
+ // x=number of valid items.
+ char **data = (char**)realloc(item->extended_info,sizeof(char*) * (x+2));
+ if (data)
+ {
+ // if we could allocate then add, otherwise we'll have to skip
+ item->extended_info = data;
+
+ size_t name_len = strlen(name), value_len = strlen(value);
+ item->extended_info[x]=(char*)malloc(name_len+value_len+2);
+ strncpy(item->extended_info[x], name, name_len);
+ strncpy(item->extended_info[x]+name_len+1, value, value_len);
+
+ item->extended_info[x+1]=0;
+ }
+ else
+ {
+ data = (char**)malloc(sizeof(char*) * (x+2));
+ if (data)
+ {
+ memcpy(data, item->extended_info, sizeof(char*) * x);
+ free(item->extended_info);
+
+ // if we could allocate then add, otherwise we'll have to skip
+ item->extended_info = data;
+
+ size_t name_len = strlen(name), value_len = strlen(value);
+ item->extended_info[x]=(char*)malloc(name_len+value_len+2);
+ strncpy(item->extended_info[x], name, name_len);
+ strncpy(item->extended_info[x]+name_len+1, value, value_len);
+
+ item->extended_info[x+1]=0;
+ }
+ }
+}
+
+/*
+----------------------------------
+wide version starts here
+----------------------------------
+*/
+void freeRecord(itemRecordW *p)
+{
+ if (p)
+ {
+ ndestring_release(p->title);
+ ndestring_release(p->artist);
+ ndestring_release(p->comment);
+ ndestring_release(p->album);
+ ndestring_release(p->genre);
+ ndestring_release(p->filename);
+ ndestring_release(p->albumartist);
+ ndestring_release(p->replaygain_album_gain);
+ ndestring_release(p->replaygain_track_gain);
+ ndestring_release(p->publisher);
+ ndestring_release(p->composer);
+ if (p->extended_info)
+ {
+ for (size_t x = 0; p->extended_info[x].key; x ++)
+ {
+ // TODO release 'key' ?
+// ndestring_release(p->extended_info[x].key);
+ ndestring_release(p->extended_info[x].value);
+ }
+ free(p->extended_info);
+ p->extended_info = 0;
+ }
+ }
+}
+
+void freeRecordList(itemRecordListW *obj)
+{
+ if (obj)
+ {
+ emptyRecordList(obj);
+ _aligned_free(obj->Items);
+ obj->Items=0;
+ obj->Alloc=obj->Size=0;
+ }
+}
+
+void emptyRecordList(itemRecordListW *obj)
+{
+ if (obj)
+ {
+ itemRecordW *p=obj->Items;
+ while (obj->Size-->0)
+ {
+ freeRecord(p);
+ p++;
+ }
+ obj->Size=0;
+ }
+}
+
+void allocRecordList(itemRecordListW *obj, int newsize, int granularity)
+{
+ if (newsize < obj->Alloc || newsize < obj->Size) return;
+
+ int old_Alloc = obj->Alloc;
+ obj->Alloc=newsize+granularity;
+ itemRecordW *data = (itemRecordW*)_aligned_realloc(obj->Items,sizeof(itemRecordW)*obj->Alloc, 16);
+ if (!data)
+ {
+ data = (itemRecordW*)_aligned_malloc(sizeof(itemRecordW) * obj->Alloc, 16);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(itemRecordW) * obj->Alloc);
+ _aligned_free(obj->Items);
+ obj->Items = data;
+ }
+ else
+ obj->Alloc = old_Alloc;
+ }
+ else
+ obj->Items = data;
+
+ if (!obj->Items) obj->Alloc=0;
+}
+
+#if 0 // unused, don't re-enable until you verify the TODO below
+void copyRecord(itemRecordW *out, const itemRecordW *in)
+{
+#define COPYSTR(FOO) out->FOO = in->FOO ? _wcsdup(in->FOO) : 0;
+#define COPY(FOO) out->FOO = in->FOO;
+ /* this is only valid if 'in' is one of our item records! */
+ out->filename = in->filename;
+ NDEString *ndeString = WCHAR_TO_NDESTRING(out->filename);
+ if (ndeString) ndeString->Retain();
+ COPYSTR(title)
+ COPYSTR(album)
+ COPYSTR(artist)
+ COPYSTR(comment)
+ COPYSTR(genre)
+ COPYSTR(albumartist);
+ COPYSTR(replaygain_album_gain);
+ COPYSTR(replaygain_track_gain);
+ COPYSTR(publisher);
+ COPYSTR(composer);
+ COPY(year);
+ COPY(track);
+ COPY(tracks);
+ COPY(length);
+ COPY(rating);
+ COPY(playcount);
+ COPY(lastplay);
+ COPY(lastupd);
+ COPY(filetime);
+ COPY(filesize);
+ COPY(bitrate);
+ COPY(type);
+ COPY(disc);
+ COPY(discs);
+ COPY(bpm);
+ COPYSTR(category);
+#undef COPYSTR
+ out->extended_info=0;
+
+ if (in->extended_info)
+ {
+ for (int y = 0; in->extended_info[y].key; y ++)
+ {
+ setRecordExtendedItem(out,in->extended_info[y].key,in->extended_info[y].value);
+ }
+ }
+}
+
+void copyRecordList(itemRecordListW *out, const itemRecordListW *in)
+{
+ allocRecordList(out,out->Size+in->Size,0);
+ if (!out->Items) return;
+ for (size_t x = 0; x < in->Size; x ++)
+ {
+ copyRecord(&out->Items[out->Size++],&in->Items[x]);
+ }
+}
+#endif
+
+wchar_t *getRecordExtendedItem(const itemRecordW *item, const wchar_t *name)
+{
+ if (item && item->extended_info && name)
+ {
+ for (size_t x = 0; item->extended_info[x].key; x ++)
+ {
+ if (!_wcsicmp(item->extended_info[x].key,name))
+ return item->extended_info[x].value;
+ }
+ }
+ return NULL;
+}
+
+wchar_t *getRecordExtendedItem_fast(const itemRecordW *item, const wchar_t *name)
+{
+ if (item && item->extended_info && name)
+ {
+ for (size_t x = 0; item->extended_info[x].key; x ++)
+ {
+ if (item->extended_info[x].key == name)
+ return item->extended_info[x].value;
+ }
+ }
+ return NULL;
+}
+
+void setRecordExtendedItem(itemRecordW *item, const wchar_t *name, const wchar_t *value)
+{
+ if (!item || !name) return;
+
+ size_t x=0;
+ if (item->extended_info) for (x = 0; item->extended_info[x].key; x ++)
+ {
+ if (item->extended_info[x].key == name)
+ {
+ ndestring_retain(const_cast<wchar_t *>(value));
+ ndestring_release(item->extended_info[x].value);
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+ return;
+ }
+ }
+
+ // x=number of valid items.
+ extendedInfoW *data=(extendedInfoW *)realloc(item->extended_info,sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info=data;
+
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ else
+ {
+ data=(extendedInfoW *)malloc(sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info=data;
+
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ }
+}
+
+// this version assumes that the 'name' won't already be in the itemRecord
+void setRecordExtendedItem_fast(itemRecordW *item, const wchar_t *name, const wchar_t *value)
+{
+ if (!item || !name) return;
+
+ size_t x = 0;
+ if (item->extended_info)
+ {
+ for (x = 0; item->extended_info[x].key; x++)
+ {
+ }
+ }
+
+ // x=number of valid items.
+ extendedInfoW *data=(extendedInfoW *)realloc(item->extended_info,sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info = data;
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ else
+ {
+ data=(extendedInfoW *)malloc(sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info=data;
+
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ }
+}
+
+// TODO: redo this without AutoChar
+#include "../replicant/nu/AutoChar.h"
+#include "../nu/AutoCharFn.h"
+#define COPY_EXTENDED_STR(field) if (input-> ## field && input-> ## field ## [0]) setRecordExtendedItem(output, #field, AutoChar(input-> ## field));
+#define COPY_EXTENDED_INT(field) if (input->##field > 0) { char temp[64] = {0}; _itoa(input->##field, temp, 10); setRecordExtendedItem(output, #field, temp); }
+#define COPY_EXTENDED_INT64(field) if (input->##field > 0) { char temp[64] = {0}; _i64toa(input->##field, temp, 10); setRecordExtendedItem(output, #field, temp); }
+#define COPY_EXTENDED_INT0(field) if (input->##field >= 0) { char temp[64] = {0}; _itoa(input->##field, temp, 10); setRecordExtendedItem(output, #field, temp); }
+void convertRecord(itemRecord *output, const itemRecordW *input)
+{
+ output->filename=_strdup(AutoCharFn(input->filename));
+ output->title=AutoCharDup(input->title);
+ output->ext=AutoCharDup(input->ext);
+ output->album=AutoCharDup(input->album);
+ output->artist=AutoCharDup(input->artist);
+ output->comment=AutoCharDup(input->comment);
+ output->genre=AutoCharDup(input->genre);
+ output->year=input->year;
+ output->track=input->track;
+ output->length=input->length;
+ output->extended_info=0;
+ COPY_EXTENDED_STR(albumartist);
+ COPY_EXTENDED_STR(replaygain_album_gain);
+ COPY_EXTENDED_STR(replaygain_track_gain);
+ COPY_EXTENDED_STR(publisher);
+ COPY_EXTENDED_STR(composer);
+ COPY_EXTENDED_INT(tracks);
+ COPY_EXTENDED_INT(rating);
+ COPY_EXTENDED_INT(playcount);
+ COPY_EXTENDED_INT64(lastplay);
+ COPY_EXTENDED_INT64(lastupd);
+ COPY_EXTENDED_INT64(filetime);
+ COPY_EXTENDED_INT(filesize);
+ COPY_EXTENDED_INT(bitrate);
+ COPY_EXTENDED_INT0(type);
+ COPY_EXTENDED_INT(disc);
+ COPY_EXTENDED_INT(discs);
+ COPY_EXTENDED_INT(bpm);
+ COPY_EXTENDED_STR(category);
+
+ if (input->extended_info)
+ {
+ for (int y = 0; input->extended_info[y].key; y ++)
+ {
+ setRecordExtendedItem(output, AutoChar(input->extended_info[y].key), AutoChar(input->extended_info[y].value));
+ }
+ }
+}
+#undef COPY_EXTENDED_STR
+#undef COPY_EXTENDED_INT
+#undef COPY_EXTENDED_INT0
+
+#include "../replicant/nu/AutoWide.h"
+#define COPY_EXTENDED_STR(field) output->##field = ndestring_wcsdup(AutoWideDup(getRecordExtendedItem(input, #field)));
+#define COPY_EXTENDED_INT(field) { char *x = getRecordExtendedItem(input, #field); output->##field=x?atoi(x):-1; }
+
+void convertRecord(itemRecordW *output, const itemRecord *input)
+{
+ output->filename=ndestring_wcsdup(AutoWideDup(input->filename));
+ output->title=ndestring_wcsdup(AutoWideDup(input->title));
+ output->ext=ndestring_wcsdup(AutoWideDup(input->ext));
+ output->album=ndestring_wcsdup(AutoWideDup(input->album));
+ output->artist=ndestring_wcsdup(AutoWideDup(input->artist));
+ output->comment=ndestring_wcsdup(AutoWideDup(input->comment));
+ output->genre=ndestring_wcsdup(AutoWideDup(input->genre));
+ output->year=input->year;
+ output->track=input->track;
+ output->length=input->length;
+ output->extended_info=0;
+ COPY_EXTENDED_STR(albumartist);
+ COPY_EXTENDED_STR(replaygain_album_gain);
+ COPY_EXTENDED_STR(replaygain_track_gain);
+ COPY_EXTENDED_STR(publisher);
+ COPY_EXTENDED_STR(composer);
+ COPY_EXTENDED_INT(tracks);
+ COPY_EXTENDED_INT(rating);
+ COPY_EXTENDED_INT(playcount);
+ COPY_EXTENDED_INT(lastplay);
+ COPY_EXTENDED_INT(lastupd);
+ COPY_EXTENDED_INT(filetime);
+ COPY_EXTENDED_INT(filesize);
+ COPY_EXTENDED_INT(type);
+ COPY_EXTENDED_INT(disc);
+ COPY_EXTENDED_INT(discs);
+ COPY_EXTENDED_INT(bpm);
+ COPY_EXTENDED_INT(bitrate);
+ COPY_EXTENDED_STR(composer);
+ COPY_EXTENDED_STR(category);
+ // TODO: copy input's extended fields
+}
+#undef COPY_EXTENDED_STR
+#undef COPY_EXTENDED_INT
+
+void convertRecordList(itemRecordList *output, const itemRecordListW *input)
+{
+ output->Alloc = output->Size = input->Size;
+ output->Items = (itemRecord*)_aligned_malloc(sizeof(itemRecord)*input->Size, 16);
+ if (output->Items)
+ {
+ memset(output->Items, 0, sizeof(itemRecord)*input->Size);
+ for(int i=0; i < input->Size; i++)
+ {
+ convertRecord(&output->Items[i],&input->Items[i]);
+ }
+ }
+ else
+ output->Alloc = output->Size = 0;
+}
+
+void convertRecordList(itemRecordListW *output, const itemRecordList *input)
+{
+ output->Alloc = output->Size = input->Size;
+ output->Items = (itemRecordW*)malloc(sizeof(itemRecordW) * input->Size);
+ if (output->Items)
+ {
+ memset(output->Items, 0, sizeof(itemRecordW) * input->Size);
+ for(int i=0; i < input->Size; i++)
+ {
+ convertRecord(&output->Items[i],&input->Items[i]);
+ }
+ }
+}
+
+extern int sse_flag;
+
+// swaps two 16 byte aligned 128-byte values
+#ifdef _M_X64
+#include <emmintrin.h>
+#endif
+static void __fastcall swap128(uint8_t *_a, uint8_t *_b)
+{
+ // ECX = a
+ // EDX = b
+#ifdef _M_IX86
+ _asm
+ {
+ // load first 64 bytes of each
+ movaps xmm0, XMMWORD PTR [ecx+0]
+ movaps xmm1, XMMWORD PTR [ecx+16]
+ movaps xmm2, XMMWORD PTR [ecx+32]
+ movaps xmm3, XMMWORD PTR [ecx+48]
+ movaps xmm4, XMMWORD PTR [edx+0]
+ movaps xmm5, XMMWORD PTR [edx+16]
+ movaps xmm6, XMMWORD PTR [edx+32]
+ movaps xmm7, XMMWORD PTR [edx+48]
+
+ // store
+ movaps XMMWORD PTR [edx+0], xmm0
+ movaps XMMWORD PTR [edx+16], xmm1
+ movaps XMMWORD PTR [edx+32], xmm2
+ movaps XMMWORD PTR [edx+48], xmm3
+ movaps XMMWORD PTR [ecx+0], xmm4
+ movaps XMMWORD PTR [ecx+16], xmm5
+ movaps XMMWORD PTR [ecx+32], xmm6
+ movaps XMMWORD PTR [ecx+48], xmm7
+
+ // load second 64 bytes of each
+ movaps xmm0, XMMWORD PTR [ecx+64]
+ movaps xmm1, XMMWORD PTR [ecx+80]
+ movaps xmm2, XMMWORD PTR [ecx+96]
+ movaps xmm3, XMMWORD PTR [ecx+112]
+ movaps xmm4, XMMWORD PTR [edx+64]
+ movaps xmm5, XMMWORD PTR [edx+80]
+ movaps xmm6, XMMWORD PTR [edx+96]
+ movaps xmm7, XMMWORD PTR [edx+112]
+
+ // store
+ movaps XMMWORD PTR [edx+64], xmm0
+ movaps XMMWORD PTR [edx+80], xmm1
+ movaps XMMWORD PTR [edx+96], xmm2
+ movaps XMMWORD PTR [edx+112], xmm3
+ movaps XMMWORD PTR [ecx+64], xmm4
+ movaps XMMWORD PTR [ecx+80], xmm5
+ movaps XMMWORD PTR [ecx+96], xmm6
+ movaps XMMWORD PTR [ecx+112], xmm7
+ }
+#else
+ //sizeof(itemRecordW) is 176 on AMD64. that's 11 SSE registers
+ __m128i b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10;
+ __m128i a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
+ __m128i *a = (__m128i *)_a;
+ __m128i *b = (__m128i *)_b;
+
+ a0 = _mm_load_si128(&a[0]);
+ a1 = _mm_load_si128(&a[1]);
+ a2 = _mm_load_si128(&a[2]);
+ a3 = _mm_load_si128(&a[3]);
+ a4 = _mm_load_si128(&a[4]);
+ a5 = _mm_load_si128(&a[5]);
+ a6 = _mm_load_si128(&a[6]);
+ a7 = _mm_load_si128(&a[7]);
+ a8 = _mm_load_si128(&a[0]);
+ a9 = _mm_load_si128(&a[1]);
+ a10 = _mm_load_si128(&a[2]);
+
+ b0 = _mm_load_si128(&b[0]);
+ b1 = _mm_load_si128(&b[1]);
+ b2 = _mm_load_si128(&b[2]);
+ b3 = _mm_load_si128(&b[3]);
+ b4 = _mm_load_si128(&b[4]);
+ b5 = _mm_load_si128(&b[5]);
+ b6 = _mm_load_si128(&b[6]);
+ b7 = _mm_load_si128(&b[7]);
+ b8 = _mm_load_si128(&b[8]);
+ b9 = _mm_load_si128(&b[9]);
+ b10 = _mm_load_si128(&b[10]);
+
+ _mm_store_si128(&a[0], b0);
+ _mm_store_si128(&a[1], b1);
+ _mm_store_si128(&a[2], b2);
+ _mm_store_si128(&a[3], b3);
+ _mm_store_si128(&a[4], b4);
+ _mm_store_si128(&a[5], b5);
+ _mm_store_si128(&a[6], b6);
+ _mm_store_si128(&a[7], b7);
+ _mm_store_si128(&b[0], a0);
+ _mm_store_si128(&b[1], a1);
+ _mm_store_si128(&b[2], a2);
+ _mm_store_si128(&b[3], a3);
+ _mm_store_si128(&b[4], a4);
+ _mm_store_si128(&b[5], a5);
+ _mm_store_si128(&b[6], a6);
+ _mm_store_si128(&b[7], a7);
+
+
+#endif
+}
+
+/***
+*shortsort(hi, lo, width, comp) - insertion sort for sorting short arrays
+*
+*Purpose:
+* sorts the sub-array of elements between lo and hi (inclusive)
+* side effects: sorts in place
+* assumes that lo < hi
+*
+*Entry:
+* char *lo = pointer to low element to sort
+* char *hi = pointer to high element to sort
+* size_t width = width in bytes of each array element
+* int (*comp)() = pointer to function returning analog of strcmp for
+* strings, but supplied by user for comparing the array elements.
+* it accepts 2 pointers to elements and returns neg if 1<2, 0 if
+* 1=2, pos if 1>2.
+*
+*Exit:
+* returns void
+*
+*Exceptions:
+*
+*******************************************************************************/
+
+static void shortsort_sse(uint8_t *lo, uint8_t *hi, const void *context,
+ int (__fastcall *comp)(const void *, const void *, const void *))
+{
+ const size_t width=sizeof(itemRecordW);
+ uint8_t *p;
+
+ /* Note: in assertions below, i and j are alway inside original bound of
+ array to sort. */
+
+ while (hi > lo) {
+ /* A[i] <= A[j] for i <= j, j > hi */
+ uint8_t *max = lo;
+ for (p = lo+width; p <= hi; p += width) {
+ /* A[i] <= A[max] for lo <= i < p */
+ if (comp(p, max, context) > 0) {
+ max = p;
+ }
+ /* A[i] <= A[max] for lo <= i <= p */
+ }
+
+ /* A[i] <= A[max] for lo <= i <= hi */
+
+ swap128(max, hi);
+
+ /* A[i] <= A[hi] for i <= hi, so A[i] <= A[j] for i <= j, j >= hi */
+
+ hi -= width;
+
+ /* A[i] <= A[j] for i <= j, j > hi, loop top condition established */
+ }
+ /* A[i] <= A[j] for i <= j, j > lo, which implies A[i] <= A[j] for i < j,
+ so array is sorted */
+}
+
+#define CUTOFF 8
+#define STKSIZ (8*sizeof(void*) - 2)
+extern "C" void qsort_itemRecord(void *base, size_t num, const void *context,
+ int (__fastcall *comp)(const void *, const void *, const void *))
+{
+ /* Note: the number of stack entries required is no more than
+ 1 + log2(num), so 30 is sufficient for any array */
+ uint8_t *lo, *hi; /* ends of sub-array currently sorting */
+ uint8_t *mid; /* points to middle of subarray */
+ uint8_t *loguy, *higuy; /* traveling pointers for partition step */
+ size_t size; /* size of the sub-array */
+ uint8_t *lostk[STKSIZ], *histk[STKSIZ];
+ int stkptr; /* stack for saving sub-array to be processed */
+ const size_t width=sizeof(itemRecordW);
+
+#ifdef _M_IX86
+ assert(sizeof(itemRecordW) == 128);
+#elif defined (_M_X64)
+ assert(sizeof(itemRecordW) == 176);
+#else
+#error not implemented on this processor!
+#endif
+ if (!sse_flag)
+ {
+ nu::qsort(base, num, sizeof(itemRecordW), context, comp);
+ return;
+ }
+
+ if (num < 2)
+ return; /* nothing to do */
+
+ stkptr = 0; /* initialize stack */
+
+ lo = static_cast<uint8_t *>(base);
+ hi = (uint8_t *)base + width * (num-1); /* initialize limits */
+
+ /* this entry point is for pseudo-recursion calling: setting
+ lo and hi and jumping to here is like recursion, but stkptr is
+ preserved, locals aren't, so we preserve stuff on the stack */
+recurse:
+
+ size = (hi - lo) / width + 1; /* number of el's to sort */
+
+ /* below a certain size, it is faster to use a O(n^2) sorting method */
+ if (size <= CUTOFF) {
+ shortsort_sse(lo, hi, context, comp);
+ }
+ else {
+ /* First we pick a partitioning element. The efficiency of the
+ algorithm demands that we find one that is approximately the median
+ of the values, but also that we select one fast. We choose the
+ median of the first, middle, and last elements, to avoid bad
+ performance in the face of already sorted data, or data that is made
+ up of multiple sorted runs appended together. Testing shows that a
+ median-of-three algorithm provides better performance than simply
+ picking the middle element for the latter case. */
+
+ mid = lo + (size / 2) * width; /* find middle element */
+
+ /* Sort the first, middle, last elements into order */
+ if (comp(lo, mid, context) > 0) {
+ swap128(lo, mid);
+ }
+ if (comp(lo, hi, context) > 0) {
+ swap128(lo, hi);
+ }
+ if (comp(mid, hi, context) > 0) {
+ swap128(mid, hi);
+ }
+
+ /* We now wish to partition the array into three pieces, one consisting
+ of elements <= partition element, one of elements equal to the
+ partition element, and one of elements > than it. This is done
+ below; comments indicate conditions established at every step. */
+
+ loguy = lo;
+ higuy = hi;
+
+ /* Note that higuy decreases and loguy increases on every iteration,
+ so loop must terminate. */
+ for (;;) {
+ /* lo <= loguy < hi, lo < higuy <= hi,
+ A[i] <= A[mid] for lo <= i <= loguy,
+ A[i] > A[mid] for higuy <= i < hi,
+ A[hi] >= A[mid] */
+
+ /* The doubled loop is to avoid calling comp(mid,mid), since some
+ existing comparison funcs don't work when passed the same
+ value for both pointers. */
+
+ if (mid > loguy) {
+ do {
+ loguy += width;
+ } while (loguy < mid && comp(loguy, mid, context) <= 0);
+ }
+ if (mid <= loguy) {
+ do {
+ loguy += width;
+ } while (loguy <= hi && comp(loguy, mid, context) <= 0);
+ }
+
+ /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
+ either loguy > hi or A[loguy] > A[mid] */
+
+ do {
+ higuy -= width;
+ } while (higuy > mid && comp(higuy, mid, context) > 0);
+
+ /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi,
+ either higuy == lo or A[higuy] <= A[mid] */
+
+ if (higuy < loguy)
+ break;
+
+ /* if loguy > hi or higuy == lo, then we would have exited, so
+ A[loguy] > A[mid], A[higuy] <= A[mid],
+ loguy <= hi, higuy > lo */
+
+ swap128(loguy, higuy);
+
+ /* If the partition element was moved, follow it. Only need
+ to check for mid == higuy, since before the swap,
+ A[loguy] > A[mid] implies loguy != mid. */
+
+ if (mid == higuy)
+ mid = loguy;
+
+ /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
+ of loop is re-established */
+ }
+
+ /* A[i] <= A[mid] for lo <= i < loguy,
+ A[i] > A[mid] for higuy < i < hi,
+ A[hi] >= A[mid]
+ higuy < loguy
+ implying:
+ higuy == loguy-1
+ or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */
+
+ /* Find adjacent elements equal to the partition element. The
+ doubled loop is to avoid calling comp(mid,mid), since some
+ existing comparison funcs don't work when passed the same value
+ for both pointers. */
+
+ higuy += width;
+ if (mid < higuy) {
+ do {
+ higuy -= width;
+ } while (higuy > mid && comp(higuy, mid, context) == 0);
+ }
+ if (mid >= higuy) {
+ do {
+ higuy -= width;
+ } while (higuy > lo && comp(higuy, mid, context) == 0);
+ }
+
+ /* OK, now we have the following:
+ higuy < loguy
+ lo <= higuy <= hi
+ A[i] <= A[mid] for lo <= i <= higuy
+ A[i] == A[mid] for higuy < i < loguy
+ A[i] > A[mid] for loguy <= i < hi
+ A[hi] >= A[mid] */
+
+ /* We've finished the partition, now we want to sort the subarrays
+ [lo, higuy] and [loguy, hi].
+ We do the smaller one first to minimize stack usage.
+ We only sort arrays of length 2 or more.*/
+
+ if ( higuy - lo >= hi - loguy ) {
+ if (lo < higuy) {
+ lostk[stkptr] = lo;
+ histk[stkptr] = higuy;
+ ++stkptr;
+ } /* save big recursion for later */
+
+ if (loguy < hi) {
+ lo = loguy;
+ goto recurse; /* do small recursion */
+ }
+ }
+ else {
+ if (loguy < hi) {
+ lostk[stkptr] = loguy;
+ histk[stkptr] = hi;
+ ++stkptr; /* save big recursion for later */
+ }
+
+ if (lo < higuy) {
+ hi = higuy;
+ goto recurse; /* do small recursion */
+ }
+ }
+ }
+
+ /* We have sorted the array, except for any pending sorts on the stack.
+ Check if there are any, and do them. */
+
+ --stkptr;
+ if (stkptr >= 0) {
+ lo = lostk[stkptr];
+ hi = histk[stkptr];
+ goto recurse; /* pop subarray from stack */
+ }
+ else
+ return; /* all subarrays done */
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/pe_subclass.cpp b/Src/Plugins/Library/ml_local/pe_subclass.cpp
new file mode 100644
index 00000000..67de6b1f
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/pe_subclass.cpp
@@ -0,0 +1,76 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "resource.h"
+
+static WNDPROC PE_oldWndProc;
+static HMENU last_viewmenu = NULL;
+static WORD waCmdMenuID;
+
+static INT_PTR CALLBACK PE_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_COMMAND && wParam > 45000 && wParam < 57000)
+ {
+ int n = wParam - 45000;
+ if (m_query_list[n])
+ {
+ queryItem *item = m_query_list[n];
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf, item->query, 0, 0);
+
+ return 0;
+ }
+ }
+ else if (uMsg == WM_INITMENUPOPUP)
+ {
+ HMENU hmenuPopup = (HMENU) wParam;
+ if (hmenuPopup == wa_playlists_cmdmenu)
+ {
+ if (!waCmdMenuID)
+ {
+ waCmdMenuID = (WORD)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_REGISTER_LOWORD_COMMAND);
+ }
+ if (last_viewmenu)
+ {
+ RemoveMenu(wa_playlists_cmdmenu, waCmdMenuID, MF_BYCOMMAND);
+ DestroyMenu(last_viewmenu);
+ last_viewmenu = NULL;
+ }
+
+ mlGetTreeStruct mgts = { 1000, 45000, -1};
+ last_viewmenu = (HMENU)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & mgts, ML_IPC_GETTREE);
+ if (last_viewmenu)
+ {
+ MENUITEMINFOW menuItem = {sizeof(MENUITEMINFOW), MIIM_SUBMENU | MIIM_ID | MIIM_TYPE, MFT_STRING,
+ MFS_ENABLED, waCmdMenuID, last_viewmenu, NULL, NULL, NULL,
+ WASABI_API_LNGSTRINGW(IDS_OPEN_MEDIA_LIBRARY_VIEW_RESULTS), 0};
+
+ if (GetMenuItemCount(last_viewmenu) > 0)
+ {
+ InsertMenuItemW(wa_playlists_cmdmenu, 1, TRUE, &menuItem);
+ }
+ else
+ {
+ DestroyMenu(last_viewmenu);
+ last_viewmenu=0;
+ }
+ }
+ }
+ }
+ return CallWindowProc(PE_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+static HWND hwnd_pe = NULL;
+void HookPlaylistEditor()
+{
+ hwnd_pe = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, IPC_GETWND_PE, IPC_GETWND);
+
+ if (IsWindow(hwnd_pe))
+ PE_oldWndProc = (WNDPROC) SetWindowLongPtrW(hwnd_pe, GWLP_WNDPROC, (LONG_PTR)PE_newWndProc);
+}
+
+void UnhookPlaylistEditor()
+{
+ SetWindowLongPtrW(hwnd_pe, GWLP_WNDPROC, (LONG_PTR)PE_oldWndProc);
+}
diff --git a/Src/Plugins/Library/ml_local/prefs.cpp b/Src/Plugins/Library/ml_local/prefs.cpp
new file mode 100644
index 00000000..ffeef4da
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/prefs.cpp
@@ -0,0 +1,936 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "ml_local.h"
+#include <time.h>
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "./scanfolderbrowser.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/AutoWide.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+extern HWND subWnd = 0, prefsWnd = 0;
+extern int g_bgrescan_int, g_bgrescan_do, g_autochannel_do;
+HWND g_bgrescan_status_hwnd;
+static W_ListView *m_dir_lv;
+
+
+static void EnableDisableRecentPlayingControls( HWND hwndDlg, int tracking )
+{
+ if ( tracking == -1 ) tracking = !!g_config->ReadInt( L"trackplays", 1 );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_CHECK7 ), tracking );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_CHECK8 ), tracking );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT2 ), tracking && !!g_config->ReadInt( L"trackplays_wait_secs", 0 ) );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT3 ), tracking && !!g_config->ReadInt( L"trackplays_wait_percent", 0 ) );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC4 ), tracking );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC5 ), tracking );
+}
+
+// Recently Played
+// When 'Recently Played' is enabled, Winamp will keep track of\nwhen and how many times items in the Media Library are played.
+static INT_PTR CALLBACK Prefs1Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ CheckDlgButton( hwndDlg, IDC_REMEMBER_SEARCH, !!g_config->ReadInt( L"remembersearch", 0 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK3, !!g_config->ReadInt( L"useminiinfo2", 0 ) );
+
+ CheckDlgButton( hwndDlg, IDC_CHECK_ATF, !!g_config->ReadInt( L"newtitle", 1 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK5, !!g_config->ReadInt( L"audiorefine", 0 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK6, !g_config->ReadInt( L"dbloadatstart", 1 ) );
+
+ CheckDlgButton( hwndDlg, IDC_ARTIST_AS_ALBUMARTIST, g_config->ReadInt( L"artist_as_albumartist", 1 ) );
+
+ SetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, g_config->ReadInt( L"querydelay", 250 ), 0 );
+
+ int tracking = !!g_config->ReadInt( L"trackplays", 1 );
+ CheckDlgButton( hwndDlg, IDC_CHECK2, tracking );
+ CheckDlgButton( hwndDlg, IDC_CHECK7, !!g_config->ReadInt( L"trackplays_wait_secs", 0 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK8, !!g_config->ReadInt( L"trackplays_wait_percent", 0 ) );
+ SetDlgItemInt( hwndDlg, IDC_EDIT2, g_config->ReadInt( L"trackplays_wait_secs_lim", 5 ), 0 );
+ SetDlgItemInt( hwndDlg, IDC_EDIT3, g_config->ReadInt( L"trackplays_wait_percent_lim", 50 ), 0 );
+ EnableDisableRecentPlayingControls( hwndDlg, tracking );
+ }
+ break;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_CHECK3:
+ g_config->WriteInt( L"useminiinfo2", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK3 ) );
+ PostMessage( plugin.hwndLibraryParent, WM_USER + 30, 0, 0 );
+ break;
+ case IDC_REMEMBER_SEARCH:
+ g_config->WriteInt( L"remembersearch", !!IsDlgButtonChecked( hwndDlg, IDC_REMEMBER_SEARCH ) );
+ break;
+ case IDC_CHECK_ATF:
+ g_config->WriteInt( L"newtitle", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK_ATF ) );
+ break;
+ case IDC_CHECK5:
+ g_config->WriteInt( L"audiorefine", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK5 ) );
+ if ( plugin.hwndLibraryParent ) PostMessage( plugin.hwndLibraryParent, WM_USER + 30, 0, 0 );
+ break;
+ case IDC_CHECK6:
+ g_config->WriteInt( L"dbloadatstart", !IsDlgButtonChecked( hwndDlg, IDC_CHECK6 ) );
+ break;
+ case IDC_CHECK2:
+ {
+ int tracking = !!IsDlgButtonChecked( hwndDlg, IDC_CHECK2 );
+ g_config->WriteInt( L"trackplays", tracking );
+ EnableDisableRecentPlayingControls( hwndDlg, tracking );
+ }
+ break;
+ case IDC_CHECK7:
+ {
+ g_config->WriteInt( L"trackplays_wait_secs", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK7 ) );
+ EnableDisableRecentPlayingControls( hwndDlg, -1 );
+ }
+ break;
+ case IDC_CHECK8:
+ {
+ g_config->WriteInt( L"trackplays_wait_percent", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK8 ) );
+ EnableDisableRecentPlayingControls( hwndDlg, -1 );
+ }
+ break;
+ case IDC_BUTTON2:
+ {
+ wchar_t title[ 64 ] = { 0 };
+ WASABI_API_LNGSTRINGW_BUF( IDS_RECENTLY_PLAYED, title, 64 );
+ MessageBoxW( hwndDlg, WASABI_API_LNGSTRINGW( IDS_RECENTLY_PLAYED_TEXT ), title, 0 );
+ }
+ break;
+ case IDC_ARTIST_AS_ALBUMARTIST:
+ {
+ int oldValue = g_config->ReadInt( L"artist_as_albumartist", 1 );
+ int newValue = !!IsDlgButtonChecked( hwndDlg, IDC_ARTIST_AS_ALBUMARTIST );
+ if ( oldValue != newValue )
+ {
+ // TODO: prompt to re-read metadata on entire library
+ }
+ g_config->WriteInt( L"artist_as_albumartist", newValue );
+ }
+ break;
+ case IDC_EDIT_QUERYDELAY:
+ {
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ BOOL t;
+ int v = GetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, &t, FALSE );
+ if ( t )
+ {
+ if ( v < 1 )
+ {
+ v = 1;
+ SetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, v, 0 );
+ }
+ else if ( v > 5000 )
+ {
+ v = 5000;
+ SetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, v, 0 );
+ }
+ g_config->WriteInt( L"querydelay", v );
+ g_querydelay = v;
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON1:
+ nukeLibrary( hwndDlg );
+ break;
+ case IDC_EDIT2:
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ BOOL t;
+ int v = GetDlgItemInt( hwndDlg, IDC_EDIT2, &t, FALSE );
+ if ( t )
+ {
+ if ( v < 0 )
+ {
+ v = 1;
+ SetDlgItemInt( hwndDlg, IDC_EDIT2, v, 0 );
+ }
+ g_config->WriteInt( L"trackplays_wait_secs_lim", v );
+ }
+ }
+ break;
+ case IDC_EDIT3:
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ BOOL t;
+ int v = GetDlgItemInt( hwndDlg, IDC_EDIT3, &t, FALSE );
+ if ( t )
+ {
+ int tweaked = 0;
+ if ( v > 99 )
+ {
+ v = 99;
+ tweaked = 1;
+ }
+ else if ( v < 1 )
+ {
+ v = 1;
+ tweaked = 1;
+ }
+ if ( tweaked )
+ {
+ SetDlgItemInt( hwndDlg, IDC_EDIT3, v, 0 );
+ }
+
+ g_config->WriteInt( L"trackplays_wait_percent_lim", v );
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void parseMetaStr( wchar_t *buf2, int *guess, int *meta, int *subdir )
+{
+ wchar_t metaPS[ 16 ] = { 0 }, metaMS[ 16 ] = { 0 }, smartPS[ 16 ] = { 0 }, smartMS[ 16 ] = { 0 }, guessS[ 16 ] = { 0 }, recurseS[ 16 ] = { 0 };
+ StringCchPrintfW( recurseS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( guessS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_GUESS_STR ) );
+ StringCchPrintfW( smartPS, 16, L"+%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ StringCchPrintfW( smartMS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ StringCchPrintfW( metaPS, 16, L"+%s", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+ StringCchPrintfW( metaMS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+
+ if ( wcsstr( buf2, metaPS ) )
+ {
+ *meta = 1;
+ }
+ else if ( wcsstr( buf2, metaMS ) )
+ {
+ *meta = 0;
+ }
+ else
+ {
+ *meta = -1;
+ }
+ if ( wcsstr( buf2, smartPS ) )
+ {
+ *guess = 0;
+ }
+ else if ( wcsstr( buf2, smartMS ) )
+ {
+ *guess = 1;
+ }
+ else if ( wcsstr( buf2, guessS ) )
+ {
+ *guess = 2;
+ }
+ else
+ {
+ *guess = -1;
+ }
+ if ( wcsstr( buf2, recurseS ) )
+ {
+ *subdir = 0;
+ }
+ else
+ {
+ *subdir = 1;
+ }
+}
+
+static void makeMetaStr( int guess_mode, int use_metadata, int subdir, wchar_t *buf, int bufLen )
+{
+ wchar_t guessstr[ 32 ] = { 0 }, recurseS[ 16 ] = { 0 };
+ if ( guess_mode >= 0 )
+ {
+ if ( guess_mode == 1 )
+ StringCchPrintfW( guessstr, 32, L"-%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ else if ( guess_mode == 2 )
+ StringCchPrintfW( guessstr, 32, L"-%s", WASABI_API_LNGSTRINGW( IDS_GUESS_STR ) );
+ else
+ StringCchPrintfW( guessstr, 32, L"+%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ }
+
+ if ( use_metadata >= 0 && guess_mode >= 0 )
+ {
+ wchar_t bufS[ 64 ] = { 0 };
+ StringCchPrintfW( bufS, 64, L"%%c%s%%s%%s", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+ StringCchPrintfW( recurseS, 16, L"%s%s", subdir ? L"" : L"-", subdir ? L"" : WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( buf, bufLen, bufS, use_metadata ? L'+' : L'-', guessstr, recurseS );
+ }
+ else if ( use_metadata >= 0 )
+ {
+ wchar_t metaS[ 16 ] = { 0 };
+ StringCchPrintfW( metaS, 16, L"%s%s", use_metadata ? L"+" : L"-", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+ StringCchPrintfW( recurseS, 16, L"%s%s", subdir ? L"" : L"-", subdir ? L"" : WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( buf, bufLen, L"%s%s", metaS, recurseS );
+ }
+ else if ( guess_mode >= 0 )
+ {
+ StringCchPrintfW( recurseS, 16, L"%s%s", subdir ? L"" : L"-", subdir ? L"" : WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( buf, bufLen, L"%s%s", guessstr, recurseS );
+ }
+ else if ( !subdir )
+ {
+ StringCchPrintfW( buf, bufLen, L"-%s", WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ }
+ else
+ {
+ StringCchCopyW( buf, bufLen, WASABI_API_LNGSTRINGW( IDS_DEFAULT ) );
+ }
+}
+
+static void saveList()
+{
+ GayStringW gs;
+ int a = 0;
+ int x, l = m_dir_lv->GetCount();
+ for ( x = 0; x < l; x++ )
+ {
+ wchar_t buf[ 1024 ] = { 0 }, buf2[ 64 ] = { 0 };
+ m_dir_lv->GetText( x, 0, buf2, 64 );
+ m_dir_lv->GetText( x, 1, buf, 1024 );
+ if ( buf[ 0 ] )
+ {
+ if ( a )
+ gs.Append( L"|" );
+ else
+ a = 1;
+
+ int meta;
+ int guess;
+ int subdir;
+ parseMetaStr( buf2, &guess, &meta, &subdir );
+
+ if ( meta >= 0 || guess >= 0 || !subdir )
+ {
+ gs.Append( L"<" );
+ if ( guess >= 0 )
+ gs.Append( guess == 1 ? L"s" : ( guess == 2 ? L"g" : L"S" ) );
+
+ if ( meta >= 0 )
+ gs.Append( meta ? L"M" : L"m" );
+
+ if ( !subdir )
+ gs.Append( L"r" );
+
+ gs.Append( L">" );
+ }
+ gs.Append( buf );
+ }
+ }
+ g_config->WriteString( "scandirlist", 0 ); // erase the old ini value
+ g_config->WriteString( "scandirlist_utf8", AutoChar( gs.Get(), CP_UTF8 ) );
+}
+
+int autoscan_add_directory( const wchar_t *path, int *guess, int *meta, int *recurse, int noaddjustcheck )
+{
+ char *_s1 = g_config->ReadString( "scandirlist", "" );
+ UINT codePage = CP_ACP;
+ if ( !_s1 || !*_s1 )
+ {
+ _s1 = g_config->ReadString( "scandirlist_utf8", "" );
+ codePage = CP_UTF8;
+ }
+
+ wchar_t *s1 = AutoWideDup( _s1, codePage );
+
+ int bufLen = 0;
+ wchar_t *s = (wchar_t *)calloc( ( bufLen = ( wcslen( s1 ) + 2 ) ), sizeof( wchar_t ) );
+ if ( s )
+ {
+ wcsncpy( s, s1, bufLen );
+ s[ wcslen( s ) + 1 ] = 0;
+
+ wchar_t *p = s;
+ while ( p && *p == L'|' ) p++;
+
+ while ( ( p = wcsstr( p, L"|" ) ) )
+ {
+ *p++ = 0;
+ while ( p && *p == L'|' ) p++;
+ }
+ p = s;
+ while ( p && *p )
+ {
+ while ( p && *p == L'|' ) p++;
+
+ int use_meta = -1;
+ int do_guess = -1;
+ int do_recurse = 1;
+ if ( *p == L'<' && wcsstr( p, L">" ) )
+ {
+ p++;
+ while ( p && *p != L'>' )
+ {
+ // <MmSs>can prefix directory
+ // M=metadata use override
+ // m=no metadata
+ // S=smart guessing
+ // s=stupid guessing
+ // g=no guessing
+ if ( *p == L'M' )
+ use_meta = 1;
+ else if ( *p == L'm' )
+ use_meta = 0;
+ else if ( *p == L'S' )
+ do_guess = 0;
+ else if ( *p == L's' )
+ do_guess = 1;
+ else if ( *p == L'g' )
+ do_guess = 2;
+ else if ( *p == L'r' )
+ do_recurse = 0;
+
+ p++;
+ }
+ p++;
+ }
+
+ if ( !_wcsnicmp( p, path, wcslen( p ) ) && ( path[ wcslen( p ) ] == L'\\' || !path[ wcslen( p ) ] ) )
+ {
+ free( s ); // directory already in there
+
+ if ( meta )
+ *meta = use_meta;
+
+ if ( guess )
+ *guess = do_guess;
+
+ if ( recurse )
+ *recurse = do_recurse;
+
+ return 1;
+ }
+ p += wcslen( p ) + 1;
+ }
+ free( s );
+ }
+
+ if ( !noaddjustcheck )
+ {
+ WIN32_FIND_DATAW fd = { 0 };
+ int bufLen = 0;
+ wchar_t *s = (wchar_t *)calloc( ( bufLen = ( wcslen( path ) + 32 ) ), sizeof( wchar_t ) );
+ StringCchPrintfW( s, bufLen, L"%s\\*.*", path );
+ HANDLE h = FindFirstFileW( s, &fd );
+ free( s );
+ if ( h != INVALID_HANDLE_VALUE )
+ {
+ FindClose( h ); // we are a directory, yay
+ GayStringW gs;
+ gs.Set( s1 );
+
+ if ( s1[ 0 ] )
+ gs.Append( L"|" );
+
+ gs.Append( path );
+
+ g_config->WriteString( "scandirlist", 0 );
+ g_config->WriteString( "scandirlist_utf8", AutoCharDup( gs.Get(), codePage, 0 ) );
+
+ if ( m_dir_lv )
+ {
+ int x = m_dir_lv->GetCount();
+ m_dir_lv->InsertItem( x, WASABI_API_LNGSTRINGW( IDS_DEFAULT ), 0 );
+ m_dir_lv->SetItemText( x, 1, path );
+ }
+ }
+ }
+ return 0;
+}
+
+static int m_edit_idx;
+static INT_PTR CALLBACK editDirProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t buf[ 1024 ] = { 0 };
+ SHAutoComplete( GetDlgItem( hwndDlg, IDC_EDIT1 ), SHACF_AUTOAPPEND_FORCE_ON | SHACF_AUTOSUGGEST_FORCE_ON | /*SHACF_FILESYS_DIRS*/0x020 | 0x010 | SHACF_USETAB );
+ m_dir_lv->GetText( m_edit_idx, 1, buf, 1024 );
+ SetDlgItemTextW( hwndDlg, IDC_EDIT1, buf );
+ m_dir_lv->GetText( m_edit_idx, 0, buf, 1024 );
+ int guess, meta, subdir;
+ parseMetaStr( buf, &guess, &meta, &subdir );
+ if ( meta )
+ CheckDlgButton( hwndDlg, IDC_CHECK1, meta > 0 ? BST_CHECKED : BST_INDETERMINATE );
+
+ if ( guess < 0 )
+ CheckDlgButton( hwndDlg, IDC_RADIO6, BST_CHECKED );
+ else if ( !guess )
+ CheckDlgButton( hwndDlg, IDC_RADIO1, BST_CHECKED );
+ else if ( guess == 2 )
+ CheckDlgButton( hwndDlg, IDC_RADIO8, BST_CHECKED );
+ else if ( guess > 0 )
+ CheckDlgButton( hwndDlg, IDC_RADIO2, BST_CHECKED );
+
+ if ( subdir ) CheckDlgButton( hwndDlg, IDC_CHECK2, BST_CHECKED );
+ }
+ return 0;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ {
+ //save to m_edit_idx :)
+ wchar_t buf[ 2048 ] = { 0 };
+ GetDlgItemTextW( hwndDlg, IDC_EDIT1, buf, 2048 );
+ m_dir_lv->SetItemText( m_edit_idx, 1, buf );
+
+ int meta = IsDlgButtonChecked( hwndDlg, IDC_CHECK1 );
+ if ( meta == BST_CHECKED )
+ meta = 1;
+ else if ( meta == BST_INDETERMINATE )
+ meta = -1;
+ else
+ meta = 0;
+
+ int guess = -1;
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO1 ) )
+ guess = 0;
+ else if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO2 ) )
+ guess = 1;
+ else if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO8 ) )
+ guess = 2;
+
+ int subdir = 1;
+ if ( !IsDlgButtonChecked( hwndDlg, IDC_CHECK2 ) )
+ subdir = 0;
+
+ makeMetaStr( guess, meta, subdir, buf, 64 );
+ m_dir_lv->SetItemText( m_edit_idx, 0, buf );
+ EndDialog( hwndDlg, 1 );
+ }
+ return 0;
+ case IDCANCEL:
+ EndDialog( hwndDlg, 0 );
+ return 0;
+ case IDC_BUTTON1:
+ wchar_t path[ MAX_PATH ] = { 0 };
+ GetDlgItemTextW( hwndDlg, IDC_EDIT1, path, MAX_PATH );
+ ScanFolderBrowser browse( FALSE );
+ browse.SetSelection( path );
+ if ( browse.Browse( hwndDlg ) )
+ {
+ wchar_t path[ MAX_PATH ] = { 0 };
+ SHGetPathFromIDListW( browse.GetPIDL(), path );
+ SetDlgItemTextW( hwndDlg, IDC_EDIT1, path );
+ }
+ return 0;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+void hideShowMetadataRadioboxes( HWND hwndDlg )
+{
+ int enabled = IsDlgButtonChecked( hwndDlg, IDC_CHECK1 );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC1 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC2 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_COMBO1 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_RADIO1 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_RADIO2 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_RADIO3 ), enabled );
+}
+
+static INT_PTR CALLBACK confMetaProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ if ( g_config->ReadInt( L"usemetadata", 1 ) ) CheckDlgButton( hwndDlg, IDC_CHECK1, BST_CHECKED );
+ {
+ int gm = g_config->ReadInt( L"guessmode", 0 );
+ CheckDlgButton( hwndDlg, gm == 2 ? IDC_RADIO3 : ( gm == 1 ? IDC_RADIO2 : IDC_RADIO1 ), BST_CHECKED );
+ }
+
+ SendDlgItemMessageW( hwndDlg, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW( IDS_ANY ) );
+ SendDlgItemMessageW( hwndDlg, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW( IDS_ALL ) );
+ SendDlgItemMessage( hwndDlg, IDC_COMBO1, CB_SETCURSEL, g_guessifany ? 0 : 1, 0 );
+ hideShowMetadataRadioboxes( hwndDlg );
+ }
+ break;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ g_config->WriteInt( L"usemetadata", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK1 ) );
+ {
+ int a = SendDlgItemMessage( hwndDlg, IDC_COMBO1, CB_GETCURSEL, 0, 0 );
+ g_guessifany = ( a == 0 );
+ g_config->WriteInt( L"guessifany", g_guessifany );
+ }
+
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO1 ) )
+ g_config->WriteInt( L"guessmode", 0 );
+
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO2 ) )
+ g_config->WriteInt( L"guessmode", 1 );
+
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO3 ) )
+ g_config->WriteInt( L"guessmode", 2 );
+
+ case IDCANCEL:
+ EndDialog( hwndDlg, 0 );
+ break;
+ case IDC_CHECK1:
+ hideShowMetadataRadioboxes( hwndDlg );
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK Prefs3Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ static int m_last_isscanning, m_set_rescan_int;
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ m_last_isscanning = -1;
+ m_set_rescan_int = 0;
+
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ SetTimer( hwndDlg, 100, 1000, NULL );
+ g_bgrescan_status_hwnd = GetDlgItem( hwndDlg, IDC_STATUS );
+
+ CheckDlgButton( hwndDlg, IDC_CHECK4, g_bgrescan_do ? BST_CHECKED : 0 );
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( IDC_CHECK4, 0 ), 0 );
+ CheckDlgButton( hwndDlg, IDC_CHECK3, g_config->ReadInt( L"bgrescan_startup", 0 ) ? BST_CHECKED : 0 );
+ CheckDlgButton( hwndDlg, IDC_CHECK2, g_config->ReadInt( L"bgrescan_compact", 1 ) ? BST_CHECKED : 0 );
+ CheckDlgButton( hwndDlg, IDC_CHECK5, g_config->ReadInt( L"autoaddplays", 0 ) ? BST_CHECKED : 0 );
+
+ SendDlgItemMessage( hwndDlg, IDC_SPIN1, UDM_SETRANGE, 0, MAKELONG( 9999, 1 ) );
+ SetDlgItemInt( hwndDlg, IDC_EDIT3, g_bgrescan_int, FALSE );
+ m_set_rescan_int = 1;
+
+ delete m_dir_lv;
+ m_dir_lv = new W_ListView;
+ m_dir_lv->setwnd( GetDlgItem( hwndDlg, IDC_LIST1 ) );
+ m_dir_lv->AddCol( WASABI_API_LNGSTRINGW( IDS_OPTIONS ), 120 );
+ m_dir_lv->AddCol( WASABI_API_LNGSTRINGW( IDS_DIRECTORY ), 500 );
+ char *_s1 = g_config->ReadString( "scandirlist", "" );
+ UINT codePage = CP_ACP;
+ if ( !_s1 || !*_s1 )
+ {
+ _s1 = g_config->ReadString( "scandirlist_utf8", "" );
+ codePage = CP_UTF8;
+ }
+ wchar_t *s1 = AutoWideDup( _s1, codePage );
+ int bufLen = 0;
+ wchar_t *s = (wchar_t *)calloc( ( bufLen = ( wcslen( s1 ) + 2 ) ), sizeof( wchar_t ) );
+ if ( s )
+ {
+ wcsncpy( s, s1, bufLen );
+ s[ wcslen( s ) + 1 ] = 0;
+
+ wchar_t *p = s;
+ while ( p && *p == L'|' ) p++;
+
+ while ( ( p = wcsstr( p, L"|" ) ) )
+ {
+ *p++ = 0;
+ while ( p && *p == L'|' ) p++;
+ }
+ int x = 0;
+ p = s;
+ while ( p && *p )
+ {
+ while ( p && *p == L'|' ) p++;
+
+ int guess_mode = -1;
+ int use_metadata = -1;
+ int subdir = 1;
+ if ( *p == L'<' && wcsstr( p, L">" ) )
+ {
+ p++;
+ while ( p && *p != L'>' )
+ {
+ // <MmSs>can prefix directory
+ // M=metadata use override
+ // m=no metadata
+ // S=smart guessing
+ // s=stupid guessing
+ if ( *p == L'M' )
+ use_metadata = 1;
+ else if ( *p == L'm' )
+ use_metadata = 0;
+ else if ( *p == L'S' )
+ guess_mode = 0;
+ else if ( *p == L's' )
+ guess_mode = 1;
+ else if ( *p == L'r' )
+ subdir = 0;
+ else if ( *p == L'g' )
+ guess_mode = 2;
+
+ p++;
+ }
+ p++;
+ }
+ wchar_t buf[ 64 ] = { 0 };
+ makeMetaStr( guess_mode, use_metadata, subdir, buf, 64 );
+
+ m_dir_lv->InsertItem( x, buf, 0 );
+ m_dir_lv->SetItemText( x, 1, p );
+ x++;
+
+ p += wcslen( p ) + 1;
+ }
+ free( s );
+ }
+
+ if ( NULL != WASABI_API_APP )
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel( m_dir_lv->getwnd(), TRUE );
+ }
+ break;
+ // process this to update the ui state when a scan is running and we didn't manually run it
+ case WM_USER + 101:
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ SetTimer( hwndDlg, 100, 1000, NULL );
+ break;
+ case WM_TIMER:
+ if ( wParam == 100 )
+ {
+ extern int g_bgscan_scanning;
+ if ( !!g_bgscan_scanning != !!m_last_isscanning )
+ {
+ m_last_isscanning = !!g_bgscan_scanning;
+ SetDlgItemTextW( hwndDlg, IDC_RESCAN, WASABI_API_LNGSTRINGW( ( m_last_isscanning ? IDS_STOP_SCAN : IDS_RESCAN_NOW ) ) );
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_CHECK5:
+ g_config->WriteInt( L"autoaddplays", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK5 ) );
+ break;
+ case IDC_CHECK2:
+ g_config->WriteInt( L"bgrescan_compact", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK2 ) );
+ break;
+ case IDC_CHECK3:
+ g_config->WriteInt( L"bgrescan_startup", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK3 ) );
+ break;
+ case IDC_CHECK4:
+ g_config->WriteInt( L"bgrescan_do", g_bgrescan_do = !!IsDlgButtonChecked( hwndDlg, IDC_CHECK4 ) );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT3 ), g_bgrescan_do );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_SPIN1 ), g_bgrescan_do );
+ break;
+ case IDC_EDIT3:
+ {
+ if ( HIWORD( wParam ) == EN_CHANGE && m_set_rescan_int )
+ {
+ BOOL t;
+ int x = GetDlgItemInt( hwndDlg, IDC_EDIT3, &t, FALSE );
+ if ( t )
+ {
+ if ( x < 1 ) x = 1;
+ g_config->WriteInt( L"bgrescan_int", g_bgrescan_int = x );
+ extern time_t g_bgscan_last_rescan;
+ g_bgscan_last_rescan = time( NULL );
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON1:
+ {
+ ScanFolderBrowser browse( FALSE );
+ if ( browse.Browse( hwndDlg ) )
+ {
+ GayStringW n;
+ wchar_t path[ MAX_PATH ] = { 0 };
+
+ SHGetPathFromIDListW( browse.GetPIDL(), path );
+
+ char *_s1 = g_config->ReadString( "scandirlist", "" );
+ UINT codePage = CP_ACP;
+ if ( !_s1 || !*_s1 )
+ {
+ _s1 = g_config->ReadString( "scandirlist_utf8", "" );
+ codePage = CP_UTF8;
+ }
+
+ wchar_t *scanlist = AutoWide( _s1, codePage );
+ n.Set( scanlist );
+
+ if ( scanlist[ 0 ] )
+ n.Append( L"|" );
+
+ n.Append( path );
+ g_config->WriteString( "scandirlist", 0 );
+ g_config->WriteString( "scandirlist_utf8", AutoChar( n.Get(), CP_UTF8 ) );
+ int x = m_dir_lv->GetCount();
+ m_dir_lv->InsertItem( x, WASABI_API_LNGSTRINGW( IDS_DEFAULT ), 0 );
+ m_dir_lv->SetItemText( x, 1, path );
+ }
+ }
+ break;
+ case IDC_BUTTON4:
+ {
+ int x, l = m_dir_lv->GetCount(), s = 0;
+ for ( x = 0; x < l; x++ )
+ {
+ if ( m_dir_lv->GetSelected( x ) )
+ {
+ m_edit_idx = x;
+ if ( WASABI_API_DIALOGBOXW( IDD_EDITDIR, hwndDlg, editDirProc ) )
+ s = 1;
+ else
+ break;
+ }
+ }
+ if ( s )
+ {
+ saveList();
+ }
+ }
+ break;
+ case IDC_BUTTON3:
+ {
+ int x, l = m_dir_lv->GetCount();
+ int s = 0;
+ for ( x = 0; x < l; x++ )
+ {
+ if ( m_dir_lv->GetSelected( x ) )
+ {
+ s = 1;
+ m_dir_lv->DeleteItem( x-- );
+ l--;
+ }
+ }
+ if ( s )
+ {
+ saveList();
+ }
+ }
+ break;
+ case IDC_RESCAN:
+ extern int g_bgscan_scanning;
+ extern time_t g_bgscan_last_rescan;
+ if ( g_bgscan_scanning )
+ {
+ Scan_Cancel();
+ SetWindowTextW( g_bgrescan_status_hwnd, WASABI_API_LNGSTRINGW( IDS_RESCAN_ABORTED ) );
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ }
+ else
+ {
+ extern int openDb( void );
+ openDb();
+ if ( plugin.hwndLibraryParent ) SendMessage( plugin.hwndLibraryParent, WM_USER + 575, 0xffff00dd, 0 );
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ }
+ break;
+ case IDC_CONFMETA:
+ WASABI_API_DIALOGBOXW( IDD_PREFS_METADATA, hwndDlg, confMetaProc );
+ break;
+ };
+ break;
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ if ( l->idFrom == IDC_LIST1 && l->code == NM_DBLCLK )
+ {
+ SendMessage( hwndDlg, WM_COMMAND, IDC_BUTTON4, 0 );
+ }
+ }
+ break;
+ case WM_DESTROY:
+ m_set_rescan_int = 0;
+ g_bgrescan_status_hwnd = 0;
+ if ( NULL != WASABI_API_APP )
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel( m_dir_lv->getwnd(), FALSE );
+ delete m_dir_lv;
+ m_dir_lv = 0;
+ break;
+ }
+ return 0;
+}
+
+static void _dosetsel( HWND hwndDlg )
+{
+ HWND tabwnd = GetDlgItem( hwndDlg, IDC_TAB1 );
+ int sel = TabCtrl_GetCurSel( tabwnd );
+
+ if ( sel >= 0 && ( sel != g_config->ReadInt( L"lastprefp", 0 ) || !subWnd ) )
+ {
+ g_config->WriteInt( L"lastprefp", sel );
+ if ( subWnd )
+ DestroyWindow( subWnd );
+
+ subWnd = 0;
+
+ UINT t = 0;
+ DLGPROC p = NULL;
+ switch ( sel )
+ {
+ case 0: t = IDD_PREFS1; p = Prefs1Proc; break;
+ case 1: t = IDD_PREFS3; p = Prefs3Proc; break;
+ }
+
+ if ( t ) subWnd = WASABI_API_CREATEDIALOGW( t, hwndDlg, p );
+
+ if ( subWnd )
+ {
+ RECT r;
+ GetClientRect( tabwnd, &r );
+ TabCtrl_AdjustRect( tabwnd, FALSE, &r );
+ SetWindowPos( subWnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE );
+ ShowWindow( subWnd, SW_SHOWNA );
+ }
+
+ if ( !SendMessage( plugin.hwndWinampParent, WM_WA_IPC, IPC_ISWINTHEMEPRESENT, IPC_USE_UXTHEME_FUNC ) )
+ {
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)tabwnd, IPC_USE_UXTHEME_FUNC );
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)subWnd, IPC_USE_UXTHEME_FUNC );
+ }
+ }
+}
+
+// frame proc
+INT_PTR CALLBACK PrefsProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ TCITEMW item;
+ HWND tabwnd = GetDlgItem( hwndDlg, IDC_TAB1 );
+ item.mask = TCIF_TEXT;
+ item.pszText = WASABI_API_LNGSTRINGW( IDS_OPTIONS );
+ SendMessageW( tabwnd, TCM_INSERTITEMW, 0, (LPARAM)&item );
+ item.pszText = WASABI_API_LNGSTRINGW( IDS_WATCH_FOLDERS );
+ SendMessageW( tabwnd, TCM_INSERTITEMW, 1, (LPARAM)&item );
+ TabCtrl_SetCurSel( tabwnd, g_config->ReadInt( L"lastprefp", 0 ) );
+ _dosetsel( hwndDlg );
+ prefsWnd = hwndDlg;
+ }
+ return 0;
+ case WM_NOTIFY:
+ {
+ LPNMHDR p = (LPNMHDR)lParam;
+ if ( p->idFrom == IDC_TAB1 && p->code == TCN_SELCHANGE ) _dosetsel( hwndDlg );
+ }
+ return 0;
+ case WM_DESTROY:
+ subWnd = NULL;
+ prefsWnd = NULL;
+ return 0;
+ }
+ return 0;
+}
+
+void refreshPrefs( int screen )
+{
+ if ( subWnd && g_config->ReadInt( L"lastprefp", -1 ) == screen )
+ {
+ if ( screen == 4 ) SendMessage( subWnd, WM_INITDIALOG, 0, 0 );
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/queries.cpp b/Src/Plugins/Library/ml_local/queries.cpp
new file mode 100644
index 00000000..6cfe338a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/queries.cpp
@@ -0,0 +1,1639 @@
+#include "main.h"
+#include "ml_local.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "../nde/nde.h"
+#include "editquery.h"
+#include "../nu/ComboBox.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/ns_wc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+const wchar_t *getFilterName(unsigned int filterId, wchar_t *buffer, size_t bufferSize);
+
+static int m_edit_item;
+
+void queryStrEscape(const wchar_t *p, GayStringW &str)
+{
+ if (!p || !*p) return;
+ size_t l = wcslen(p);
+ wchar_t *escaped = (wchar_t *)calloc((l*3+1), sizeof(wchar_t));
+ wchar_t *d = escaped;
+ while (p && *p) {
+ if (*p == L'%') { *d++ = L'%'; *d++ = L'%'; }
+ else if (*p == L'\"') { *d++ = L'%'; *d++ = L'2'; *d++ = L'2'; }
+ else if (*p == L'\'') { *d++ = L'%'; *d++ = L'2'; *d++ = L'7'; }
+ else if (*p == L'[') { *d++ = L'%'; *d++ = L'5'; *d++ = L'B'; }
+ else if (*p == L']') { *d++ = L'%'; *d++ = L'5'; *d++ = L'D'; }
+ else if (*p == L'(') { *d++ = L'%'; *d++ = L'2'; *d++ = L'8'; }
+ else if (*p == L')') { *d++ = L'%'; *d++ = L'2'; *d++ = L'9'; }
+ else if (*p == L'#') { *d++ = L'%'; *d++ = L'2'; *d++ = L'3'; }
+ else *d++ = *p;
+ p++;
+ }
+ *d = 0;
+ str.Set(escaped);
+ free(escaped);
+}
+
+static INT_PTR CALLBACK scrollChildHostProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK childAdvanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int m_item_mode=0,main_oninitdialog;
+int m_item_image = MLTREEIMAGE_DEFAULT;
+wchar_t m_item_name[256]=L"";
+wchar_t m_item_query[1024]=L"";
+
+typedef struct {
+ int title;
+ wchar_t *query;
+ char sort_by;
+ char sort_dir;
+ char *columns; //xff terminated list :). NULL means default columns
+ int imageIndex;
+ int mode;
+} smartViewPreset;
+
+#define ARTIST 0x01
+#define ALBUMARTIST 0x02
+#define GENRE 0x03
+#define PUBLISHER 0x04
+#define COMPOSER 0x05
+#define ALBUM 0x06
+#define YEAR 0x07
+#define ARTISTINDEX 0x08
+#define ALBUMARTISTINDEX 0x09
+#define PODCASTCHANNEL 0x0A
+#define ALBUMART 0x0B
+
+#define MAKEVIEW_3FILTER(a, b, c) (a | (b << 8) | (c << 16))
+#define MAKEVIEW_2FILTER(a, b) (a | (b << 8))
+
+static smartViewPreset presets[] = {
+ {IDS_AUDIO, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_VIDEO, L"type = 1", 10, 0, "\x7\1\5\x1E\6\3\x20\x8\x9\xA\xff", TREE_IMAGE_LOCAL_VIDEO,MAKEVIEW_2FILTER(GENRE,YEAR)},
+ {IDS_MOST_PLAYED, L"playcount > 0", 9, 0, "\x9\0\1\2\3\xA\xff", TREE_IMAGE_LOCAL_MOSTPLAYED,0},
+ {IDS_RECENTLY_ADDED, L"dateadded > [3 days ago]", 33, 0, "\x21\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYADDED,0},
+ {IDS_RECENTLY_MODIFIED, L"lastupd > [3 days ago]", 11, 0, "\xB\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYMODIFIED,0},
+ {IDS_RECENTLY_PLAYED, L"lastplay > [2 weeks ago]", 10, 0, "\xA\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYPLAYED,0},
+ {IDS_NEVER_PLAYED, L"playcount = 0 | playcount isempty", 0, 0, "\0\1\2\3\xff", TREE_IMAGE_LOCAL_NEVERPLAYED,0},
+ {IDS_TOP_RATED, L"rating >= 3", 8, 0, "\x8\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_TOPRATED,0},
+ {IDS_PODCASTS,L"ispodcast = 1", 0, 0, 0, TREE_IMAGE_LOCAL_PODCASTS, MAKEVIEW_3FILTER(GENRE,PODCASTCHANNEL,YEAR)},
+ {IDS_AUDIO_BY_GENRE, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_3FILTER(GENRE,ARTIST,ALBUM)},
+ {IDS_AUDIO_BY_INDEX, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_3FILTER(ARTISTINDEX,ARTIST,ALBUM)},
+ {IDS_SIMPLE_ALBUM, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 0x0100000B},
+ {IDS_60s_MUSIC, L"type = 0 and year >= 1960 and year < 1970", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_70s_MUSIC, L"type = 0 and year >= 1970 and year < 1980", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_80s_MUSIC, L"type = 0 and year >= 1980 and year < 1990", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_90s_MUSIC, L"type = 0 and year >= 1990 and year < 2000", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_00s_MUSIC, L"type = 0 and year >= 2000 and year < 2010", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_ROCK_MUSIC, L"type = 0 and genre has \"Rock\"", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_CLASSICAL_MUSIC, L"type = 0 and genre has \"Classical\"", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_2FILTER(COMPOSER,ALBUM)},
+ {IDS_RECORD_LABELS, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_3FILTER(PUBLISHER,ALBUMARTIST,ALBUM)},
+ {IDS_CUSTOM, L"", 0, 0, NULL, MLTREEIMAGE_DEFAULT, 0},
+};
+
+#define NUM_PRESETS (sizeof(presets) / sizeof(smartViewPreset))
+
+void showdlgelements(HWND hwndDlg, int nCmdShow) {
+ const int dlgelems[] = {
+ IDC_STATIC_FILTER,
+ IDC_RADIO_SIMPLE,
+ IDC_IMAGE_SIMPLE,
+ IDC_STATIC_SIMPLE,
+ IDC_RADIO_SIMPLEALBUM,
+ IDC_IMAGE_SIMPLEALBUM,
+ IDC_STATIC_SIMPLEALBUM,
+ IDC_RADIO_TWOFILTERS,
+ IDC_IMAGE_TWOFILTERS,
+ IDC_STATIC_TWOFILTERS,
+ IDC_RADIO_THREEFILTERS,
+ IDC_IMAGE_THREEFILTERS,
+ IDC_STATIC_THREEFILTERS,
+ IDC_COMBO_FILTER1,
+ IDC_COMBO_FILTER2,
+ IDC_COMBO_FILTER3,
+ IDC_STATIC_FILTER2,
+ IDC_STATIC_FILTER3,
+ };
+ for(int i=0; i < (sizeof(dlgelems)/sizeof(int)); i++)
+ ShowWindow(GetDlgItem(hwndDlg,dlgelems[i]),nCmdShow);
+}
+
+#define SPL_RemoveAll(hwnd) SendMessage(hwnd,WM_USER+41,0,0)
+#define SPL_GetQueryString(hwnd) SendMessage(hwnd,WM_USER+40, 0 ,0)
+#define SPL_AddFilter(hwnd, filter) (HWND)scrollChildHostProc(hwnd,WM_USER+32, (WPARAM)filter, (LPARAM)1)
+#define SPL_AddBlankFilter(hwnd) (HWND)scrollChildHostProc(hwnd,WM_USER+32, 0, (LPARAM)2)
+#define SPL_RemoveFilter(hwnd, filterhwnd) SendMessage(hwnd,WM_USER+34,(WPARAM)filterhwnd,(LPARAM)-1)
+#define SPL_UpdateScroll(hwnd) SendMessage(hwnd,WM_USER+33,0,0)
+
+static int m_simple_dirty;
+
+void SetStaticItemImage(HWND hwndDlg, UINT ctrl_id, UINT ctrl_img)
+{
+ SendDlgItemMessage(hwndDlg,ctrl_id,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(WASABI_API_ORIG_HINST,MAKEINTRESOURCE(ctrl_img),IMAGE_BITMAP,0,0,LR_SHARED));
+}
+
+int ResizeComboBoxDropDownW(HWND hwndDlg, UINT id, const wchar_t *str, int width){
+ SIZE size = {0};
+ HWND control = GetDlgItem(hwndDlg, id);
+ HDC hdc = GetDC(control);
+ // get and select parent dialog's font so that it'll calculate things correctly
+ HFONT font = (HFONT)SendMessage(hwndDlg,WM_GETFONT,0,0), oldfont = (HFONT)SelectObject(hdc,font);
+ GetTextExtentPoint32W(hdc, str, wcslen(str)+1, &size);
+
+ int ret = width;
+ if(size.cx > width)
+ {
+ SendDlgItemMessage(hwndDlg, id, CB_SETDROPPEDWIDTH, size.cx, 0);
+ ret = size.cx;
+ }
+
+ SelectObject(hdc, oldfont);
+ ReleaseDC(control, hdc);
+ return ret;
+}
+
+struct FiltersContext
+{
+ HWND last1, last2;
+ int mode;
+ HWND m_scrollchild;
+ int *error;
+};
+
+static bool EnumFilters(Scanner *scanner, nde_filter_t filter, void *context_in)
+{
+ FiltersContext *context = (FiltersContext *)context_in;
+
+ HWND x = SPL_AddFilter(context->m_scrollchild,filter);
+ if(x) { // we have a filter which talks about a field
+ context->last2=context->last1;
+ context->last1=x;
+ context->mode++;
+ if(context->mode > 2)
+ context->error[0]=1; // not in our strict form
+ } else { // we have an AND, OR or a NOT
+ int f=NDE_Filter_GetOp(filter);
+ if(f == FILTER_OR) CheckDlgButton(context->last2,IDC_OR,TRUE);
+ else if(f == FILTER_AND) CheckDlgButton(context->last2,IDC_AND,TRUE);
+ else context->error[0]=1; // we can't do FILTER_NOT
+ context->mode--;
+ if(context->mode != 1) context->error[0]=1; // not in our strict form
+ }
+ return !context->error[0];
+}
+
+static INT_PTR CALLBACK addQueryFrameDialogProc2(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static HWND m_scrollchild;
+ static int m_editting=0;
+ static C_Config *conf=0;
+ static wchar_t * m_item_meta;
+ static int mode;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ const int filters[] = {IDC_COMBO_FILTER1, IDC_COMBO_FILTER2,IDC_COMBO_FILTER3};
+ HWND controlWindow;
+ int iItem;
+ wchar_t buffer[512] = {0};
+
+ mode=0;
+ m_item_meta=0;
+ conf=0;
+ m_simple_dirty=0;
+
+ openDb();
+
+ CheckDlgButton(hwndDlg,IDC_RADIO_SIMPLE,TRUE);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_INFO, WASABI_API_LNGSTRINGW(IDS_WILLS_UBER_STRING));
+ EnableWindow(GetDlgItem(hwndDlg,IDC_HIDE_EXTINFO), g_config->ReadInt(L"useminiinfo2", 0));
+ // set up presets combo box
+ {
+ controlWindow = GetDlgItem(hwndDlg, IDC_COMBO_PRESETS);
+ if (NULL != controlWindow)
+ {
+ int width = 0;
+ for(size_t i=0; i < NUM_PRESETS; i++)
+ {
+ if (NULL != WASABI_API_LNGSTRINGW_BUF(presets[i].title, buffer, ARRAYSIZE(buffer)))
+ {
+ iItem = SendMessageW(controlWindow, CB_ADDSTRING, 0, (LPARAM)buffer);
+ if (CB_ERR != iItem)
+ {
+ width = ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO_PRESETS, buffer, width);
+ SendMessageW(controlWindow, CB_SETITEMDATA,(WPARAM)iItem, (LPARAM)(i + 1));
+ }
+ }
+ }
+ }
+ }
+
+ for(size_t i=0; i < ARRAYSIZE(filters); i++)
+ {
+ controlWindow = GetDlgItem(hwndDlg, filters[i]);
+ if (NULL != controlWindow)
+ {
+ int width = 0;
+ for(unsigned int filterId = 0;; filterId++)
+ {
+ if (NULL == getFilterName(filterId, buffer, ARRAYSIZE(buffer)))
+ break;
+
+ iItem = SendMessageW(controlWindow, CB_ADDSTRING, 0, (LPARAM)buffer);
+ if (CB_ERR != iItem)
+ {
+ width = ResizeComboBoxDropDownW(hwndDlg, filters[i], buffer, width);
+ SendMessageW(controlWindow, CB_SETITEMDATA,(WPARAM)iItem, (LPARAM)filterId);
+ }
+ }
+ }
+ }
+
+ if (lParam == 1)
+ {
+ if (m_edit_item == -1)
+ {
+ // nothing to do, m_item_name, m_item_query & m_item_mode have been set already
+ }
+ else
+ {
+ lstrcpynW(m_item_name,m_query_list[m_edit_item]->name,sizeof(m_item_name)/sizeof(wchar_t));
+ lstrcpynW(m_item_query,m_query_list[m_edit_item]->query,sizeof(m_item_query)/sizeof(wchar_t));
+ m_item_mode = m_query_list[m_edit_item]->mode;
+ m_item_image = m_query_list[m_edit_item]->imgIndex;
+
+ // re-select the preset name if we can get a match to a known preset (looks nicer and all that)
+ controlWindow = GetDlgItem(hwndDlg, IDC_COMBO_PRESETS);
+ if (NULL != controlWindow)
+ {
+ for(size_t i=0; i < NUM_PRESETS; i++)
+ {
+ if(m_item_query[0] && !_wcsicmp(presets[i].query, m_item_query)
+ && presets[i].mode == m_item_mode)
+ {
+ SendMessageW(controlWindow, CB_SETCURSEL, i, 0);
+ SendMessage(hwndDlg,WM_COMMAND,(WPARAM)MAKEWPARAM(IDC_COMBO_PRESETS,CBN_SELCHANGE),(LPARAM)GetDlgItem(hwndDlg,IDC_COMBO_PRESETS));
+ break;
+ }
+ }
+ }
+
+ // config
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, m_query_list[m_edit_item]->metafn);
+ conf = new C_Config(configDir);
+ CheckDlgButton(hwndDlg, IDC_HIDE_EXTINFO, conf->ReadInt(L"midivvis", 1) ? 0 : 1);
+ }
+ SetWindowTextW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_EDIT_SMART_VIEW));
+ m_editting=1;
+ }
+ else
+ {
+ m_editting=0;
+ if (m_item_mode != -1) m_item_mode=0;
+ m_item_name[0]=0;
+ m_item_query[0]=0;
+ m_item_image = MLTREEIMAGE_DEFAULT;
+
+ wchar_t filename[1024 + 256] = {0};
+ extern void makemetafn(wchar_t *filename, wchar_t **out);
+ makemetafn(filename, &m_item_meta);
+ conf = new C_Config(filename);
+ }
+
+ if(!mode) m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_SCROLLCHILDHOST,hwndDlg,scrollChildHostProc);
+ else m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_ADD_VIEW_CHILD_ADVANCED,hwndDlg,childAdvanced);
+
+ // this will make sure that we've got the little images shown in all languages (done for 5.51+)
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_SIMPLE,IDB_NEWFILTER_SIMPLE);
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_SIMPLEALBUM,IDB_NEWFILTER_SIMPLEALBUM);
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_TWOFILTERS,IDB_NEWFILTER_TWOFILTERS);
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_THREEFILTERS,IDB_NEWFILTER_THREEFILTERS);
+
+ if(m_editting==0 && g_config->ReadInt(L"newfilter_showhelp",1))
+ {
+ showdlgelements(hwndDlg,SW_HIDE);
+ ShowWindow(hwndDlg,SW_SHOWNA);
+ EnableWindow(GetParent(hwndDlg),0);
+ break; // don't run through
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_INFO),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_SHOWINFO),SW_HIDE);
+ // run through
+ }
+ }
+ case WM_USER+9:
+ { // do stuff when we're shown or preset is changed
+ showdlgelements(hwndDlg,SW_SHOWNA);
+ ShowWindow(m_scrollchild,SW_SHOWNA);
+ // set up filter radio buttons and combo boxes
+ extern int GetFilter(int mode, int n);
+ extern int GetNumFilters(int mode);
+
+ int numFilters=GetNumFilters(m_item_mode);
+ int f[3] = {0};
+ f[0] = GetFilter(m_item_mode,0);
+ f[1] = GetFilter(m_item_mode,1);
+ f[2] = GetFilter(m_item_mode,2);
+
+ if(numFilters==0) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_SIMPLE);
+ else if(numFilters==1) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_SIMPLEALBUM);
+ else if(numFilters==2) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_TWOFILTERS);
+ else if(numFilters==3) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_THREEFILTERS);
+
+ for(int i=0; i<3; i++) {
+ ComboBox comboBox(hwndDlg, (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3));
+ if(f[i]==0) comboBox.SelectItem(i==0?0:5);
+ else comboBox.SelectItem(f[i]-1);
+ }
+ // set up "name" edit control
+ SetDlgItemTextW(hwndDlg,IDC_NAME,m_item_name);
+
+ // do the query editor thing...
+ SendMessage(hwndDlg,WM_USER+11,0,0);
+ }
+ // run through
+ case WM_USER+10:
+ { // enable filter combo boxes based on radio buttons
+ int numFilters=0;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_SIMPLEALBUM)) numFilters=1;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_TWOFILTERS)) numFilters=2;
+ else if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_THREEFILTERS)) numFilters=3;
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_FILTER3),numFilters>2);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_FILTER2),numFilters>1);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_FILTER3),numFilters>2);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_FILTER2),numFilters>1);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_FILTER1),numFilters>1);
+
+ if (WM_INITDIALOG == uMsg)
+ {
+ // show edit info window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"smart_x", -1), g_config->ReadInt(L"smart_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOWNA);
+
+ HWND parentWindow = GetParent(hwndDlg);
+ if (NULL != parentWindow)
+ EnableWindow(parentWindow, 0);
+ }
+ /*else
+ ShowWindow(hwndDlg, SW_SHOW);*/
+ }
+ break;
+ case WM_USER+11:
+ if(!mode) {
+ SPL_RemoveAll(m_scrollchild);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t p=NDE_Table_CreateScanner(g_table);
+ ((Scanner *)p)->disable_date_resolution=1; // TODO: don't use C++ NDE api
+
+ int error=0; // not really an error, just we can't express it properly
+
+ if (m_item_query[0] && !NDE_Scanner_Query(p, m_item_query))
+ error=1; // ok, actually an error.
+
+ if (m_item_query[0] && !error)
+ {
+ FiltersContext context;
+ context.error = &error;
+ context.last1 = 0;
+ context.last2 = 0;
+ context.m_scrollchild = m_scrollchild;
+ context.mode = 0;
+ ((Scanner *)p)->WalkFilters((Scanner::FilterWalker)EnumFilters, &context); // TODO: don't use C++ NDE api
+ }
+
+ NDE_Table_DestroyScanner(g_table, p);
+ LeaveCriticalSection(&g_db_cs);
+ if(error) {
+ SPL_RemoveAll(m_scrollchild);
+ HWND x = SPL_AddBlankFilter(m_scrollchild);
+ ComboBox c(x,IDC_COMBO1);
+ int n = c.GetCount();
+ for(int i=0; i<n; i++) if(c.GetItemData(i) == 0x1ea7c0de) c.SelectItem(i);
+ SetDlgItemTextW(x,IDC_EDIT1,m_item_query);
+ SendMessage(x,WM_COMMAND,(WPARAM)MAKEWPARAM(IDC_COMBO1,CBN_SELCHANGE),(LPARAM)GetDlgItem(x,IDC_COMBO1));
+ }
+
+ m_simple_dirty=0;
+ SPL_UpdateScroll(m_scrollchild);
+ }
+ break;
+ case WM_DESTROY:
+ if(conf) delete conf; conf=0;
+ if(m_item_meta) free(m_item_meta); m_item_meta=0;
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_BUTTON_MODE:
+ if(m_simple_dirty)
+ SPL_GetQueryString(m_scrollchild);
+ DestroyWindow(m_scrollchild);
+ mode = !mode;
+ if(!mode) m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_SCROLLCHILDHOST,hwndDlg,scrollChildHostProc);
+ else m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_ADD_VIEW_CHILD_ADVANCED,hwndDlg,childAdvanced);
+ SendMessage(hwndDlg,WM_USER+11,0,0);
+ SendMessage(hwndDlg,WM_USER+10,0,0);
+ ShowWindow(m_scrollchild,SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_INFO),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_SHOWINFO),SW_HIDE);
+
+ SetDlgItemTextW(hwndDlg,IDC_BUTTON_MODE,WASABI_API_LNGSTRINGW( !mode ? IDS_ADVANCED_MODE : IDS_SIMPLE_MODE));
+
+ showdlgelements(hwndDlg,SW_SHOWNA);
+ break;
+ case IDC_COMBO_PRESETS:
+ switch(HIWORD(wParam)) {
+ case CBN_SELCHANGE:
+ {
+ ComboBox combo(hwndDlg,IDC_COMBO_PRESETS);
+ int n = combo.GetItemData(combo.GetSelection());
+ if(n>0 && n<=NUM_PRESETS) {
+ wchar_t cusStr[16] = {0};
+ n--;
+ // populate with a preset
+ m_item_mode = presets[n].mode;
+ m_item_image = presets[n].imageIndex;
+ if(!_wcsicmp(WASABI_API_LNGSTRINGW_BUF(IDS_CUSTOM,cusStr,16),
+ WASABI_API_LNGSTRINGW(presets[n].title))) m_item_name[0]=0;
+ else WASABI_API_LNGSTRINGW_BUF(presets[n].title,m_item_name,256);
+ lstrcpynW(m_item_query,presets[n].query,sizeof(m_item_query)/sizeof(wchar_t));
+ showdlgelements(hwndDlg,SW_SHOWNA);
+ ShowWindow(m_scrollchild,SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_INFO),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_SHOWINFO),SW_HIDE);
+ SendMessage(hwndDlg,WM_USER+9,0,0);
+ }
+ }
+ break;
+ }
+ break;
+ // filter type radio buttons (simulate clicks on the radio button when the image or static text gets clicked)
+ case IDC_CHECK_SHOWINFO:
+ g_config->WriteInt(L"newfilter_showhelp",0);
+ break;
+ case IDC_STATIC_SIMPLE:
+ case IDC_IMAGE_SIMPLE:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_SIMPLE),BM_CLICK,0,0);
+ break;
+ case IDC_STATIC_SIMPLEALBUM:
+ case IDC_IMAGE_SIMPLEALBUM:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_SIMPLEALBUM),BM_CLICK,0,0);
+ break;
+ case IDC_STATIC_TWOFILTERS:
+ case IDC_IMAGE_TWOFILTERS:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_TWOFILTERS),BM_CLICK,0,0);
+ break;
+ case IDC_STATIC_THREEFILTERS:
+ case IDC_IMAGE_THREEFILTERS:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_THREEFILTERS),BM_CLICK,0,0);
+ break;
+ // enable filter combo boxes based on radio buttons
+ case IDC_RADIO_SIMPLE:
+ case IDC_RADIO_SIMPLEALBUM:
+ case IDC_RADIO_TWOFILTERS:
+ case IDC_RADIO_THREEFILTERS:
+ CheckDlgButton(hwndDlg,IDC_RADIO_SIMPLE,0);
+ CheckDlgButton(hwndDlg,IDC_RADIO_SIMPLEALBUM,0);
+ CheckDlgButton(hwndDlg,IDC_RADIO_TWOFILTERS,0);
+ CheckDlgButton(hwndDlg,IDC_RADIO_THREEFILTERS,0);
+ CheckDlgButton(hwndDlg,LOWORD(wParam),1);
+ SendMessage(hwndDlg,WM_USER+10,0,0);
+ break;
+ // OK and Cancel buttons
+ case IDOK:
+ {
+ //name
+ GetDlgItemTextW(hwndDlg,IDC_NAME,m_item_name,255);
+ m_item_name[255]=0;
+ if(!m_item_name[0]) {
+ wchar_t title[64] = {0};
+ MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_MUST_ENTER_A_NAME),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,64),MB_OK);
+ return 0;
+ }
+ //query
+ if (m_simple_dirty)
+ SPL_GetQueryString(m_scrollchild);
+
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_SIMPLEALBUM))
+ {
+ if(conf)
+ {
+ conf->WriteInt(L"adiv2pos",100000);
+ conf->WriteInt(L"albumartviewmode",2);
+ }
+ m_item_mode = 0x0100000B;
+ }
+ else
+ { // mode
+ int numFilters=3;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_SIMPLE)) numFilters=0;
+ else if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_TWOFILTERS)) numFilters=2;
+
+ int f[3] = {0};
+ for(int i=0; i<3; i++) {
+ ComboBox comboBox(hwndDlg, (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3));
+ f[i] = (comboBox.GetItemData(comboBox.GetSelection()) + 1) & 0xff;
+ }
+ if(f[1] == 6) f[1]=0;
+ if(numFilters == 2) f[2]=0;
+ if(numFilters == 0) f[0]=f[1]=f[2]=0;
+ m_item_mode = f[0] | (f[1] << 8) | (f[2] << 16);
+ }
+
+ if(conf) {
+ int v = IsDlgButtonChecked(hwndDlg,IDC_HIDE_EXTINFO)?0:1;
+ conf->WriteInt(L"midivvis",v);
+
+ ComboBox combo(hwndDlg,IDC_COMBO_PRESETS);
+ int n = combo.GetItemData(combo.GetSelection());
+ if(n>0 && n<=NUM_PRESETS) {
+ n--;
+ conf->WriteInt(L"mv_sort_by", presets[n].sort_by);
+ conf->WriteInt(L"mv_sort_dir", presets[n].sort_dir);
+
+ if(presets[n].columns) {
+ int cnt = 0;
+ while ((unsigned char)presets[n].columns[cnt] != 0xff)
+ {
+ wchar_t buf[32] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"column%d", cnt);
+ conf->WriteInt(buf, (unsigned char)presets[n].columns[cnt]);
+ cnt++;
+ }
+ conf->WriteInt(L"nbcolumns", cnt);
+ }
+ }
+ }
+
+ if (m_edit_item == -1) {
+ // nothing to do, return values are m_item_name, m_item_query and m_item_mode
+ } else {
+ if(!m_editting) addQueryItem(m_item_name,m_item_query,m_item_mode,1,m_item_meta, m_item_image);
+ else replaceQueryItem(m_edit_item,m_item_name,m_item_query,m_item_mode, m_item_image);
+ saveQueryTree();
+ }
+ if(m_editting) PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ case IDCANCEL:
+ {
+ RECT smart_rect = {0};
+ GetWindowRect(hwndDlg, &smart_rect);
+ g_config->WriteInt(L"smart_x", smart_rect.left);
+ g_config->WriteInt(L"smart_y", smart_rect.top);
+
+ EndDialog(hwndDlg,(LOWORD(wParam) == IDOK));
+ EnableWindow(GetParent(hwndDlg),1);
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK childAdvanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ HWND parentWindow, controlWindow;
+ parentWindow = GetAncestor(hwndDlg, GA_PARENT);
+ if (NULL != parentWindow &&
+ FALSE != GetWindowRect(GetDlgItem(parentWindow,IDC_CHILDFRAME),&r))
+ {
+ MapWindowPoints(HWND_DESKTOP, parentWindow, (POINT*)&r, 2);
+ SetWindowPos(hwndDlg,NULL,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+
+ SetDlgItemTextW(hwndDlg,IDC_QUERY,m_item_query);
+
+ controlWindow = GetDlgItem(hwndDlg, IDC_EDIT1);
+ if (NULL != controlWindow)
+ {
+ SetWindowTextA(controlWindow, (char*)WASABI_API_LOADRESFROMFILEW(L"TEXT", MAKEINTRESOURCEW(IDR_QUERIES_TEXT), 0));
+ }
+ }
+ return 1;
+ case WM_USER+40: // get query str
+ GetDlgItemTextW(hwndDlg,IDC_QUERY,m_item_query,1024);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_EDIT:
+ {
+ wchar_t query[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_QUERY,query, 1024);
+ wchar_t querybuf[4096] = {0};
+ const wchar_t *newquery = editQuery(hwndDlg, query, querybuf, 4096);
+ if (newquery != NULL) {
+ SetDlgItemTextW(hwndDlg, IDC_QUERY, newquery);
+ m_simple_dirty=1;
+ }
+ }
+ break;
+ case IDC_QUERY:
+ m_simple_dirty=1;
+ break;
+ }
+ break;
+ }
+
+ const int controls[] =
+ {
+ IDC_EDIT1,
+ };
+ if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls)))
+ return TRUE;
+
+ return 0;
+}
+
+BOOL IsDirectMouseWheelMessage(const UINT uMsg)
+{
+ static UINT WINAMP_WM_DIRECT_MOUSE_WHEEL = WM_NULL;
+
+ if (WM_NULL == WINAMP_WM_DIRECT_MOUSE_WHEEL)
+ {
+ WINAMP_WM_DIRECT_MOUSE_WHEEL = RegisterWindowMessageW(L"WINAMP_WM_DIRECT_MOUSE_WHEEL");
+ if (WM_NULL == WINAMP_WM_DIRECT_MOUSE_WHEEL)
+ return FALSE;
+ }
+
+ return (WINAMP_WM_DIRECT_MOUSE_WHEEL == uMsg);
+}
+
+static INT_PTR CALLBACK filterProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ // populate comboboxes
+ {
+ HWND hwndCB=GetDlgItem(hwndDlg,IDC_COMBO1);
+ int strs[]=
+ {
+ IDS_FILENAME,
+ IDS_TITLE,
+ IDS_ARTIST,
+ IDS_ALBUM,
+ IDS_YEAR,
+ IDS_GENRE,
+ IDS_COMMENT,
+ IDS_TRACK_NUMBER,
+ IDS_LENGTH,
+ IDS_IS_VIDEO,
+ IDS_LAST_UPDATED,
+ IDS_PLAYED_LAST,
+ IDS_RATING,
+ NULL, // fear the 13
+ NULL, // gracenote
+ IDS_PLAY_COUNT,
+ IDS_FILE_TIME,
+ IDS_FILE_SIZE_KB,
+ IDS_BITRATE_KBPS,
+ IDS_DISC_NUMBER,
+ IDS_ALBUM_ARTIST,
+ IDS_ALBUM_GAIN,
+ IDS_TRACK_GAIN,
+ IDS_PUBLISHER,
+ IDS_COMPOSER,
+ IDS_BPM, // no bpm
+ IDS_DISCS,
+ IDS_TRACKS,
+ IDS_IS_PODCAST,
+ IDS_PODCAST_CHANNEL,
+ IDS_PODCAST_PUBLISH_DATE,
+ NULL,
+ NULL,
+ IDS_LOSSLESS,
+ IDS_CATEGORY,
+ IDS_CODEC,
+ IDS_DIRECTOR,
+ IDS_PRODUCER,
+ IDS_WIDTH,
+ IDS_HEIGHT,
+ NULL,
+ IDS_DATE_ADDED,
+ };
+ int x;
+ int cnt = 0, width = 0;
+ wchar_t * str;
+ for (x = 0; x < sizeof(strs)/sizeof(strs[0]); x ++)
+ {
+ if (strs[x])
+ {
+ int a=SendMessageW(hwndCB, CB_ADDSTRING,0,(LPARAM)(str = WASABI_API_LNGSTRINGW(strs[x])));
+ width = ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO1, str, width);
+ SendMessage(hwndCB,CB_SETITEMDATA,(WPARAM)a,(LPARAM)x);
+ cnt++;
+ }
+ }
+
+ int a=SendMessageW(hwndCB, CB_ADDSTRING,0,(LPARAM)(str = WASABI_API_LNGSTRINGW(IDS_CUSTOM)));
+ ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO1, str, width);
+ SendMessage(hwndCB,CB_SETITEMDATA,(WPARAM)a,(LPARAM)0x1ea7c0de);
+ cnt++;
+ if (lParam)
+ {
+ nde_filter_t filter=(nde_filter_t)lParam;
+ int a=NDE_Filter_GetID(filter);
+ if (a != -1)
+ {
+ for (x = 0; x < cnt; x ++)
+ {
+ int d = SendMessage(hwndCB,CB_GETITEMDATA,(WPARAM)x,0);
+ if (d == a)
+ break;
+ }
+ if (x < cnt)
+ {
+ SendMessage(hwndCB,CB_SETCURSEL,x,0);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO1,CBN_SELCHANGE),(LPARAM)hwndCB);
+ int filtop=NDE_Filter_GetOp(filter);
+ HWND hwndCB2=GetDlgItem(hwndDlg,IDC_COMBO2);
+ cnt=SendMessage(hwndCB2,CB_GETCOUNT,0,0);
+ for (x = 0; x < cnt; x ++)
+ {
+ if (SendMessage(hwndCB2,CB_GETITEMDATA,(WPARAM)x,0) == filtop)
+ break;
+ }
+ if (x < cnt)
+ {
+ SendMessage(hwndCB2,CB_SETCURSEL,x,0);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO2,CBN_SELCHANGE),(LPARAM)hwndCB2);
+ }
+ }
+ }
+ nde_field_t f=NDE_Filter_GetData(filter);
+ if (f)
+ {
+ int ft=NDE_Field_GetType(f);
+ //hack cause we never actualy get FIELD_LENGTH here.
+ if (ft==FIELD_INTEGER && a==MAINTABLE_ID_LENGTH) ft=FIELD_LENGTH;
+
+ switch (ft)
+ {
+ case FIELD_FILENAME:
+ case FIELD_STRING:
+ SetDlgItemTextW(hwndDlg,IDC_EDIT1,NDE_StringField_GetString(f));
+ break;
+ case FIELD_LENGTH:
+ {
+ int v=NDE_IntegerField_GetValue(f);
+ wchar_t buf[128] = {0};
+ if (v < 60)
+ wsprintf(buf,L"%d",v);
+ else if (v < 60*60)
+ wsprintf(buf,L"%d:%02d",v/60,v%60);
+ else
+ wsprintf(buf,L"%d:%02d:%02d",v/60/60,(v/60)%60,v%60);
+
+ SetDlgItemText(hwndDlg,IDC_EDIT1,buf);
+ }
+ break;
+ case FIELD_INTEGER:
+ SetDlgItemInt(hwndDlg,IDC_EDIT1,NDE_IntegerField_GetValue(f),TRUE);
+ break;
+ }
+ }
+ }
+ }
+ return 1;
+
+ case WM_USER+29:
+ if(IsDlgButtonChecked(hwndDlg,IDC_AND)) return 1;
+ if(IsDlgButtonChecked(hwndDlg,IDC_OR)) return 0;
+ CheckDlgButton(hwndDlg,wParam?IDC_AND:IDC_OR,TRUE);
+ return wParam;
+ case WM_USER+40:
+ if (wParam && lParam>0)
+ {
+ wchar_t *buf=(wchar_t *)wParam;
+ size_t buf_len=(size_t)lParam;
+ // produce expression here
+ HWND hwndCB=GetDlgItem(hwndDlg,IDC_COMBO1);
+ HWND hwndCB2=GetDlgItem(hwndDlg,IDC_COMBO2);
+ int x=SendMessage(hwndCB,CB_GETCURSEL,0,0);
+ if (x != CB_ERR)
+ {
+ int Id=SendMessage(hwndCB,CB_GETITEMDATA,x,0);
+ if(Id == 0x1ea7c0de) { // custom!
+ GetDlgItemTextW(hwndDlg,IDC_EDIT1,buf,buf_len);
+ return 0;
+ }
+ x = SendMessage(hwndCB2,CB_GETCURSEL,0,0);
+ if (x != CB_ERR)
+ {
+ int Op=SendMessage(hwndCB2,CB_GETITEMDATA,x,0);
+
+ if (Id != -1 && Op != -1)
+ {
+ wchar_t res[256] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_EDIT1,res,255);
+ res[255]=0;
+ nde_field_t p = NDE_Table_GetColumnByID(g_table, (unsigned char)Id);
+ if (p)
+ {
+ const wchar_t *fn=NDE_ColumnField_GetFieldName(p);
+
+
+ wchar_t *opstr=NULL;
+ switch (Op)
+ {
+ case FILTER_EQUALS: opstr=L"="; break;
+ case FILTER_NOTEQUALS: opstr=L"!="; break;
+ case FILTER_CONTAINS: opstr=L"HAS"; break;
+ case FILTER_NOTCONTAINS: opstr=L"NOTHAS"; break;
+ case FILTER_ABOVE: opstr=L">"; break;
+ case FILTER_BELOW: opstr=L"<"; break;
+ case FILTER_ABOVEOREQUAL: opstr=L">="; break;
+ case FILTER_BELOWOREQUAL: opstr=L"<="; break;
+ case FILTER_BEGINS: opstr=L"BEGINS"; break;
+ case FILTER_ENDS: opstr=L"ENDS"; break;
+ case FILTER_LIKE: opstr=L"LIKE"; break;
+ case FILTER_ISEMPTY: opstr=L"ISEMPTY"; break;
+ case FILTER_ISNOTEMPTY: opstr=L"ISNOTEMPTY"; break;
+ }
+ if (fn && fn[0] && opstr && opstr[0])
+ {
+ if (Op == FILTER_ISEMPTY || Op == FILTER_ISNOTEMPTY)
+ wsprintfW(buf, L"%s %s",fn,opstr);
+ else if (NDE_ColumnField_GetDataType(p) == FIELD_DATETIME)
+ wsprintfW(buf, L"%s %s [%s]",fn,opstr,res);
+ else
+ {
+ GayStringW escaped;
+ queryStrEscape(res, escaped);
+ wsprintfW(buf, L"%s %s \"%s\"",fn,opstr,escaped.Get());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON1:
+ SendMessage(GetParent(hwndDlg),WM_USER+34,(WPARAM)hwndDlg,-1);
+ m_simple_dirty=1;
+ return 0;
+ case IDC_OR:
+ case IDC_AND:
+ m_simple_dirty=1;
+ break;
+ case IDC_BUTTON_QUERYBUILD:
+ {
+ wchar_t query[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_EDIT,query, 1024);
+ wchar_t querybuf[4096] = {0};
+ const wchar_t *newquery = editQuery(hwndDlg, query, querybuf, 4096);
+ if (newquery != NULL) {
+ SetDlgItemTextW(hwndDlg, IDC_EDIT, newquery);
+ m_simple_dirty=1;
+ }
+ }
+ break;
+ case IDC_BUTTON2:
+ {
+ wchar_t query[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_EDIT1,query,1024);
+
+ const wchar_t *newquery = editTime(hwndDlg, query);
+ if (newquery != NULL) SetDlgItemTextW(hwndDlg, IDC_EDIT1, newquery);
+ }
+ // todo: date picker
+ return 0;
+ case IDC_EDIT1:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ m_simple_dirty=1;
+ }
+ return 0;
+ case IDC_COMBO2:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ m_simple_dirty=1;
+ HWND hwndCB2=(HWND)lParam;
+ int x=SendMessage(hwndCB2,CB_GETCURSEL,0,0);
+ if (x != CB_ERR)
+ {
+ int a=SendMessage(hwndCB2,CB_GETITEMDATA,(WPARAM)x,0);
+ if (a == FILTER_ISEMPTY || a == FILTER_ISNOTEMPTY)
+ {
+ EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT1),0);
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT1),1);
+ }
+ }
+ }
+ return 0;
+ case IDC_COMBO1:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ m_simple_dirty=1;
+ HWND hwndCB=(HWND)lParam;
+ HWND hwndCB2=GetDlgItem(hwndDlg,IDC_COMBO2);
+
+ int x=SendMessage(hwndCB,CB_GETCURSEL,0,0);
+#define GAP 2
+ if (x != CB_ERR)
+ {
+ int lastsel=SendMessage(hwndCB2,CB_GETCURSEL,0,0);
+ SendMessage(hwndCB2,CB_RESETCONTENT,0,0);
+ // populate with proper comparisons for type. give a default, too
+ int myId = SendMessage(hwndCB,CB_GETITEMDATA,(WPARAM)x,0);
+ if(myId == 0x1ea7c0de) { // custom!
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON_QUERYBUILD),SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_COMBO2),SW_HIDE);
+
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON2),&r);
+ ScreenToClient(hwndDlg,((LPPOINT)&r));
+
+ RECT r1;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_COMBO1),&r1);
+ ScreenToClient(hwndDlg,((LPPOINT)&r1));
+ ScreenToClient(hwndDlg,((LPPOINT)&r1)+1);
+
+ RECT r2;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_EDIT1),&r2);
+ ScreenToClient(hwndDlg,((LPPOINT)&r2));
+ ScreenToClient(hwndDlg,((LPPOINT)&r2)+1);
+
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_EDIT1),0,r1.right+GAP,r1.top,r.left-r1.right-GAP-GAP,r2.bottom-r2.top,/*SWP_NOMOVE|*/SWP_NOACTIVATE|SWP_NOZORDER);
+ return 0;
+ }
+
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON_QUERYBUILD),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_COMBO2),SW_SHOWNA);
+
+ nde_field_t p = (-1 != myId) ? NDE_Table_GetColumnByID(g_table, (unsigned char)myId) : NULL;
+ if (p)
+ {
+ if (NDE_ColumnField_GetDataType(p)==FIELD_DATETIME)
+ {
+ //if (!IsWindowVisible(GetDlgItem(hwndDlg,IDC_BUTTON2)))
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON2),&r);
+ ScreenToClient(hwndDlg,((LPPOINT)&r));
+
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON2),SW_SHOWNA);
+
+ RECT r1;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_COMBO2),&r1);
+ ScreenToClient(hwndDlg,((LPPOINT)&r1));
+ ScreenToClient(hwndDlg,((LPPOINT)&r1)+1);
+
+ RECT r2;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_EDIT1),&r2);
+ ScreenToClient(hwndDlg,((LPPOINT)&r2));
+ ScreenToClient(hwndDlg,((LPPOINT)&r2)+1);
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_EDIT1),0,r1.right+GAP,r1.top,r.left-r1.right-GAP-GAP,r2.bottom-r2.top,/*SWP_NOMOVE|*/SWP_NOACTIVATE|SWP_NOZORDER);
+ }
+ }
+ else
+ {
+ //if (IsWindowVisible(GetDlgItem(hwndDlg,IDC_BUTTON2)))
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON2),&r);
+ ScreenToClient(hwndDlg,((LPPOINT)&r) + 1);
+
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON2),SW_HIDE);
+
+ RECT r1;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_COMBO2),&r1);
+ ScreenToClient(hwndDlg,((LPPOINT)&r1));
+ ScreenToClient(hwndDlg,((LPPOINT)&r1)+1);
+
+ RECT r2;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_EDIT1),&r2);
+ ScreenToClient(hwndDlg,((LPPOINT)&r2));
+ ScreenToClient(hwndDlg,((LPPOINT)&r2)+1);
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_EDIT1),0,r1.right+GAP,r1.top,r.right-r1.right-GAP,r2.bottom-r2.top,/*SWP_NOMOVE|*/SWP_NOACTIVATE|SWP_NOZORDER);
+ }
+ }
+#undef GAP
+ typedef struct
+ {
+ int str;
+ char id;
+ } fillT;
+
+ int myfillt_len=0;
+ fillT *myfillt=NULL;
+ static fillT foo1[]=
+ {
+ {IDS_EQUALS,FILTER_EQUALS},
+ {IDS_DOES_NOT_EQUAL,FILTER_NOTEQUALS},
+ {IDS_CONTAINS,FILTER_CONTAINS},
+ {IDS_DOES_NOT_CONTAIN,FILTER_NOTCONTAINS},
+ {IDS_IS_ABOVE,FILTER_ABOVE},
+ {IDS_IS_BELOW,FILTER_BELOW},
+ {IDS_EQUALS_OR_IS_ABOVE,FILTER_ABOVEOREQUAL},
+ {IDS_EQUALS_OR_IS_BELOW,FILTER_BELOWOREQUAL},
+ {IDS_IS_EMPTY,FILTER_ISEMPTY},
+ {IDS_IS_NOT_EMPTY,FILTER_ISNOTEMPTY},
+ {IDS_BEGINS_WITH,FILTER_BEGINS},
+ {IDS_ENDS_WITH,FILTER_ENDS},
+ {IDS_IS_SIMILAR_TO,FILTER_LIKE},
+ };
+ static fillT foo2[]=
+ {
+ {IDS_AT,FILTER_EQUALS},
+ {IDS_NOT_AT,FILTER_NOTEQUALS},
+ {IDS_AFTER,FILTER_ABOVE},
+ {IDS_BEFORE,FILTER_BELOW},
+ {IDS_SINCE,FILTER_ABOVEOREQUAL},
+ {IDS_UNTIL,FILTER_BELOWOREQUAL},
+ {IDS_IS_EMPTY,FILTER_ISEMPTY},
+ {IDS_IS_NOT_EMPTY,FILTER_ISNOTEMPTY},
+ };
+ switch (NDE_ColumnField_GetDataType(p))
+ {
+ case FIELD_DATETIME:
+ {
+ myfillt_len = sizeof(foo2)/sizeof(foo2[0]);
+ myfillt=foo2;
+ }
+ break;
+ case FIELD_LENGTH:
+ case FIELD_INTEGER:
+ {
+ myfillt_len = sizeof(foo1)/sizeof(foo1[0]) - 3;
+ myfillt=foo1;
+ }
+ break;
+ case FIELD_FILENAME:
+ case FIELD_STRING:
+ {
+ myfillt_len = sizeof(foo1)/sizeof(foo1[0]);
+ myfillt=foo1;
+ }
+ break;
+ default:
+ break;
+ }
+ if (myfillt)
+ {
+ wchar_t *str;
+ int width = 0;
+ while (myfillt_len--)
+ {
+ int a=SendMessageW(hwndCB2,CB_ADDSTRING,0,(LPARAM)(str = WASABI_API_LNGSTRINGW(myfillt->str)));
+ width = ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO2, str, width);
+ SendMessage(hwndCB2,CB_SETITEMDATA,a,(LPARAM)myfillt->id);
+ myfillt++;
+ }
+ if (lastsel != CB_ERR)
+ SendMessage(hwndCB2,CB_SETCURSEL,lastsel,0);
+ }
+ }
+ }
+ }
+ return 0;
+ }
+ return 0;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(uMsg) ||
+ WM_MOUSEWHEEL == uMsg)
+ {
+ HWND parentWindow;
+ parentWindow = GetAncestor(hwndDlg, GA_PARENT);
+ if (NULL != parentWindow)
+ {
+ SendMessageW(parentWindow, WM_MOUSEWHEEL, wParam, lParam);
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (long)TRUE);
+ return TRUE;
+ }
+ }
+
+ return 0;
+}
+
+
+static INT_PTR CALLBACK scrollChildProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static int osize;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ GetClientRect(hwndDlg,&r);
+ osize=r.bottom;
+ }
+ return 1;
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_BUTTON1)
+ SPL_AddBlankFilter(hwndDlg);
+ return 0;
+ case WM_USER+40: // get query string
+ if (!lParam)
+ {
+ // now we go through our children, asking each one for a string, and combine them.
+ GayStringW str;
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ wchar_t * nextop=NULL;
+ while (h)
+ {
+ if (h != GetDlgItem(hwndDlg,IDC_BUTTON1))
+ {
+ wchar_t buf[512] = {0};
+ buf[0]=0;
+ SendMessage(h,WM_USER+40,(WPARAM)buf,(LPARAM)sizeof(buf)/sizeof(*buf));
+ if (buf[0])
+ {
+ if(nextop) str.Append(nextop);
+ nextop = IsDlgButtonChecked(h,IDC_AND) ? L" AND " : L" OR ";
+ //if (str.Get() && str.Get()[0]) str.Append(isOr ? L" OR " : L" AND ");
+ str.Append(buf);
+ }
+ }
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+ lstrcpynW(m_item_query,str.Get()?str.Get():L"",sizeof(m_item_query)/sizeof(*m_item_query));
+ }
+ return 0;
+ case WM_USER+41: // remove all
+ {
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ std::vector<void*> w;
+ while (h) {
+ if (h != GetDlgItem(hwndDlg,IDC_BUTTON1))
+ w.push_back((void*)h);
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+ for ( void *l_w : w )
+ SendMessage( hwndDlg, WM_USER + 34, (WPARAM)(HWND)l_w, -1 );
+ }
+ break;
+ case WM_USER+34: // remove filter by hwnd
+ if (wParam && lParam == -1)
+ {
+ HWND hwndRemove = (HWND) wParam;
+ RECT r;
+
+ GetClientRect(hwndRemove,&r);
+ int lh=r.bottom; // height to remove
+
+ GetWindowRect(hwndRemove,&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+ int ltop=r.top;
+
+ DestroyWindow(hwndRemove);
+
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ while (h)
+ {
+ RECT r;
+
+ GetWindowRect(h,&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+
+ if (r.top > ltop)
+ {
+ SetWindowPos(h,0,r.left,r.top - lh,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+
+ GetClientRect(hwndDlg,&r);
+ r.bottom -= lh;
+ SetWindowPos(hwndDlg,0,0,0,r.right,r.bottom,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
+
+ SendMessage(GetParent(hwndDlg),WM_USER+33,17,0);
+ }
+ return 0;
+ case WM_USER+32: // add filter. lParam is mode. 1 means add by filter, 2 means add blank
+ if(lParam == 2) {
+ BOOL b=0;
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ while (h) {
+ if (h != GetDlgItem(hwndDlg,IDC_BUTTON1))
+ b = filterProc(h,WM_USER+29,(WPARAM)b,0);
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+ }
+ if ((lParam == 1 && wParam) || (lParam == 2 && !wParam))
+ {
+ HWND newChild=0;
+ nde_filter_t filter=(nde_filter_t)wParam;
+ if (lParam == 2 || (NULL != NDE_Table_GetColumnByID(g_table, NDE_Filter_GetID(filter))))
+ {
+ newChild=WASABI_API_CREATEDIALOGPARAMW(IDD_SCROLLCHILDFILTER,hwndDlg,filterProc,(LPARAM)filter);
+ RECT r,r2;
+ GetClientRect(hwndDlg,&r);
+ GetClientRect(newChild,&r2);
+ SetWindowPos(hwndDlg,0,0,0,r.right,r.bottom + r2.bottom,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
+ SetWindowPos(newChild,0,0,r.bottom - osize,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+
+ HWND h = GetDlgItem(hwndDlg,IDC_BUTTON1);
+ GetWindowRect(h,&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+ SetWindowPos(h,0,r.left,r.top + r2.bottom,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ ShowWindow(newChild,SW_SHOWNA);
+ }
+ if (lParam == 2) {
+ // update scroll, hugging bottom
+ SendMessage(GetParent(hwndDlg),WM_USER+33,16,0);
+ }
+ return (intptr_t)newChild;
+ }
+ return 0;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(uMsg) ||
+ WM_MOUSEWHEEL == uMsg)
+ {
+ HWND parentWindow;
+ parentWindow = GetAncestor(hwndDlg, GA_PARENT);
+ if (NULL != parentWindow)
+ {
+ SendMessageW(parentWindow, WM_MOUSEWHEEL, wParam, lParam);
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (long)TRUE);
+ return TRUE;
+ }
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK scrollChildHostProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static HWND m_child;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(GetParent(hwndDlg),IDC_CHILDFRAME),&r);
+ ScreenToClient(GetParent(hwndDlg),(LPPOINT)&r);
+ ScreenToClient(GetParent(hwndDlg),((LPPOINT)&r)+1);
+ SetWindowPos(hwndDlg,NULL,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ m_child=WASABI_API_CREATEDIALOGW(IDD_SCROLLCHILD,hwndDlg,scrollChildProc);
+ return 1;
+ case WM_USER+33:
+ if (m_child)
+ {
+ RECT r;
+ RECT r2;
+ GetClientRect(hwndDlg,&r2);
+ GetClientRect(m_child,&r);
+ if (r2.bottom < r.bottom)
+ {
+ SCROLLINFO si={sizeof(si),SIF_RANGE|SIF_PAGE|SIF_POS,0,r.bottom,r2.bottom,wParam == 16 ? r.bottom : 0,0};
+
+ if (wParam == 17)
+ si.fMask &= ~SIF_POS;
+
+ SetScrollInfo(hwndDlg,SB_VERT,&si,TRUE);
+
+ if (wParam != 17)
+ SetWindowPos(m_child,NULL,0,wParam == 16 ? r2.bottom-r.bottom : 0,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ } else {
+ //hide the scrollbar
+ SetWindowPos(m_child,NULL,0,0,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ ShowScrollBar(hwndDlg,SB_VERT,FALSE);
+ }
+ ShowWindow(m_child,SW_SHOWNA);
+ }
+ return 0;
+ case WM_USER+32:
+ case WM_USER+41:
+ case WM_USER+40:
+ if (m_child) return scrollChildProc(m_child,uMsg,wParam,lParam);
+ return 0;
+ case WM_VSCROLL:
+ {
+ RECT r;
+ RECT r2;
+ GetClientRect(hwndDlg,&r2);
+ GetClientRect(m_child,&r);
+
+ if (r2.bottom < r.bottom)
+ {
+ int v=0;
+ if (LOWORD(wParam) == SB_THUMBPOSITION || LOWORD(wParam) == SB_THUMBTRACK)
+ {
+ SCROLLINFO si={sizeof(si),SIF_TRACKPOS|SIF_POS};
+ GetScrollInfo(hwndDlg,SB_VERT,&si);
+ v=si.nTrackPos;
+ }
+ else if (LOWORD(wParam) == SB_TOP)
+ {
+ v=0;
+ }
+ else if (LOWORD(wParam) == SB_BOTTOM)
+ {
+ v=r.bottom-r2.bottom;
+ }
+ else if (LOWORD(wParam) == SB_PAGEDOWN || LOWORD(wParam) == SB_LINEDOWN)
+ {
+ SCROLLINFO si={sizeof(si),SIF_TRACKPOS|SIF_POS};
+ GetScrollInfo(hwndDlg,SB_VERT,&si);
+ v=si.nPos + r2.bottom;
+ if (v > r.bottom-r2.bottom) v=r.bottom-r2.bottom;
+ }
+ else if (LOWORD(wParam) == SB_PAGEUP || LOWORD(wParam) == SB_LINEUP)
+ {
+ SCROLLINFO si={sizeof(si),SIF_TRACKPOS|SIF_POS};
+ GetScrollInfo(hwndDlg,SB_VERT,&si);
+ v=si.nPos - r2.bottom;
+ if (v < 0) v=0;
+ }
+ else return 0;
+
+ SetScrollPos(hwndDlg,SB_VERT,v,!(LOWORD(wParam) == SB_THUMBPOSITION || LOWORD(wParam) == SB_THUMBTRACK));
+ SetWindowPos(m_child,NULL,0,0-v,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ else
+ {
+ SetScrollPos(hwndDlg,SB_VERT,0,!(LOWORD(wParam) == SB_THUMBPOSITION || LOWORD(wParam) == SB_THUMBTRACK));
+ SetWindowPos(m_child,NULL,0,0,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ }
+ return 0;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(uMsg) ||
+ WM_MOUSEWHEEL == uMsg)
+ {
+ WORD scrollCommand;
+ short delta;
+
+ delta = HIWORD(wParam);
+
+ scrollCommand = (delta > 0) ? SB_LINEUP : SB_LINEDOWN;
+
+ SendMessageW(hwndDlg, WM_VSCROLL, MAKEWPARAM(scrollCommand, 0), 0L);
+ }
+ return 0;
+}
+
+void addNewQuery(HWND parent) {
+ HWND hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_ADD_VIEW_2, parent, addQueryFrameDialogProc2, 0);
+ SetActiveWindow(hwnd);
+}
+
+void queryEditItem(int n)
+{
+ m_edit_item=n;
+ HWND hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_ADD_VIEW_2, plugin.hwndLibraryParent, addQueryFrameDialogProc2, 1);
+ SetActiveWindow(hwnd);
+}
+
+// returns true if edition was validated, false if cancel was clicked
+// actual return values are in m_item_query, m_item_name and m_item_mode
+int queryEditOther(HWND hwnd, const char *query, const char *viewname, int mode)
+{
+ int notnew=1;
+ if (query == NULL || !*query) notnew = 0;
+ m_edit_item = -1;
+ if (notnew)
+ {
+ MultiByteToWideCharSZ(CP_ACP, 0, viewname, -1, m_item_name, 256);
+ MultiByteToWideCharSZ(CP_ACP, 0, query, -1, m_item_query, 1024);
+ }
+ m_item_mode = mode;
+ if(mode == -1)
+ return WASABI_API_DIALOGBOXPARAMW(IDD_ADD_VIEW_2_NF, hwnd, addQueryFrameDialogProc2, notnew);
+ else
+ return WASABI_API_DIALOGBOXPARAMW(IDD_ADD_VIEW_2, hwnd, addQueryFrameDialogProc2, notnew);
+}
+
+void queryDeleteItem(HWND parent, int n)
+{
+ QueryList::iterator iter;
+ iter = m_query_list.find(n);
+ if (iter == m_query_list.end()) return;
+
+ wchar_t title[64] = {0};
+ queryItem *item = iter->second;
+ if (MessageBoxW(parent,WASABI_API_LNGSTRINGW(IDS_DELETE_THIS_VIEW),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,title,64),
+ MB_YESNO|MB_ICONQUESTION) == IDYES)
+ {
+ mediaLibrary.RemoveTreeItem(iter->first);
+ m_query_list.erase(iter->first);
+ saveQueryTree();
+
+ // we deleted the item from the tree, which apparently is enough to close the current dialog if it was
+ // the current dialog. hot.
+
+ for(iter = m_query_list.begin(); iter != m_query_list.end(); iter++)
+ if(iter->second && iter->second->index > item->index) iter->second->index--;
+
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ DeleteFileW(configDir);
+
+ free(item->metafn);
+ free(item->name);
+ free(item->query);
+ free(item);
+ }
+}
+
+
+ int IPC_LIBRARY_SENDTOMENU;
+static librarySendToMenuStruct s_menu;
+
+static WNDPROC ml_oldWndProc2;
+static INT_PTR CALLBACK ml_newWndProc2(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s_menu.build_hMenu && s_menu.mode==1)
+ {
+ myMenu = TRUE;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s_menu, IPC_LIBRARY_SENDTOMENU)==(LRESULT)-1)
+ s_menu.mode=2;
+ myMenu = FALSE;
+ if(ml_oldWndProc2) SetWindowLongPtrW(hwndDlg,GWLP_WNDPROC,(LONG_PTR)ml_oldWndProc2);
+ ml_oldWndProc2 = NULL;
+
+ }
+ return 0;
+ }
+ if (ml_oldWndProc2) return CallWindowProc(ml_oldWndProc2,hwndDlg,uMsg,wParam,lParam);
+ return 0;
+}
+
+HMENU main_sendto_hmenu;
+int main_sendto_mode;
+
+void view_queryContextMenu(INT_PTR param1, HWND hHost, POINTS pts, int n)
+{
+ queryItem *item=m_query_list[n];
+ if (item == NULL) return;
+
+ ml_oldWndProc2 = (WNDPROC)SetWindowLongPtrW(hHost, GWLP_WNDPROC, (LONG_PTR)ml_newWndProc2);
+
+ HMENU menu=GetSubMenu(g_context_menus,2);
+ main_sendto_hmenu=GetSubMenu(menu,2);
+
+ s_menu.mode = 0;
+ s_menu.hwnd = 0;
+ s_menu.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU)==(LRESULT)-1)
+ {
+ s_menu.mode = 1;
+ s_menu.hwnd = hHost;
+ s_menu.data_type = ML_TYPE_ITEMRECORDLIST;
+ s_menu.ctx[1] = 1;
+ s_menu.build_hMenu = main_sendto_hmenu;
+ }
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ UpdateMenuItems(NULL, menu, IDR_QUERY_ACCELERATORS);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON,
+ pt.x, pt.y, hHost, NULL);
+ if(ml_oldWndProc2) SetWindowLongPtrW(hHost,GWLP_WNDPROC,(LONG_PTR)ml_oldWndProc2);
+ switch(r)
+ {
+ case ID_QUERYWND_EDIT:
+ queryEditItem(n);
+ break;
+ case ID_QUERYWND_DELETE:
+ queryDeleteItem(hHost,n);
+ break;
+ case ID_QUERYWND_PLAYQUERY:
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf,item->query,0);
+ }
+ break;
+ case ID_QUERYWND_ENQUEUEQUERY:
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf,item->query,1);
+ }
+ break;
+ case ID_QUERYMENU_ADDNEWQUERY:
+ addNewQuery(hHost);
+ break;
+ default:
+ if (s_menu.mode == 2)
+ {
+ s_menu.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s_menu, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ // build my data.
+ s_menu.mode=3;
+ s_menu.data_type = ML_TYPE_ITEMRECORDLISTW;
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, item->query);
+ itemRecordListW obj={0,};
+ saveQueryToListW(&viewconf, s, &obj, 0, 0, (resultsniff_funcW)-1);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ s_menu.data = (void*)&obj;
+
+ LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s_menu,IPC_LIBRARY_SENDTOMENU);
+ if (result != 1)
+ {
+ s_menu.mode=3;
+ s_menu.data_type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA={0,};
+ convertRecordList(&objA, &obj);
+ s_menu.data = (void*)&objA;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s_menu,IPC_LIBRARY_SENDTOMENU);
+ freeRecordList(&objA);
+ }
+ freeRecordList(&obj);
+
+ }
+ }
+ break;
+ }
+ if (s_menu.mode)
+ {
+ s_menu.mode=4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s_menu,IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ main_sendto_hmenu=0;
+ EatKeyboard();
+}
+
+void queriesContextMenu(INT_PTR param1, HWND hHost, POINTS pts) {
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU menu=GetSubMenu(g_context_menus,3);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ pt.x, pt.y, hHost, NULL);
+ switch(r) {
+ case ID_QUERYMENU_ADDNEWQUERY:
+ addNewQuery(hHost);
+ break;
+ case ID_QUERYMENU_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &preferences);
+ break;
+ case ID_QUERYMENU_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8105304048660-The-Winamp-Media-Library");
+ break;
+ }
+ EatKeyboard();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/queries.txt b/Src/Plugins/Library/ml_local/queries.txt
new file mode 100644
index 00000000..64030f6e
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/queries.txt
@@ -0,0 +1,77 @@
+You can enable more powerful views using the custom query language.
+
+The basic format is:
+
+<field> <comparison> [value] [<logic operator> <field> <comparison> [value] [...]]
+
+Field names:
+ TYPE: 0 for audio files, 1 for video files
+ FILENAME: Full filename (including path)
+ LENGTH: Length, in seconds (or hh:mm:ss)
+ ARTIST: Artist
+ ALBUM: Album
+ ALBUMARTIST: Album Artist
+ TITLE: Title
+ TRACKNO: Track number of file
+ GENRE: Genre
+ YEAR: Year
+ COMMENT: Comment
+ COMPOSER: Composer
+ DISC: Disc number of a CD set
+ FILESIZE: File size, in kilobytes
+ FILETIME: Last known file date/time on disk
+ LASTUPD: Date/time of file imported to library or modified in library
+ LASTPLAY: Date/time of last play
+ RATING: Rating value (1-5, or 0 or empty for unrated)
+ PLAYCOUNT: Number of plays
+ PUBLISHER: Publisher or record label
+ DIRECTOR: Film director (for videos)
+ PRODUCER: Film or Record producer
+ CATEGORY: Category
+ REPLAYGAIN_ALBUM_GAIN: ReplayGain Album Gain
+ REPLAYGAIN_TRACK_GAIN: ReplayGain Track Gain
+ BITRATE: Bitrate (in KBPS)
+ TRACKS: Total number of tracks on the disc
+ DISCS: Total number of discs in the set
+ ISPODCAST: 1 for a podcast episode, 0 otherwise
+ PODCASTCHANNEL: The name of the channel for a podcast
+ PODCASTPUBDATE: Date/time when the podcast was published
+ LOSSLESS: Shows lossless audio formats, e.g. lossless=1
+
+Comparison operators:
+ '=': String or integer equals value
+ '!=': String or integer does not equal value
+ '<': String or integer is less than value
+ '>': String or integer is greater than value
+ '<=': String or integer is less than or equal to value
+ '>=': String or integer is greater than or equal to value
+ HAS: String contains value
+ NOTHAS: String does not contain value
+ LIKE: String is similar to value ("the" and whitespace are ignored)
+ BEGINS: String begins with value
+ BEGINSLIKE: String begins like value
+ ENDS: String ends with value
+ ISEMPTY: (no comparison value required) TRUE if <field> is empty
+ ISNOTEMPTY: (no comparison value required) TRUE if <field> is not empty
+
+Values:
+ "strings with spaces" or strings_without_spaces
+ integers can be "32" or just 32
+ integers for LENGTH can be a plain integer (seconds), or mm:ss or hh:mm:ss
+ date/timestamps should be [datetime data], which can be either an absolute
+ or relative time. i.e.: [3 weeks ago], [18:15], [05/30/2003],
+ [yesterday noon], [3 days ago 5 pm], [now], [5 mn before may 30th], etc.
+
+Logic operators:
+ &&, &, or AND: boolean AND two comparisons
+ ||, |, or OR: boolean OR two comparisons
+ !, or NOT: prefix this to an expression for the boolean NOT of that expression
+
+Examples:
+ all video files: type = 1
+ audio by Air longer than 4 minutes: type = 0 & artist = "air" & length > 4:00
+ all files on drive C: filename BEGINS C:
+ high rated items that haven't been played lately: rating > 3 & lastplay < [1 week ago]
+ newly added items that haven't been played yet: lastupd >= [yesterday] & playcount <= 0
+
+Note that you can also use this syntax in the search field of views. Simply prefix a '?' or 'query:' to your search string
diff --git a/Src/Plugins/Library/ml_local/remove.cpp b/Src/Plugins/Library/ml_local/remove.cpp
new file mode 100644
index 00000000..ff24848a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/remove.cpp
@@ -0,0 +1,31 @@
+#include "main.h"
+#include "api_mldb.h"
+
+// returns 0 on success
+// returns 1 on failure of either bad filename or invalid table
+int RemoveFileFromDB(const wchar_t *filename)
+{
+ // From mldbApi
+ int ret = 1;
+ if (!g_table) openDb();
+ if (filename && g_table)
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)filename, 0);
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename))
+ {
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ ret = 0;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ // Issue wasabi callback for post removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)filename, 0);
+ }
+ return ret;
+}
diff --git a/Src/Plugins/Library/ml_local/resource.h b/Src/Plugins/Library/ml_local/resource.h
new file mode 100644
index 00000000..44f6bb3a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resource.h
@@ -0,0 +1,606 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_local.rc
+//
+#define IDS_LOCAL_MEDIA 1
+#define IDS_SEARCHING_X_FILES_FOUND 2
+#define IDS_GETTING_INFO_FROM_FILES_PERCENT 3
+#define IDR_QUERIES_TEXT 4
+#define IDS_SCANNING_DIR 4
+#define IDS_SCANNING_FILE 5
+#define IDS_CHECKING_FOR_FILE 6
+#define IDS_COMPACTING 7
+#define IDS_REMOVING_FILES_NOT_EXISTING 8
+#define IDS_INITIALIZING 9
+#define IDS_SCANNING_X_OF_X_X_REMOVED 10
+#define IDS_SCANNED_X_FILES_X_REMOVED 11
+#define IDS_DATE_TIME_IS_TOO_COMPLEX 17
+#define IDS_DATE_TIME_EDITOR_QUESTION 18
+#define IDS_COMPARE_TO_STRING 19
+#define IDS_AFTER 20
+#define IDS_BEFORE 21
+#define IDS_SINCE 22
+#define IDS_UNTIL 23
+#define IDS_COMPARE_TO_LENGTH 24
+#define IDS_ABOVE 25
+#define IDS_BELOW 26
+#define IDS_ABOVE_OR_EQUAL 27
+#define IDS_BELOW_OR_EQUAL 28
+#define IDS_COMPARE_TO_NUMBER 29
+#define IDS_OFFSET_BY 30
+#define IDS_TIME_AGO 31
+#define IDS_SPACE_AFTER 32
+#define IDS_SPACE_BEFORE 33
+#define IDS_THE 34
+#define IDS_1ST 35
+#define IDS_2ND 36
+#define IDS_3RD 37
+#define IDS_4TH 38
+#define IDS_OF_THIS_MONTH 39
+#define IDS_NOW 40
+#define IDS_THIS_DATE 41
+#define IDS_THIS_MONTH 42
+#define IDS_THIS_DAY 43
+#define IDS_THIS_TIME 44
+#define IDS_ON_SPACE 45
+#define IDS_IN_SPACE 46
+#define IDS_OF_SPACE 47
+#define IDS_AT_SPACE 48
+#define IDS_NOON 49
+#define IDS_MIDNIGHT 50
+#define IDS_AGO 51
+#define IDS_QUERY_FIELD_IS_EMPTY 52
+#define IDS_EMPTY_QUERY 53
+#define IDS_NO_CHANGES_MADE_TO_QUERY 54
+#define IDS_QUERY_NOT_CHANGED 55
+#define IDS_REGISTERED 56
+#define IDS_ADD_PLEDIT_TO_LOCAL_MEDIA 57
+#define IDS_ERROR 58
+#define IDS_ADD_TO_LOCAL_MEDIA 59
+#define IDS_AUDIO 60
+#define IDS_VIDEO 61
+#define IDS_MOST_PLAYED 62
+#define IDS_RECENTLY_ADDED 63
+#define IDS_RECENTLY_PLAYED 64
+#define IDS_NEVER_PLAYED 65
+#define IDS_TOP_RATED 66
+#define IDS_REMOVE_ALL_ITEMS_IN_LIBRARY 67
+#define IDS_CONFIRMATION 68
+#define IDS_NEW_SMART_VIEW 69
+#define IDS_RESCAN_WATCH_FOLDERS 70
+#define IDS_ADD_MEDIA_TO_LIBRARY 71
+#define IDS_REMOVE_MISSING_FILES_FROM_ML 72
+#define IDS_ANY 73
+#define IDS_ALL 74
+#define IDS_OPTIONS 75
+#define IDS_DIRECTORY 76
+#define IDS_STOP_SCAN 77
+#define IDS_RESCAN_NOW 78
+#define IDS_DEFAULT 79
+#define IDS_RESCAN_ABORTED 80
+#define IDS_WATCH_FOLDERS 81
+#define IDS_EDIT_VIEW 82
+#define IDS_MY_NEW_VIEW 83
+#define IDS_SIMPLE_VIEW_EDITOR 84
+#define IDS_ADVANCED_EDITOR 85
+#define IDS_FILTERS 86
+#define IDS_MUST_ENTER_A_NAME 87
+#define IDS_DISCARD_CHANGES_TO_SIMPLE_EDITOR 88
+#define IDS_DAY 88
+#define IDS_VIEW_QUERY_MAY_HAVE_ERRORS 89
+#define IDS_VIEW_QUERY_IS_TOO_COMPLEX 90
+#define IDS_TEXT_AFTER_THE_FIRST_ERROR 91
+#define IDS_SOME_OF_THE_QUERY_LOGIC 92
+#define IDS_VIEW_EDITOR_QUESTION 93
+#define IDS_DELETE_THIS_VIEW 94
+#define IDS_REFINE 95
+#define IDS_CLEAR_REFINE 96
+#define IDS_ARTISTS 97
+#define IDS_ARTIST 97
+#define IDS_TITLE 98
+#define IDS_ALBUM 99
+#define IDS_LENGTH 100
+#define IDS_TRACK_LENGTH 101
+#define IDS_TRACK_NUMBER 101
+#define IDS_GENRE 102
+#define IDR_CONTEXTMENUS 103
+#define IDS_YEAR 103
+#define IDD_VIEW_MEDIA 104
+#define IDS_FILENAME 104
+#define IDD_VIEW_AUDIO 105
+#define IDS_RATING 105
+#define IDS_PLAY_COUNT 106
+#define IDS_PLAYED_LAST 107
+#define IDD_ADD_VIEW 108
+#define IDS_LAST_UPDATED 108
+#define IDS_FILE_TIME 109
+#define IDS_COMMENT 110
+#define IDS_FILE_SIZE 111
+#define IDS_BITRATE 112
+#define IDS_TYPE 113
+#define IDS_DISC 114
+#define IDS_ALBUM_ARTIST 115
+#define IDS_FILE_PATH 116
+#define IDD_EDITDIR 117
+#define IDS_ALBUM_GAIN 117
+#define IDS_TRACK_GAIN 118
+#define IDS_PUBLISHER 119
+#define IDS_COMPOSER 120
+#define IDS_EXTENSION 121
+#define IDS_IS_PODCAST 122
+#define IDD_EDIT_QUERY 123
+#define IDS_PODCAST_CHANNEL 123
+#define IDS_PODCAST_PUBLISH_DATE 124
+#define IDS_SCANNING 125
+#define IDD_EDIT_QUERY_PICK 126
+#define IDS_ERROR_DELETING_FILES 126
+#define IDD_NDE_RECOVERY 127
+#define IDS_ERROR_DELETING_X 127
+#define IDD_NEEDADDFILES 128
+#define IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY 128
+#define IDS_HIDE_INFO 129
+#define IDS_SHOW_INFO 130
+#define IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY 131
+#define IDS_ADD_MORE 132
+#define IDS_PLAY_ALL_FILES_BY 133
+#define IDB_TREEITEM_AUDIO 134
+#define IDS_PLAY_ALL_FILES_FROM 134
+#define IDB_TREEITEM_MOSTPLAYED 135
+#define IDS_PODCAST 135
+#define IDB_TREEITEM_NEVERPLAYED 136
+#define IDS_NON_PODCAST 136
+#define IDS_MIXABLE 137
+#define IDS_X_ITEM 138
+#define IDB_TREEITEM_RECENTLYADDED 139
+#define IDS_ALBUM_ART 139
+#define IDB_TREEITEM_RECENTLYPLAYED 140
+#define IDB_TREEITEM_TOPRATED 141
+#define IDS_LOOKING_UP_MEDIA_INFO 141
+#define IDB_TREEITEM_VIDEO 142
+#define IDS_CLICK_AN_ITEM_TO_GET_ITS_INFO 142
+#define IDD_MONITOR_SMALL 143
+#define IDS_PLEASE_CONNECT_TO_THE_INTERNET_TO_USE_THIS_FEATURE 143
+#define IDD_MONITOR_HIDE 144
+#define IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM 144
+#define IDD_REFRESH_METADATA 145
+#define IDS_ALL_X_ALBUMS 145
+#define IDD_REINDEX 146
+#define IDS_NO_ALBUM 146
+#define IDS_OPEN_MEDIA_LIBRARY_VIEW_RESULTS 147
+#define IDB_BITMAP1 148
+#define IDS_MEDIA_LIBRARY_VIEW_RESULTS 148
+#define IDB_TREEITEM_PODCASTS 149
+#define IDS_ADD_MEDIA_TO_LIBRARY_ 149
+#define IDS_SELECT_FOLDER_TO_ADD_TO_WINAMP_MEDIA_LIBRARY 150
+#define IDR_TEXT1 150
+#define IDB_TREEITEM_RECENTLYMODIFIED 150
+#define IDS_ADD 151
+#define IDD_ADD_VIEW_2 151
+#define IDS_SCAN_FOLDER_IN_BACKGROUND 152
+#define IDS_FOLDER_NAME_IS_INCORRECT 153
+#define IDS_INCORRECT_FOLDER_NAME 154
+#define IDS_WILLS_UBER_STRING 155
+#define IDS_EDIT_SMART_VIEW 156
+#define IDB_NEWFILTER_SIMPLE 157
+#define IDS_ADVANCED_MODE 157
+#define IDB_NEWFILTER_THREEFILTERS 158
+#define IDS_SIMPLE_MODE 158
+#define IDB_NEWFILTER_TWOFILTERS 159
+#define IDS_DAYS 159
+#define IDB_NEWFILTER_SIMPLEALBUM 160
+#define IDS_LENGTH_DURATION_STRING 160
+#define IDS_ITEM 161
+#define IDD_ADD_VIEW_2_NF 162
+#define IDS_ITEMS 162
+#define IDD_ADD_VIEW_CHILD_ADVANCED 163
+#define IDS_SCANNING_PLAIN 163
+#define IDS_ALL_ARTISTS 164
+#define IDS_ARTIST_INDEX 165
+#define IDB_TOOL_MODE 165
+#define IDS_ALBUM_ARTIST_INDEX 166
+#define IDB_TOOL_ART 166
+#define IDS_CUSTOM 167
+#define IDB_TOOL_COLS 167
+#define IDS_IS_VIDEO 168
+#define IDS_FILE_SIZE_KB 169
+#define IDR_IMAGE_NOTFOUND 169
+#define IDS_BITRATE_KBPS 170
+#define IDS_DISC_NUMBER 171
+#define IDS_DISCS 172
+#define IDS_TRACKS 173
+#define IDS_EQUALS 174
+#define IDS_DOES_NOT_EQUAL 175
+#define IDS_CONTAINS 176
+#define IDS_DOES_NOT_CONTAIN 177
+#define IDS_IS_ABOVE 178
+#define IDS_IS_BELOW 179
+#define IDS_EQUALS_OR_IS_ABOVE 180
+#define IDS_EQUALS_OR_IS_BELOW 181
+#define IDS_IS_EMPTY 182
+#define IDS_IS_NOT_EMPTY 183
+#define IDS_BEGINS_WITH 184
+#define IDS_ENDS_WITH 185
+#define IDS_IS_SIMILAR_TO 186
+#define IDS_AT 187
+#define IDS_NOT_AT 188
+#define IDS_STRING189 189
+#define IDS_PODCASTS 190
+#define IDS_AUDIO_BY_GENRE 191
+#define IDS_AUDIO_BY_INDEX 192
+#define IDS_60s_MUSIC 193
+#define IDS_70s_MUSIC 194
+#define IDS_80s_MUSIC 195
+#define IDS_90s_MUSIC 196
+#define IDS_00s_MUSIC 197
+#define IDS_ROCK_MUSIC 198
+#define IDS_CLASSICAL_MUSIC 199
+#define IDS_RECORD_LABELS 200
+#define IDS_ALBUMS 201
+#define IDS_ALBUM_ARTISTS 202
+#define IDS_ARTIST_S 203
+#define IDS_COMPOSERS 204
+#define IDS_GENRES 205
+#define IDS_PUBLISHERS 206
+#define IDS_YEARS 207
+#define IDS_ALBUM_ARTIST_INDEXES 208
+#define IDS_ARTIST_INDEXES 209
+#define IDS_PODCAST_CHANNELS 210
+#define IDS_ALL_X_S 211
+#define IDS_NO_S 212
+#define IDS_SIZE 213
+#define IDS_INFO_NAVIGATE_ERROR 214
+#define IDS_NO_IMAGE 214
+#define IDS_AVAILABLE 215
+#define IDS_NO_ARTIST 216
+#define IDS_NO_GENRE 217
+#define IDS_OTHER2 218
+#define IDD_PREFS1 219
+#define IDS_STRING219 219
+#define IDS_CREATE_NEW_SMART_VIEW 219
+#define IDS_SIMPLE_ALBUM 219
+#define IDD_VIEW_DB_ERROR 220
+#define IDS_AUDIO_BUTTON_TT1 220
+#define IDS_AUDIO_BUTTON_TT2 221
+#define IDS_AUDIO_BUTTON_TT3 222
+#define IDS_GET_ALBUM_ART 223
+#define IDS_REFRESH_ALBUM_ART 224
+#define IDS_OPEN_FOLDER 225
+#define IDS_BPM 226
+#define IDS_META_STR 227
+#define IDD_PREFSFR 228
+#define IDS_SMART_STR 228
+#define IDS_GUESS_STR 229
+#define IDS_RECURSE_STR 230
+#define IDS_MORE_ARTIST_INFO 234
+#define IDS_PLAY_RANDOM_ITEM 235
+#define IDS_ENQUEUE_RANDOM_ITEM 236
+#define IDS_LOSSLESS 237
+#define IDS_CATEGORY 238
+#define IDD_VIEW_MINIINFO 239
+#define IDS_CATEGORIES 239
+#define IDS_GENRE_ALT 240
+#define IDS_YEAR_ALT 241
+#define IDD_PREFS3 242
+#define IDS_ALBUM_ARTIST_ALT 242
+#define IDS_ALBUM_GAIN_ALT 243
+#define IDS_PUBLISHER_ALT 244
+#define IDS_COMPOSER_ALT 245
+#define IDS_ARTIST_INDEX_ALT 246
+#define IDD_CUSTCOLUMNS 247
+#define IDS_ALBUM_ARTIST_INDEX_ALT 247
+#define IDS_PODCAST_CHANNEL_ALT 248
+#define IDS_CATEGORY_ALT 249
+#define IDD_SCROLLCHILD 250
+#define IDS_ARTIST_ALT 250
+#define IDD_SCROLLCHILDHOST 251
+#define IDS_ALBUM_ALT 251
+#define IDD_SCROLLCHILDFILTER 252
+#define IDS_KBPS 252
+#define IDD_TIMEEDITOR 253
+#define IDS_RECENTLY_PLAYED_TEXT 253
+#define IDS_PRODUCER 255
+#define IDS_DIRECTOR 256
+#define IDS_PRODUCER_ALT 257
+#define IDS_DIRECTOR_ALT 258
+#define IDS_CODEC 259
+#define IDD_PREFS_METADATA 260
+#define IDS_WIDTH 260
+#define IDS_HEIGHT 261
+#define IDS_DIMENSION 262
+#define IDS_IN_X_SEC 263
+#define IDS_TRACKS_MENU 264
+#define IDS_ERROR_PLG_SELECT_TRACKS 267
+#define IDS_NULLSOFT_PLAYLIST_GENERATOR 268
+#define IDS_VIEW_ALL_FILES_BY 269
+#define IDS_VIEW_ALL_FILES_FROM 270
+#define IDR_DB_ERROR 270
+#define IDR_NDE_ERROR 273
+#define IDS_DATE_ADDED 274
+#define IDS_REFRESH_FILESIZE_DATEADDED 275
+#define IDS_RECENTLY_MODIFIED 276
+#define IDS_FINISHED 277
+#define IDS_REFRESH_MESSAGE 278
+#define IDS_CLOUD 279
+#define IDR_VIEW_ACCELERATORS 279
+#define IDS_CLOUD_SOURCES 280
+#define IDR_QUERY_ACCELERATORS 280
+#define IDS_CLOUD_HIDDEN 281
+#define IDS_TRACK_AVAILABLE 282
+#define IDS_UPLOAD_TO_SOURCE 283
+#define IDS_UPLOADING_TO_SOURCE 284
+#define IDS_STRING140 285
+#define IDD_EDIT_INFO 286
+#define IDD_ADDSTUFF 287
+#define IDS_NO_RATING 516
+#define IDS_X_ITEM_SELECTED 517
+#define IDS_X_ITEMS_SELECTED 518
+#define IDS_UPDATING_FILES 519
+#define IDS_UPDATING_X 520
+#define IDS_ERROR_UPDATING_FILE 521
+#define IDS_INFO_UPDATING_ERROR 522
+#define IDC_LIST1 1000
+#define IDC_LIST2 1001
+#define IDC_EDIT_ARTIST 1002
+#define IDC_LIST3 1002
+#define IDC_CHECK_ARTIST 1003
+#define IDC_CHECK_TITLE 1004
+#define IDC_CHECK_ALBUM 1005
+#define IDC_CLEAR 1005
+#define IDC_QUICKSEARCH 1006
+#define IDC_CHECK_TRACK 1006
+#define IDC_PROGRESS1 1007
+#define IDC_CHECK_GENRE 1007
+#define IDC_STATUS 1008
+#define IDC_CHECK_YEAR 1008
+#define IDC_RECT 1009
+#define IDC_EDIT_TITLE 1009
+#define IDC_EDIT_ALBUM 1010
+#define IDC_EDIT_TRACK 1011
+#define IDC_EDIT_GENRE 1012
+#define IDC_HDELIM 1013
+#define IDC_EDIT_YEAR 1013
+#define IDC_CHECK_COMMENT 1014
+#define IDC_MEDIASTATUS 1015
+#define IDC_EDIT_COMMENT 1015
+#define IDC_BUTTON_PLAY 1016
+#define IDC_CHECK_DISC 1016
+#define IDC_BUTTON_ENQUEUE 1017
+#define IDC_EDIT_DISC 1017
+#define IDC_BUTTON_INFOTOGGLE 1018
+#define IDC_CHECK_ALBUMARTIST 1018
+#define IDC_EDIT_ALBUMARTIST 1019
+#define IDC_BUTTON_CREATEPLAYLIST 1019
+#define IDC_SEARCHCAPTION 1020
+#define IDC_CHECK_PUBLISHER 1020
+#define IDC_EDIT_PUBLISHER 1021
+#define IDC_RESCAN 1022
+#define IDC_CHECK_COMPOSER 1022
+#define IDC_NAME 1023
+#define IDC_EDIT_COMPOSER 1023
+#define IDC_QUERY 1024
+#define IDC_CHECK_CATEGORY 1024
+#define IDC_DIV1 1025
+#define IDC_EDIT_COMPOSER2 1025
+#define IDC_EDIT_CATEGORY 1025
+#define IDC_DIV2 1026
+#define IDC_CHECK_BPM 1026
+#define IDC_EDIT_BPM 1027
+#define IDC_CHECK_RATING 1028
+#define IDC_RADIO1 1029
+#define IDC_RADIO2 1030
+#define IDC_RADIO6 1031
+#define IDC_RADIO_TWOFILTERS 1031
+#define IDC_COMBO1 1032
+#define IDC_RADIO8 1032
+#define IDC_COMBO2 1033
+#define IDC_RADIO_THREEFILTERS 1033
+#define IDC_RADIO3 1035
+#define IDC_RADIO_BELOW 1035
+#define IDC_RADIO_ABOVEOREQUAL 1036
+#define IDC_RADIO_BELOWOREQUAL 1037
+#define IDC_COMBO_FILTER1 1046
+#define IDC_COMBO_FILTER2 1047
+#define IDC_COMBO_FILTER3 1048
+#define IDC_RADIO_FILTERS2 1049
+#define IDC_RADIO_FILTERS3 1050
+#define IDC_HEADER 1051
+#define IDC_STATIC_FILTER3 1051
+#define IDC_CHECK1 1052
+#define IDC_REMEMBER_SEARCH 1053
+#define IDC_CHECK2 1054
+#define IDC_CHECK7 1055
+#define IDC_EDIT2 1056
+#define IDC_STATIC4 1060
+#define IDC_CHECK8 1061
+#define IDC_BUTTON1 1062
+#define IDC_BUTTON3 1063
+#define IDC_STATIC5 1063
+#define IDC_BUTTON4 1064
+#define IDC_BUTTON5 1065
+#define IDC_BUTTON_NOW 1065
+#define IDC_BUTTON2 1066
+#define IDC_BUTTON_QUERYBUILD 1067
+#define IDC_EDIT1 1071
+#define IDC_CHECK3 1076
+#define IDC_CHECK4 1077
+#define IDC_CHECK5 1078
+#define IDC_TAB1 1079
+#define IDC_CHECK6 1087
+#define IDC_TEXT 1090
+#define IDC_RADIO_ISEMPTY 1091
+#define IDC_RADIO_TIMEAGO_Y 1094
+#define IDC_RADIO_TIMEAGO_M 1095
+#define IDC_RADIO_TIMEAGO_W 1096
+#define IDC_RADIO_TIMEAGO_D 1097
+#define IDC_RADIO_TIMEAGO_H 1098
+#define IDC_RADIO_TIMEAGO_MIN 1099
+#define IDC_RADIO_TIMEAGO_S 1100
+#define IDC_EDIT3 1106
+#define IDC_MINUTES 1137
+#define IDC_CHECK_RELATIVE 1139
+#define IDC_CHECK_ABSOLUTE 1140
+#define IDC_DATETIMEPICKER2 1141
+#define IDC_CHECK_TIMEAGO 1143
+#define IDC_CHECK_SELECTIVE 1144
+#define IDC_RADIO_THISYEAR 1146
+#define IDC_RADIO_THISMONTH 1147
+#define IDC_RADIO_YEAR 1148
+#define IDC_RADIO_MONTH 1150
+#define IDC_RADIO_THISDAY 1151
+#define IDC_RADIO_DAY 1152
+#define IDC_EDIT_DAY 1153
+#define IDC_RADIO_THISTIME 1154
+#define IDC_RADIO_TIME 1155
+#define IDC_EDIT_RESULT 1164
+#define IDC_EDIT_QUERY 1165
+#define IDC_STATIC_YEAR 1166
+#define IDC_STATIC_MONTH 1167
+#define IDC_STATIC_DAY 1168
+#define IDC_STATIC_TIME 1169
+#define IDC_STATIC_STRING 1173
+#define IDC_STATIC_ABSOLUTE 1174
+#define IDC_STATIC_RELATIVE 1175
+#define IDC_STATIC_TIMEAGO 1176
+#define IDC_STATIC_SELECTIVE 1177
+#define IDC_EDIT 1178
+#define IDC_DATETIMEPICKER1 1179
+#define IDC_LIST_FIELDS 1180
+#define IDC_RADIO_EQUAL 1181
+#define IDC_RADIO_ABOVE 1182
+#define IDC_RADIO_ISLIKE 1183
+#define IDC_CHECK_NOT 1184
+#define IDC_EDIT_STRING 1185
+#define IDC_DATETIMEPICKER 1187
+#define IDC_EDIT_DATETIME 1187
+#define IDC_EDIT_TIMEAGO 1188
+#define IDC_BUTTON_PICK 1189
+#define IDC_BUTTON_MONTH 1190
+#define IDC_BUTTON_AND 1191
+#define IDC_BUTTON_OR 1192
+#define IDC_STATIC_DATETIME 1197
+#define IDC_STATIC_DIRECTION 1198
+#define IDC_RADIO_AFTER 1199
+#define IDC_RADIO_BEFORE 1200
+#define IDC_RADIO_NOON 1201
+#define IDC_RADIO_MIDNIGHT 1202
+#define IDC_STATIC_QUERYTIME 1203
+#define IDC_RADIO_BEGINS 1204
+#define IDC_RADIO_ENDS 1205
+#define IDC_STATIC_RESULT 1206
+#define IDC_STATIC_QUERY 1207
+#define IDC_STATIC_GENERAL 1208
+#define ID_BUTTON_SENDTOQUERY 1209
+#define IDC_RADIO_CONTAINS 1210
+#define IDC_DEFS 1210
+#define IDC_STATIC_TOTAL 1212
+#define IDC_STATIC_PERCENT 1213
+#define IDC_STATIC_RECOVERED 1214
+#define IDC_PROGRESS_PERCENT 1215
+#define IDC_STATIC_LOST 1216
+#define ID_ADD_FILES 1274
+#define IDC_OP 1275
+#define IDC_CHILDFRAME 1277
+#define IDC_BUTTON_EDITDATETIME 1278
+#define IDC_STATIC_CURDATETIME 1279
+#define IDC_CONFMETA 1288
+#define IDC_STATIC1 1293
+#define IDC_STATIC2 1294
+#define IDC_BUTTON_MIX 1304
+#define IDC_MIXABLE 1305
+#define IDC_CHECK_ATF 1311
+#define IDC_COMBO_FILTER 1312
+#define IDC_REFRESHMETADATA_STATUS 1314
+#define IDC_ALBUMGAIN 1315
+#define IDC_ARTIST_AS_ALBUMARTIST 1316
+#define IDC_FILTERS0 1317
+#define IDC_RADIO_FILTERS0 1317
+#define IDC_STATIC_FILTER2 1318
+#define IDC_RADIO_SIMPLE 1320
+#define IDC_IMAGE_SIMPLEALBUM 1321
+#define IDC_IMAGE_TWOFILTERS 1322
+#define IDC_IMAGE_THREEFILTERS 1323
+#define IDC_STATIC_SIMPLE 1324
+#define IDC_STATIC_SIMPLEALBUM 1325
+#define IDC_STATIC_TWOFILTERS 1326
+#define IDC_STATIC_THREEFILTERS 1327
+#define IDC_RADIO_SIMPLEALBUM 1328
+#define IDC_HIDE_EXTINFO 1329
+#define IDC_COMBO_PRESETS 1330
+#define IDC_STATIC_INFO 1331
+#define IDC_CHECK_SHOWINFO 1332
+#define IDC_STATIC_FILTER 1333
+#define IDC_IMAGE_SIMPLE 1334
+#define IDC_AND 1335
+#define IDC_OR 1336
+#define IDC_BUTTON_MODE 1337
+#define IDC_BUTTON_ARTMODE 1338
+#define IDC_BUTTON_VIEWMODE 1339
+#define IDC_BUTTON_COLUMNS 1340
+#define IDC_BTN_LINK_PROMO 1340
+#define IDC_SPIN1 1341
+#define IDC_IMPORT_ITUNES 1342
+#define IDC_CHECK_DIRECTOR 1343
+#define IDC_EDIT_DIRECTOR 1344
+#define IDC_CHECK_PRODUCER 1345
+#define IDC_EDIT_PRODUCER 1346
+#define IDC_STATIC_QUERYDELAY 1347
+#define IDC_CHECK_PODCAST 1347
+#define IDC_EDIT_QUERYDELAY 1348
+#define IDC_EDIT_PODCAST_CHANNEL 1348
+#define IDC_ERROR_1 1349
+#define IDC_CHECK_PODCAST_CHANNEL 1349
+#define IDC_RESET_DB_ON_ERROR 1352
+#define IDC_DB_ERROR 1353
+#define IDC_COMBO_RATING 1354
+#define IDS_SUBSTANTIVES 2048
+#define IDS_PLAY_ENQ_RND_ALTERNATIVE 2049
+#define ID_MEDIAWND_PLAYSELECTEDFILES 40001
+#define ID_MEDIAWND_ENQUEUESELECTEDFILES 40002
+#define ID_MEDIAWND_REMOVEFROMLIBRARY 40003
+#define ID_AUDIOWND_PLAYSELECTION 40004
+#define ID_AUDIOWND_ENQUEUESELECTION 40005
+#define ID_MEDIAWND_SELECTALL 40006
+#define ID_QUERYWND_EDIT 40007
+#define ID_QUERYWND_DELETE 40008
+#define ID_QUERYMENU_ADDNEWQUERY 40009
+#define ID_QUERYWND_PLAYQUERY 40010
+#define ID_QUERYWND_ENQUEUEQUERY 40011
+#define ID_EDITITEMINFOS 40012
+#define ID_MEDIAWND_ADDTOPLAYLIST 40033
+#define ID_MEDIAWND_EXPLOREFOLDER 40037
+#define ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES 40038
+#define ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS 40039
+#define ID_HEADERWND_CUSTOMIZECOLUMNS 40074
+#define ID_RATE_1 40075
+#define ID_RATE_2 40076
+#define ID_RATE_3 40077
+#define ID_RATE_4 40078
+#define ID_RATE_5 40079
+#define ID_RATE_0 40080
+#define IDC_REFRESH_METADATA 40098
+#define ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR 40099
+#define ID_ARTHEADERWND_SMALLICON 40100
+#define ID_ARTHEADERWND_MEDIUMICON 40101
+#define ID_ARTHEADERWND_LARGEICON 40102
+#define ID_ARTHEADERWND_MEDIUMDETAILS 40104
+#define ID_ARTHEADERWND_LARGEDETAILS 40105
+#define ID_ARTHEADERWND_SMALLDETAILS 40106
+#define ID_AUDIOWND_PLAYRANDOMITEM 40108
+#define ID_AUDIOWND_ENQUEUERANDOMITEM 40109
+#define ID_QUERYMENU_PREFERENCES 40110
+#define ID_QUERYMENU_HELP 40111
+#define ID_ARTHEADERWND_EXTRALARGEICON 40112
+#define ID_ARTHEADERWND_SHOWTEXT 40113
+#define ID_PE_ID3 40208
+#define IDS_NULLSOFT_LOCAL_MEDIA 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 281
+#define _APS_NEXT_COMMAND_VALUE 40129
+#define _APS_NEXT_CONTROL_VALUE 1355
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp b/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp
new file mode 100644
index 00000000..2688e2e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/icn_columns.bmp b/Src/Plugins/Library/ml_local/resources/icn_columns.bmp
new file mode 100644
index 00000000..46be6b46
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/icn_columns.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp b/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp
new file mode 100644
index 00000000..60f52169
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_simple.bmp b/Src/Plugins/Library/ml_local/resources/nf_simple.bmp
new file mode 100644
index 00000000..eaf5b9aa
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_simple.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp b/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp
new file mode 100644
index 00000000..1d94f00a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp b/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp
new file mode 100644
index 00000000..4af1ba08
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp b/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp
new file mode 100644
index 00000000..2aed6d4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/notfound.png b/Src/Plugins/Library/ml_local/resources/notfound.png
new file mode 100644
index 00000000..f76c0516
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/notfound.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp
new file mode 100644
index 00000000..0fb45741
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp
new file mode 100644
index 00000000..0d235d66
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp
new file mode 100644
index 00000000..d299fda8
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp
new file mode 100644
index 00000000..428d5b50
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp
new file mode 100644
index 00000000..225e45cc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp
new file mode 100644
index 00000000..4ec00ef0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp
new file mode 100644
index 00000000..476bde34
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp
new file mode 100644
index 00000000..63b5036f
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp
new file mode 100644
index 00000000..8fccdeef
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/util.cpp b/Src/Plugins/Library/ml_local/util.cpp
new file mode 100644
index 00000000..8ac23b8d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/util.cpp
@@ -0,0 +1,77 @@
+#include "main.h"
+#include "../replicant/nu/ns_wc.h"
+#include "ml_local.h"
+#include <string.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+
+extern "C" {
+
+void process_substantives(wchar_t* dest)
+{
+ if(!substantives)
+ {
+ wchar_t *b = dest;
+ while (!IsCharSpaceW(*b) && *b) b++;
+ while (IsCharSpaceW(*b) && *b) b++;
+ while (!IsCharSpaceW(*b) && *b) b++;
+ CharLowerW(b);
+ }
+}
+
+HRESULT ResolveShortCut(HWND hwnd, LPCWSTR pszShortcutFile, LPWSTR pszPath)
+{
+ IShellLink* psl = 0;
+ WIN32_FIND_DATA wfd = {0};
+
+ *pszPath = 0; // assume failure
+
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+ IID_IShellLink, (void **) &psl);
+ if (SUCCEEDED(hres))
+ {
+ IPersistFile* ppf;
+
+ hres = psl->QueryInterface(IID_IPersistFile, (void **) &ppf); // OLE 2! Yay! --YO
+ if (SUCCEEDED(hres))
+ {
+ wchar_t wsz[MAX_PATH] = {0};
+
+ hres = ppf->Load(wsz, STGM_READ);
+ if (SUCCEEDED(hres))
+ {
+ hres = psl->Resolve(hwnd, SLR_ANY_MATCH);
+ if (SUCCEEDED(hres))
+ {
+ wchar_t szGotPath[MAX_PATH] = {0};
+ wcsncpy(szGotPath, pszShortcutFile, MAX_PATH);
+ hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA *)&wfd, SLGP_SHORTPATH );
+ wcsncpy(pszPath, szGotPath, MAX_PATH);
+ }
+ }
+ ppf->Release();
+ }
+ psl->Release();
+ }
+ return SUCCEEDED(hres);
+}
+
+void ConvertRatingMenuStar(HMENU menu, UINT menu_id)
+{
+ MENUITEMINFOW mi = {sizeof(mi), MIIM_DATA | MIIM_TYPE, MFT_STRING};
+ wchar_t rateBuf[32], *rateStr = rateBuf;
+ mi.dwTypeData = rateBuf;
+ mi.cch = 32;
+ if(GetMenuItemInfoW(menu, menu_id, FALSE, &mi))
+ {
+ while(rateStr && *rateStr)
+ {
+ if(*rateStr == L'*') *rateStr = L'\u2605';
+ rateStr=CharNextW(rateStr);
+ }
+ SetMenuItemInfoW(menu, menu_id, FALSE, &mi);
+ }
+}
+
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/version.rc2 b/Src/Plugins/Library/ml_local/version.rc2
new file mode 100644
index 00000000..104d3186
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 3,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", "3,35,0,0"
+ VALUE "InternalName", "Nullsoft Library"
+ VALUE "LegalCopyright", "Copyright © 2002-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_local.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_local/view_audio.cpp b/Src/Plugins/Library/ml_local/view_audio.cpp
new file mode 100644
index 00000000..a2068ffa
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_audio.cpp
@@ -0,0 +1,2403 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include "../nu/listview.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "../nde/nde_c.h"
+#include <shlwapi.h>
+#include "SimpleFilter.h"
+#include "AlbumFilter.h"
+#include "AlbumArtFilter.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../nu/CGlobalAtom.h"
+#include <tataki/export.h>
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+
+static CGlobalAtom PROPW_DIVDATA(L"DIVDATA");
+#define DELIMSTR "|"
+#define LDELIMSTR L"|"
+
+#define MAX_FILTERS 3
+
+ViewFilter *filter[MAX_FILTERS] = {0};
+
+int numFilters = 0;
+extern void queryStrEscape(const char *raw, GayString &str);
+extern void FixAmps(wchar_t *str, size_t size);
+static W_ListView m_list1;
+static W_ListView m_list2;
+static W_ListView m_list3;
+static HWND m_media_hwnd;
+static HWND m_hwnd;
+GayStringW l_query;
+static int IPC_LIBRARY_SENDTOMENU;
+static int m_sort_by, m_sort_dir, m_sort_which;
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+enum
+{
+ PLAY=0,
+ ENQUEUE=1,
+};
+
+#define ARTIST 0x01
+#define ALBUMARTIST 0x02
+#define GENRE 0x03
+#define PUBLISHER 0x04
+#define COMPOSER 0x05
+#define ALBUM 0x06
+#define YEAR 0x07
+#define ARTISTINDEX 0x08
+#define ALBUMARTISTINDEX 0x09
+#define PODCASTCHANNEL 0x0A
+#define ALBUMART 0x0B
+#define CATEGORY 0x0C
+#define DIRECTOR 0x0D
+#define PRODUCER 0x0E
+
+#define MAKEVIEW_3FILTER(a, b, c) (a | (b << 8) | (c << 16))
+#define MAKEVIEW_2FILTER(a, b) (a | (b << 8))
+
+static ViewFilter * getFilter(int n, HWND hwndDlg, int dlgitem, C_Config *c)
+{
+ switch (n)
+ {
+ case 0: return new ArtistFilter();
+ case 1: return new AlbumArtistFilter();
+ case 2: return new GenreFilter();
+ case 3: return new PublisherFilter();
+ case 4: return new ComposerFilter();
+ case 5: return new AlbumFilter();
+ case 6: return new YearFilter();
+ case 7: return new ArtistIndexFilter();
+ case 8: return new AlbumArtistIndexFilter();
+ case 9: return new PodcastChannelFilter();
+ case 10: return new AlbumArtFilter(hwndDlg,dlgitem,c);
+ case 11: return new CategoryFilter();
+ case 12: return new DirectorFilter();
+ case 13: return new ProducerFilter();
+ }
+ return new ArtistFilter();
+}
+
+const wchar_t *getFilterName(unsigned int filterId, wchar_t *buffer, size_t bufferSize) // for config
+{
+ const int filterNames[] =
+ {
+ IDS_ARTIST,
+ IDS_ALBUM_ARTIST,
+ IDS_GENRE,
+ IDS_PUBLISHER,
+ IDS_COMPOSER,
+ IDS_ALBUM,
+ IDS_YEAR,
+ IDS_ARTIST_INDEX,
+ IDS_ALBUM_ARTIST_INDEX,
+ IDS_PODCAST_CHANNEL,
+ IDS_ALBUM_ART,
+ IDS_CATEGORY,
+ IDS_DIRECTOR,
+ IDS_PRODUCER,
+ };
+
+ if (filterId >= ARRAYSIZE(filterNames))
+ return NULL;
+
+ return WASABI_API_LNGSTRINGW_BUF(filterNames[filterId], buffer, bufferSize);
+}
+
+static void mysearchCallbackAlbumUpdate(itemRecordW *items, int numitems, int user32, int *killswitch)
+{
+ if (killswitch && *killswitch) return;
+
+ // user32 specifies whether or not to bother with rebuilding artist list
+ if (user32 == 2 && numFilters == 2) return;
+
+ if (user32 <= 2 && numFilters >= 3)
+ filter[2]->Fill(items,numitems,killswitch,0);
+
+ if (user32 <= 1 && numFilters >= 2)
+ filter[1]->Fill(items,numitems,killswitch,numFilters >= 3 ? filter[2]->numGroups : 0);
+
+ if (user32 == 0 && numFilters >= 1)
+ filter[0]->Fill(items, numitems, killswitch,numFilters >= 2 ? filter[1]->numGroups : 0);
+
+ PostMessage(m_hwnd, WM_APP + 3, 0x69, user32);
+}
+
+static int makeFilterQuery(GayStringW *query, ViewFilter *f)
+{
+ int ret = 0;
+ if (!(f->list->GetSelected(0) && f->HasTopItem()))
+ {
+ int c = f->list->GetCount();
+ int selCount = SendMessageW(f->list->getwnd(), LVM_GETSELECTEDCOUNT, 0, 0L);
+
+ if (selCount && ((f->HasTopItem()) ? selCount < (c - 1) : selCount < c))
+ {
+ int needor = 0, needclose = 0;
+ for (int x = f->HasTopItem()?1:0; x < c; x ++)
+ {
+ if (f->list->GetSelected(x))
+ {
+ if (needor)
+ query->Append(L"|");
+ else
+ {
+ if (query->Get()[0])
+ query->Append(L"&(");
+ else
+ query->Set(L"(");
+ needclose = 1;
+ }
+ if (!f->MakeFilterQuery(x,query))
+ {
+ const wchar_t *val = f->GetText(x);
+ if (val && *val)
+ {
+ query->Append(f->GetField());
+ query->Append(L" ");
+ query->Append(f->GetComparisonOperator());
+ query->Append(L" \"");
+ GayStringW escaped;
+ queryStrEscape(val, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\"");
+ }
+ else
+ {
+ const wchar_t *field = f->GetField();
+ query->Append(field);
+ query->Append(L" = \"\" | ");
+ query->Append(field);
+ query->Append(L" ISEMPTY");
+ }
+ }
+ ret++;
+ needor = 1;
+ }
+ }
+ if (needclose) query->Append(L")");
+ }
+ }
+ return ret;
+}
+
+static void getParentPlusSearchQuery(GayStringW *gs)
+{
+ extern wchar_t* m_query;
+ wchar_t *parq = m_query;
+
+ if (parq && parq[0])
+ {
+ gs->Set(L"(");
+ gs->Append(parq);
+ gs->Append(L")");
+ }
+ else gs->Set(L"");
+
+ GayStringW query;
+ wchar_t buf[2048] = {0};
+ GetWindowTextW(GetDlgItem(m_hwnd, IDC_QUICKSEARCH), buf, ARRAYSIZE(buf));
+ if (buf[0]) makeQueryStringFromText(&query, buf);
+
+ if (query.Get() && query.Get()[0])
+ {
+ if (gs->Get()[0])
+ {
+ gs->Append(L" & (");
+ gs->Append(query.Get());
+ gs->Append(L")");
+ }
+ else gs->Set(query.Get());
+ }
+}
+
+
+static void playList(int enqueue, int pane)
+{
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+
+ for (int i=0; i<=pane; i++)
+ makeFilterQuery(&query,filter[i]);
+
+ if (!g_table) return;
+
+ main_playQuery(g_view_metaconf, query.Get(), enqueue);
+}
+
+static void playRandomList(int enqueue, int pane)
+{
+ static int inited = 0;
+ if (!inited)
+ srand((unsigned)(time(0)));
+ inited = 1;
+ int n;
+ n = filter[pane]->list->GetCount() * rand()/RAND_MAX;
+ // Martin> dunno if we should display the results in ML then? atm we are
+ filter[pane]->list->UnselectAll();
+ filter[pane]->list->SetSelected(n);
+ filter[pane]->list->ScrollTo(n);
+ playList(enqueue, pane);
+}
+
+static void buildRecordListW(itemRecordListW *obj, int pane)
+{
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+
+ for (int i=0; i<=pane; i++)
+ makeFilterQuery(&query,filter[i]);
+
+ if (!g_table) return;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query.Get());
+ saveQueryToListW(g_view_metaconf, s, obj, 0, 0, (resultsniff_funcW)-1);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+}
+
+void UpdateRating_RowCache(const wchar_t *filename, int new_rating);
+static bool rateList(int rate)
+{
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ int valid = 0;
+ for (int i=0; i<numFilters; i++)
+ valid += makeFilterQuery(&query,filter[i]);
+
+ if (valid)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query.Get());
+ NDE_Scanner_First(s);
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+ // get filename here as calling later fails due to the edit for some reason
+ // as was being called directly in the updateFileInfo(..) call previously..
+ wchar_t *filename = NDE_StringField_GetString(f);
+ if (!filename) break;
+ // update before, so we can take advantage of row cache pointer equality (sometimes)
+ UpdateRating_RowCache(filename, rate);
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, rate);
+ NDE_Scanner_Post(s);
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (rate > 0)
+ {
+ wsprintfW(buf, L"%d", rate);
+ }
+ else
+ buf[0] = 0;
+
+ updateFileInfo(filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+
+ g_table_dirty++;
+ }
+ while (NDE_Scanner_Next(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty) NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+
+ // refresh media list
+// SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)0, (LPARAM)0);
+ return true;
+ }
+ return false;
+}
+
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+typedef void (WINAPI *DIVIDERMOVED)(HWND, INT, LPARAM);
+typedef struct _DIVIDER
+{
+ BOOL fVertical;
+ DIVIDERMOVED callback;
+ LPARAM param;
+ WNDPROC fnOldProc;
+ BOOL fUnicode;
+ INT clickoffs;
+
+} DIVIDER;
+
+BOOL AttachDivider(HWND hwnd, BOOL fVertical, DIVIDERMOVED callback, LPARAM param)
+{
+ if (!hwnd) return FALSE;
+
+ DIVIDER *pd = (DIVIDER*)calloc(1, sizeof(DIVIDER));
+ if (!pd) return FALSE;
+
+ pd->fUnicode = IsWindowUnicode(hwnd);
+ pd->fnOldProc = (WNDPROC)((pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc) :
+ SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc));
+ if (!pd->fnOldProc || !SetPropW(hwnd, PROPW_DIVDATA, pd))
+ {
+ free(pd);
+ return FALSE;
+ }
+ pd->fVertical = fVertical;
+ pd->param = param;
+ pd->callback = callback;
+
+ return TRUE;
+}
+#define GET_DIVIDER(hwnd) (DIVIDER*)GetPropW(hwnd, PROPW_DIVDATA)
+
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DIVIDER *pd;
+ pd = GET_DIVIDER(hwnd);
+ if (!pd) return (IsWindowUnicode(hwnd)) ? DefWindowProcW(hwnd, uMsg, wParam, lParam) : DefWindowProcA(hwnd, uMsg, wParam, lParam);
+
+ switch (uMsg)
+ {
+ case WM_DESTROY:
+ RemovePropW(hwnd, PROPW_DIVDATA);
+ (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+ (pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc) : SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc);
+ free(pd);
+ return 0;
+ case WM_LBUTTONDOWN:
+ pd->clickoffs = (pd->fVertical) ? LOWORD(lParam) : HIWORD(lParam);
+ SetCapture(hwnd);
+ break;
+ case WM_LBUTTONUP:
+ ReleaseCapture();
+ break;
+ case WM_SETCURSOR:
+ SetCursor(LoadCursor(NULL, (pd->fVertical) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+
+ case WM_MOUSEMOVE:
+ {
+ RECT rw;
+ GetWindowRect(hwnd, &rw);
+ GetCursorPos(((LPPOINT)&rw) + 1);
+ (pd->fVertical) ? rw.right -= pd->clickoffs : rw.bottom -= pd->clickoffs;
+
+ if ((pd->fVertical && rw.left != rw.right) || (!pd->fVertical && rw.top != rw.bottom))
+ {
+ MapWindowPoints(HWND_DESKTOP, GetParent(hwnd), ((LPPOINT)&rw) + 1, 1);
+ if (pd->callback) pd->callback(hwnd, (pd->fVertical) ? rw.right : rw.bottom, pd->param);
+ }
+ }
+ break;
+ }
+
+ return (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+}
+
+static int m_nodrawtopborders;
+static int adiv1_nodraw, adiv2_nodraw;
+
+#define DIVIDER_FILTER 0x1
+#define DIVIDER_FILTER2 0x3
+#define DIVIDER_MEDIA 0x2
+
+static int div_filterpos, div_filter2pos, div_mediapos;
+static BOOL g_displaysearch = TRUE;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+ BOOL *pHidden;
+}LAYOUT, PLAYOUT;
+
+typedef struct _LCTRL
+{
+ INT id;
+ BOOL hidden;
+} LCTRL;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if (fVis && (_layout->cx < 1 || _layout->cy < 1)) { _layout->flags |= SWP_HIDEWINDOW; *_layout->pHidden = TRUE; } \
+ if (!fVis && *_layout->pHidden && _layout->cx > 0 && _layout->cy > 0) { _layout->flags |= SWP_SHOWWINDOW; *_layout->pHidden = FALSE; } \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x3
+#define GROUP_SEARCH 0x1
+#define GROUP_FILTER 0x2
+#define GROUP_MEDIA 0x3
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw)
+{
+ static LCTRL controls[] =
+ {
+ {GROUP_SEARCH, FALSE}, {IDC_BUTTON_ARTMODE, FALSE}, {IDC_BUTTON_VIEWMODE, FALSE}, {IDC_BUTTON_COLUMNS, FALSE}, {IDC_SEARCHCAPTION, FALSE}, {IDC_CLEAR, FALSE}, {IDC_QUICKSEARCH, FALSE},
+ {GROUP_MEDIA, FALSE}, {IDC_HDELIM, FALSE}, {IDD_VIEW_MEDIA, FALSE},
+ {GROUP_FILTER, FALSE}, {IDC_LIST1, FALSE}, {IDC_DIV1, FALSE}, {IDC_LIST2, FALSE}, {IDC_DIV2, FALSE}, {IDC_LIST3, FALSE}
+ };
+
+ INT index, divY, divX, divX2;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.bottom == rc.top || rc.right == rc.left) return;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ divY = (div_mediapos * (rc.bottom- rc.top)) / 100000;
+ divX = numFilters==1? rc.right : (div_filterpos * (rc.right- rc.left)) / 100000;
+ divX2 = numFilters==3? (div_filter2pos * (rc.right- rc.left)) / 100000 : rc.right;
+
+ if (divX > divX2 && numFilters==3)
+ {
+ div_filterpos=33333;
+ div_filter2pos=66667;
+ divX = (div_filterpos * (rc.right- rc.left)) / 100000;
+ divX2 = (div_filter2pos * (rc.right- rc.left)) / 100000;
+ }
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(LCTRL); index++)
+ {
+ if (controls[index].id >= GROUP_MIN && controls[index].id <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index].id)
+ {
+ case GROUP_SEARCH:
+ if (g_displaysearch)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_CLEAR);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaysearch;
+ break;
+
+ case GROUP_MEDIA:
+ m_nodrawtopborders = 0;
+ if (divY > (rc.bottom - WASABI_API_APP->getScaleY(85)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_HDELIM), &rw);
+ divY = rc.bottom - (rw.bottom - rw.top);
+ }
+ else if (divY < (rc.top + WASABI_API_APP->getScaleY(24)))
+ {
+ divY = rc.top; m_nodrawtopborders = 1;
+ }
+
+ SetRect(&rg, rc.left, divY, rc.right, rc.bottom);
+ rc.bottom = rg.top;
+ break;
+
+ case GROUP_FILTER:
+ if (divX < (rc.left + WASABI_API_APP->getScaleX(15)))
+ {
+ divX = rc.left;
+ adiv1_nodraw = 1;
+ }
+ // fixes moving the dividers over to the far right so that the
+ // 'docking' is consistant in both the two and three pain view
+ else if (divX > (rc.right - (WASABI_API_APP->getScaleX(17)*(numFilters-1)+(numFilters==2?WASABI_API_APP->getScaleX(10):0))))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_DIV1), &rw);
+ divX = rc.right - (rw.right - rw.left)*(numFilters-1);
+ adiv1_nodraw = 2;
+ }
+ else adiv1_nodraw = 0;
+
+ if (divX2 < (rc.left + WASABI_API_APP->getScaleX(15)*(numFilters-2)))
+ {
+ divX2 = rc.left;
+ adiv2_nodraw = 1;
+ }
+ else if (divX2 > (rc.right - WASABI_API_APP->getScaleX(18*2)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_DIV2), &rw);
+ divX2 = rc.right - (rw.right - rw.left);
+ adiv2_nodraw = 2;
+ }
+ else adiv2_nodraw = 0;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index].id;
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ pl->pHidden = &controls[index].hidden;
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_BUTTON_ARTMODE:
+ if(numFilters == 1)
+ {
+ SETLAYOUTPOS(pl, 0, 0, 0, 0);
+ break;
+ }
+ case IDC_BUTTON_VIEWMODE:
+ case IDC_BUTTON_COLUMNS:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, (ri.right - ri.left), (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(1));
+ break;
+ case IDC_SEARCHCAPTION:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(6),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ rg.left += (pl->cx + WASABI_API_APP->getScaleX(8));
+ break;
+ }
+ case IDC_CLEAR:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_QUICKSEARCH:
+ pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_LIST1:
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(1),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ (numFilters == 1 ? rg.right - rg.left - WASABI_API_APP->getScaleX(3) : divX),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ rg.left += pl->cx;
+ break;
+ case IDC_DIV1:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), ri.right - ri.left, (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST2:
+ if (numFilters == 3)
+ {
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), divX2 - rg.left, (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ }
+ else
+ {
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ }
+ rg.left += pl->cx;
+ break;
+ case IDC_DIV2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), ri.right - ri.left, (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST3:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_HDELIM:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(2), (ri.bottom - ri.top));
+ rg.top += pl->cy;
+ break;
+ case IDD_VIEW_MEDIA:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if ((SWP_HIDEWINDOW & pl->flags) && !IsWindowVisible(pl->hwnd)) continue;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (IDD_VIEW_MEDIA == pc->id)
+ {
+ SetRect(&ri, pc->x, pc->y, pc->x + pc->cx, pc->y + pc->cy);
+ ValidateRect(hwnd, &ri);
+ pc->rgn = CreateRectRgn(0, 0, pc->cx, pc->cy);
+ GetWindowRect(pc->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 1);
+ SendMessage(pc->hwnd, WM_USER + 0x201, MAKEWPARAM(offsetX + (pc->x - ri.left), offsetY + (pc->y - ri.top)), (LPARAM)pc->rgn);
+ }
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ switch (pc->id)
+ {
+ case IDD_VIEW_MEDIA:
+ SendMessage(pc->hwnd, WM_USER + 0x201, 0, 0L);
+ break;
+ }
+ }
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE | RDW_NOINTERNALPAINT | RDW_ALLCHILDREN);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void WINAPI OnDividerMoved(HWND hwnd, INT nPos, LPARAM param)
+{
+ RECT rc;
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ if (hwndParent)
+ {
+ GetClientRect(hwndParent, &rc);
+ switch ((INT)param)
+ {
+ case DIVIDER_FILTER:
+ div_filterpos = (nPos * 100000) / (rc.right - rc.left);
+ if (div_filterpos + 500 > div_filter2pos) div_filter2pos = div_filterpos+500;
+ break;
+ case DIVIDER_FILTER2:
+ div_filter2pos = (nPos * 100000) / (rc.right - rc.left);
+ if (div_filter2pos - 500 < div_filterpos) div_filterpos = div_filter2pos-500;
+ break;
+ case DIVIDER_MEDIA:
+ div_mediapos = (nPos * 100000) / (rc.bottom - rc.top);
+ break;
+ }
+ LayoutWindows(hwndParent, TRUE);
+ }
+}
+
+static int ignore_selections;
+
+//returns true if a selection has been set which needs to be propagated to the next pane
+static bool UpdateFilterPane(int pane, char* selconfigname)
+{
+ filter[pane]->SortResults(g_view_metaconf, pane, 0);
+
+ ListView_SetItemCount(filter[pane]->list->getwnd(), filter[pane]->Size());
+ InvalidateRect(filter[pane]->list->getwnd(), NULL, TRUE);
+
+ ignore_selections = 1;
+ char *_selstr = g_config->ReadInt(L"remembersearch", 0) ? g_view_metaconf->ReadString(selconfigname, "") : NULL;
+ wchar_t * selstr = AutoWide(_selstr, CP_UTF8);
+ int a = 0;
+ if (selstr && *selstr)
+ {
+ int len = (wcslen(selstr) + 2);
+ wchar_t *t = (wchar_t*)calloc(len, sizeof(wchar_t));
+ wcsncpy(t, selstr, len);
+ g_view_metaconf->WriteString(selconfigname, "");
+
+ t[wcslen(t)+1] = 0;
+ wchar_t *outp = t;
+ wchar_t *inp = t;
+ while (inp && *inp)
+ {
+ if (*inp == *LDELIMSTR)
+ {
+ if (inp[1] == *LDELIMSTR)
+ {
+ *outp++ = *LDELIMSTR; // unescape the output
+ inp += 2;
+ }
+ else
+ {
+ *outp++ = 0; inp++;
+ }
+ }
+ else *outp++ = *inp++;
+ }
+ *outp = 0;
+
+ int x;
+ for (x = 1; x < filter[pane]->Size(); x ++)
+ {
+ wchar_t *p = t;
+ while (p && *p)
+ {
+ if (!lstrcmpiW(filter[pane]->GetText(x), p)) break;
+ p += wcslen(p) + 1;
+ }
+ if (p && *p)
+ {
+ if (!a)
+ {
+ ListView_SetSelectionMark(filter[pane]->list->getwnd(), x);
+ ListView_SetItemState(filter[pane]->list->getwnd(), x, LVIS_FOCUSED, LVIS_FOCUSED);
+ ListView_EnsureVisible(filter[pane]->list->getwnd(), x, NULL);
+ }
+ a++;
+ filter[pane]->list->SetSelected(x);
+ }
+ }
+ free(t);
+ }
+ if (a)
+ {
+ ignore_selections = 0;
+ return true;
+ }
+ else
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, buf, sizeof(buf));
+ if (buf[0])
+ {
+ ignore_selections = 0;
+ l_query.Set(L""); // force refresh
+ }
+ if(filter[pane]->HasTopItem())
+ filter[pane]->list->SetSelected(0);
+ ignore_selections = 0;
+ return false;
+ }
+}
+
+HBITMAP ConvertTo24bpp(HBITMAP bmp, int bpp)
+{
+ HDC hdcMem, hdcMem2;
+ HBITMAP hbm24;
+ BITMAP bm;
+
+ GetObjectW(bmp, sizeof(BITMAP), &bm);
+
+ hdcMem = CreateCompatibleDC(0);
+ hdcMem2 = CreateCompatibleDC(0);
+
+ void *bits;
+ BITMAPINFOHEADER bi;
+
+ ZeroMemory(&bi, sizeof(bi));
+ bi.biSize = sizeof(bi);
+ bi.biWidth= bm.bmWidth;
+ bi.biHeight = -bm.bmHeight;
+ bi.biPlanes = 1;
+ bi.biBitCount= (WORD)bpp;
+
+ hbm24 = CreateDIBSection(hdcMem2, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, NULL);
+
+ HBITMAP oBmp = (HBITMAP)SelectObject(hdcMem, bmp);
+ HBITMAP oBmp24 = (HBITMAP)SelectObject(hdcMem2, hbm24);
+
+ BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
+
+ SelectObject(hdcMem, oBmp);
+ SelectObject(hdcMem2, oBmp24);
+
+ DeleteDC(hdcMem);
+ DeleteDC(hdcMem2);
+
+
+ return hbm24;
+}
+
+static __forceinline BYTE pm(int a, int b)
+{
+ return (BYTE)((a * b) / 0xff);
+}
+
+void SetToolbarButtonBitmap(HWND hwndDlg, int buttonrc, int bitmaprc, ARGB32 fc)
+{
+ // benski> we could use this if it wasn't for the lack of WASABI_API_IMGLDR on classic skin
+ // even though we have the resource loader ...
+ // SkinBitmap *sbm = new SkinBitmap( bitmaprc);
+ HBITMAP hbm1 = (HBITMAP)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(bitmaprc),IMAGE_BITMAP,0,0,LR_VGACOLOR);
+ HBITMAP hbm = ConvertTo24bpp(hbm1,32);
+ DeleteObject(hbm1);
+ BITMAP bm;
+ GetObjectW(hbm, sizeof(BITMAP), &bm);
+ int l = bm.bmWidth * bm.bmHeight;
+ ARGB32 *x = (ARGB32*)bm.bmBits;
+ ARGB32 *end = x+l;
+ BYTE r = (BYTE)(fc & 0x00ff0000 >> 16);
+ BYTE g = (BYTE)(fc & 0x0000ff00 >> 8);
+ BYTE b = (BYTE)(fc & 0x000000ff);
+ while (x < end)
+ {
+ BYTE a = (BYTE)((~(*x))&0xff);
+ *(x++) = (a<<24) | (pm(r,a)<<16) | (pm(g,a)<<8) | pm(b,a);
+ }
+
+ void * mem = malloc(bm.bmWidth*bm.bmHeight*sizeof(ARGB32));
+ memcpy(mem,bm.bmBits,bm.bmWidth*bm.bmHeight*sizeof(ARGB32));
+
+ SkinBitmap *sbm = new SkinBitmap((ARGB32*)mem,bm.bmWidth,bm.bmHeight);
+ DeleteObject(hbm);
+ HWND btn = GetDlgItem(hwndDlg,buttonrc);
+ if (IsWindow(btn))
+ {
+ SkinBitmap* old = (SkinBitmap*)SetWindowLongPtr(btn,GWLP_USERDATA,(LONG_PTR)sbm);
+ if (old)
+ {
+ if (old->getBits()) free(old->getBits()); delete old;
+ }
+ InvalidateRect(btn, NULL, TRUE);
+ }
+}
+
+int GetFilter(int mode, int n)
+{
+ if (n==0) return ((mode & 0x0000ff));
+ if (n==1)
+ {
+ int f=((mode & 0x00ff00) >> 8); if (f) return f; return 6;
+ } // album
+ if (n==2) return ((mode & 0xff0000) >> 16);
+ return 0;
+}
+
+int GetNumFilters(int mode)
+{
+ if (mode == 0) return 0;
+ if (mode > 0xffffff) return 1;
+ if (GetFilter(mode,2) == 0) return 2;
+ return 3;
+}
+
+typedef struct { int id, id2; } hi;
+
+//extern "C"
+//{
+//void __cdecl do_help(HWND hwnd, UINT id, HWND hTooltipWnd);
+
+void do_help(HWND hwnd, UINT id, HWND hTooltipWnd)
+{
+ RECT r;
+ POINT p;
+ GetWindowRect(GetDlgItem(hwnd, id), &r);
+ GetCursorPos(&p);
+ if (p.x >= r.left && p.x < r.right && p.y >= r.top && p.y < r.bottom)
+ {}
+ else
+ {
+ r.left += r.right;
+ r.left /= 2;
+ r.top += r.bottom;
+ r.top /= 2;
+ SetCursorPos(r.left, r.top);
+ }
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_INITIAL, 0);
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_RESHOW, 0);
+}
+
+#define C_BLAH
+#define DO_HELP() \
+ static HWND hTooltipWnd; \
+ C_BLAH \
+ if (uMsg == WM_HELP) { \
+ HELPINFO *hi=(HELPINFO *)(lParam); \
+ if (hi->iContextType == HELPINFO_WINDOW) { do_help(hwndDlg,hi->iCtrlId,hTooltipWnd);} \
+ return TRUE; \
+ } \
+ if (uMsg == WM_NOTIFY) { LPNMHDR t=(LPNMHDR)lParam; if (t->code == TTN_POP) { SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,1000); SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,1000); } } \
+ if (uMsg == WM_DESTROY && IsWindow(hTooltipWnd)) { DestroyWindow(hTooltipWnd); hTooltipWnd=NULL; } \
+ if (uMsg == WM_INITDIALOG) { \
+ int x; \
+ hTooltipWnd = CreateWindow(TOOLTIPS_CLASS,(LPCWSTR)NULL,TTS_ALWAYSTIP|TTS_NOPREFIX, \
+ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, hwndDlg,NULL,GetModuleHandle(NULL),NULL); \
+ SendMessage(hTooltipWnd,TTM_SETMAXTIPWIDTH,0,587); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,250); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,500); \
+ for (x = 0; x < sizeof(helpinfo)/sizeof(helpinfo[0]); x ++) { \
+ TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_SUBCLASS|TTF_IDISHWND; \
+ ti.uId=(UINT_PTR)GetDlgItem(hwndDlg,helpinfo[x].id); ti.hwnd=hwndDlg; ti.lpszText=WASABI_API_LNGSTRINGW(helpinfo[x].id2); \
+ SendMessage(hTooltipWnd,TTM_ADDTOOL,0,(LPARAM) &ti); \
+ } \
+ }
+//}
+
+static int refresh;
+INT_PTR CALLBACK view_audioDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static int we_are_drag_and_dropping;
+ static HMENU sendto_hmenu;
+ static librarySendToMenuStruct s;
+
+ hi helpinfo[]={
+ {IDC_BUTTON_ARTMODE,IDS_AUDIO_BUTTON_TT1},
+ {IDC_BUTTON_VIEWMODE,IDS_AUDIO_BUTTON_TT2},
+ {IDC_BUTTON_COLUMNS,IDS_AUDIO_BUTTON_TT3},
+ };
+ DO_HELP();
+
+ BOOL a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+ if (numFilters>=1)
+ {
+ a = filter[0]->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (numFilters>=2)
+ {
+ a = filter[1]->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (numFilters>=3)
+ {
+ a = filter[2]->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+
+ switch (uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (wParam)
+ {
+ if((HMENU)wParam == s.build_hMenu && s.mode == 1)
+ {
+ myMenu = TRUE;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ s.mode = 2;
+ myMenu = FALSE;
+ }
+ // only populate the average rating when needed otherwise it makes all of the menu slow
+ else if((HMENU)wParam == GetSubMenu(GetSubMenu(g_context_menus, 1),4))
+ {
+ int ave_rating = 0, valid = 0;
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ for (int i=0; i<numFilters; i++)
+ {
+ valid += makeFilterQuery(&query,filter[i]);
+ }
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t q = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(q, query.Get());
+ NDE_Scanner_First(q);
+ int count = 0;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(q, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+ nde_field_t g = NDE_Scanner_GetFieldByID(q, MAINTABLE_ID_RATING);
+ int nde_rating = NDE_IntegerField_GetValue(g);
+ if(nde_rating > 0) ave_rating += nde_rating;
+ count++;
+ }
+ while (NDE_Scanner_Next(q));
+
+ NDE_Table_DestroyScanner(g_table, q);
+ LeaveCriticalSection(&g_db_cs);
+
+ if(ave_rating > 0)
+ {
+ float ave = ave_rating/(count*1.0f);
+ int diff = ave_rating/count;
+ int adjust = (ave-diff)>=0.5f;
+ ave_rating = (int)(ave+adjust);
+ }
+
+ Menu_SetRatingValue((HMENU)wParam, (valid?ave_rating:-1));
+ }
+ }
+ return 0;
+ case WM_DISPLAYCHANGE:
+ {
+ ARGB32 fc = dialogSkinner.Color(WADLG_BUTTONFG) & 0x00FFFFFF;
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_ARTMODE,IDB_TOOL_ART,fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_VIEWMODE,IDB_TOOL_MODE,fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_COLUMNS,IDB_TOOL_COLS,fc);
+ if (IsWindow(m_media_hwnd)) SendNotifyMessageW(m_media_hwnd, WM_DISPLAYCHANGE, wParam, lParam);
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE);
+ }
+ return 0;
+ case WM_INITDIALOG:
+ {
+ hwndSearchGlobal = GetDlgItem(hwndDlg, IDC_QUICKSEARCH); // Set the hwnd for the search to a global so we can access it from the media view dialog
+ ResumeCache();
+ int numFilters0=GetNumFilters(m_query_mode);
+ int f1 = GetFilter(m_query_mode,0);
+ int f2 = GetFilter(m_query_mode,1);
+ int f3 = GetFilter(m_query_mode,2);
+
+ filter[0] = getFilter(f1-1,hwndDlg,IDC_LIST1,g_view_metaconf);
+ filter[0]->list = &m_list1;
+ filter[0]->nextFilter = NULL;
+ filter[0]->list->setwnd(GetDlgItem(hwndDlg, IDC_LIST1));
+
+ if (numFilters0>=2)
+ {
+ filter[1] = getFilter(f2-1,hwndDlg,IDC_LIST2,g_view_metaconf);
+ filter[1]->list = &m_list2;
+ filter[1]->list->setwnd(GetDlgItem(hwndDlg, IDC_LIST2));
+ filter[1]->nextFilter = NULL;
+ filter[0]->nextFilter = filter[1];
+ }
+
+ if (numFilters0>=3)
+ {
+ filter[2] = getFilter(f3-1,hwndDlg,IDC_LIST3,g_view_metaconf);
+ filter[2]->list = &m_list3;
+ filter[2]->list->setwnd(GetDlgItem(hwndDlg, IDC_LIST3));
+ filter[2]->nextFilter = NULL;
+ filter[1]->nextFilter = filter[2];
+ }
+ numFilters=numFilters0;
+
+ m_hwnd = hwndDlg;
+ getParentPlusSearchQuery(&l_query);
+
+ for (int j=0; j<numFilters0; j++)
+ {
+ filter[j]->list->ForceUnicode();
+ filter[j]->AddColumns();
+ }
+
+ div_mediapos = g_view_metaconf->ReadInt(L"adiv2pos", 50000);
+
+ if (numFilters == 1)
+ {
+ div_filterpos = g_view_metaconf->ReadInt(L"adiv1pos", 100000);
+ div_filter2pos = -1;
+ }
+ else if (numFilters == 2)
+ {
+ div_filterpos = g_view_metaconf->ReadInt(L"adiv1pos", 50000);
+ div_filter2pos = -1;
+ }
+ else if (numFilters == 3)
+ {
+ div_filterpos = g_view_metaconf->ReadInt(L"adiv1pos", 33333);
+ div_filter2pos = g_view_metaconf->ReadInt(L"adiv3pos", 66667);
+ if (div_filterpos + div_filter2pos > 200000) // sanity check?
+ {
+ div_filterpos = 33333;
+ div_filter2pos = 66667;
+ }
+ }
+
+ if (numFilters >= 2) AttachDivider(GetDlgItem(hwndDlg, IDC_DIV1), TRUE, OnDividerMoved, DIVIDER_FILTER);
+ if (numFilters >= 3) AttachDivider(GetDlgItem(hwndDlg, IDC_DIV2), TRUE, OnDividerMoved, DIVIDER_FILTER2);
+ AttachDivider(GetDlgItem(hwndDlg, IDC_HDELIM), FALSE, OnDividerMoved, DIVIDER_MEDIA);
+
+ m_media_hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_MEDIA, hwndDlg, view_mediaDialogProc, (LPARAM)!g_config->ReadInt(L"audiorefine", 0));
+ SetWindowLongPtr(m_media_hwnd, GWLP_ID, IDD_VIEW_MEDIA);
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+
+ m.hwndToSkin = m_list1.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ m.hwndToSkin = m_list2.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ if (numFilters == 3)
+ {
+ m.hwndToSkin = m_list3.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+
+ if (!g_view_metaconf->ReadInt(L"av0_hscroll",0)) MLSkinnedScrollWnd_ShowHorzBar(m_list1.getwnd(), FALSE);
+ if (!g_view_metaconf->ReadInt(L"av1_hscroll",0)) MLSkinnedScrollWnd_ShowHorzBar(m_list2.getwnd(), FALSE);
+ if (!g_view_metaconf->ReadInt(L"av2_hscroll",0) && numFilters == 3) MLSkinnedScrollWnd_ShowHorzBar(m_list3.getwnd(), FALSE);
+
+ FLICKERFIX ff;
+ ff.mode = FFM_ERASEINPAINT;
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ INT ffcl[] = { IDC_QUICKSEARCH, IDC_SEARCHCAPTION, IDC_CLEAR, IDC_BUTTON_ARTMODE, IDC_BUTTON_VIEWMODE, IDC_BUTTON_COLUMNS};
+ for (int i = 0; i < sizeof(ffcl) / sizeof(INT); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(mediaLibrary.library, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+ if (i < 3)
+ {
+ m.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ }
+ }
+
+ SendMessageW(hwndDlg, WM_DISPLAYCHANGE, 0, 0L);
+
+ // clear the media windows refine shit
+ SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ SetDlgItemText(m_media_hwnd, IDC_SEARCHCAPTION, WASABI_API_LNGSTRINGW(IDS_REFINE));
+ SetDlgItemText(m_media_hwnd, IDC_CLEAR, WASABI_API_LNGSTRINGW(IDS_CLEAR_REFINE));
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ if (g_config->ReadInt(L"remembersearch", 0))
+ {
+ char *query = g_view_metaconf->ReadString("lastquery_a_utf8", "");
+ SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, AutoWide(query, CP_UTF8));
+ KillTimer(hwndDlg, 205);
+ }
+ for (int j=0; j<numFilters; j++)
+ filter[j]->SortResults(g_view_metaconf, j, 0);
+
+ {
+ wchar_t buf[32] = {0};
+ for (int i = 0; i < numFilters; i++)
+ {
+ int dlgitem = (i==0)?IDC_LIST1:((i==1)?IDC_LIST2:IDC_LIST3);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d", filter[i]->GetConfigId(), i);
+ int l_sc = g_view_metaconf->ReadInt(buf, 0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_dir_%d", filter[i]->GetConfigId(), i);
+ int l_sd = g_view_metaconf->ReadInt(buf, 0);
+ SendMessage((HWND)SendMessage(GetDlgItem(hwndDlg,dlgitem), LVM_GETHEADER, 0, 0L),WM_ML_IPC,MAKEWPARAM(l_sc,!l_sd),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ }
+ }
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ return TRUE;
+ }
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ if (di->CtlID == IDC_BUTTON_ARTMODE || di->CtlID == IDC_BUTTON_VIEWMODE || di->CtlID == IDC_BUTTON_COLUMNS)
+ {
+ // draw the toolbar buttons!
+ SkinBitmap* hbm = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,di->CtlID),GWLP_USERDATA);
+ if (hbm && di->rcItem.left != di->rcItem.right && di->rcItem.top != di->rcItem.bottom)
+ {
+ DCCanvas dc(di->hDC);
+ if (di->itemState & ODS_SELECTED) hbm->blitAlpha(&dc,di->rcItem.left+6,di->rcItem.top+5);
+ else hbm->blitAlpha(&dc,di->rcItem.left+4,di->rcItem.top+3);
+ }
+ return TRUE;
+ }
+ }
+ }
+ break;
+ case WM_TIMER:
+ if (wParam == 165) // list3 sel change
+ {
+ KillTimer(hwndDlg, 165);
+ if (numFilters < 3) break;
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ int r = makeFilterQuery(&query,filter[0]);
+ r += makeFilterQuery(&query,filter[1]);
+ r += makeFilterQuery(&query,filter[2]);
+
+ if (wcscmp(query.Get(), l_query.Get()))
+ {
+ bgQuery_Stop();
+ l_query.Set(query.Get());
+
+ wchar_t buf[2048] = {0};
+ GetDlgItemTextW(m_media_hwnd, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ if (buf[0]) makeQueryStringFromText(&query, buf);
+
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner) NDE_Scanner_Query(m_media_scanner, query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ if (r > 0) SendMessage(m_media_hwnd, WM_APP + 3, 0x666, 0); // notify the child to use the first item as a media lookup
+ SendMessage(m_media_hwnd, WM_APP + 1, 0, 0);
+ }
+ }
+ if (wParam == 166) // album sel change
+ {
+ KillTimer(hwndDlg, 166);
+
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ int r = makeFilterQuery(&query,filter[0]);
+ r += makeFilterQuery(&query,filter[1]);
+
+ if (wcscmp(query.Get(), l_query.Get()))
+ {
+ bgQuery_Stop();
+ l_query.Set(query.Get());
+
+ wchar_t buf[2048] = {0};
+ GetDlgItemTextW(m_media_hwnd, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ if (buf[0]) makeQueryStringFromText(&query, buf);
+
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner) NDE_Scanner_Query(m_media_scanner, query.Get());
+ LeaveCriticalSection(&g_db_cs);
+ if (r > 0) SendMessage(m_media_hwnd, WM_APP + 3, 0x666, 0); // notify the child to use the first item as a media lookup
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, 2);
+ }
+ }
+ if (wParam == 167) // artist sel change
+ {
+ KillTimer(hwndDlg, 167);
+
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+
+ int r = makeFilterQuery(&query,filter[0]);
+
+ if (wcscmp(query.Get(), l_query.Get()))
+ {
+ bgQuery_Stop();
+ l_query.Set(query.Get());
+
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ ListView_SetItemCount(m_list2.getwnd(), 0);
+ if (r > 0) SendMessage(m_media_hwnd, WM_APP + 3, 0x666, 0); // notify the child to use the first item as a media lookup
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)1); // pass a callback for sort
+ }
+ }
+ if (wParam == 205)
+ {
+ KillTimer(hwndDlg, 205);
+ bgQuery_Stop();
+ getParentPlusSearchQuery(&l_query);
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, l_query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ ignore_selections = 1;
+ m_list3.SetSelected(0);
+ m_list2.SetSelected(0);
+ m_list1.SetSelected(0);
+ ListView_SetItemCount(m_list1.getwnd(), 0);
+ ListView_SetItemCount(m_list2.getwnd(), 0);
+ ListView_SetItemCount(m_list3.getwnd(), 0);
+ ignore_selections = 0;
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)0);
+ refresh -= 1;
+ }
+ break;
+ case WM_MOUSEMOVE:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ POINT p;
+ p.x = GET_X_LPARAM(lParam);
+ p.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m;
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ m.p = p;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result <= 0)
+ {
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_QUERYSTRING;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ }
+
+ break;
+ }
+ break;
+
+ case WM_SETCURSOR:
+ case WM_LBUTTONDOWN:
+ {
+ static INT id[] = { IDC_HDELIM, IDC_DIV1, IDC_DIV2 };
+ RECT rw;
+ POINT pt;
+ INT count;
+
+ count = numFilters;
+ if (count > sizeof(id)/sizeof(INT)) count = sizeof(id)/sizeof(INT);
+
+ GetCursorPos(&pt);
+ for (INT i = 0; i < count; i++)
+ {
+ HWND hwndDiv = GetDlgItem(hwndDlg, id[i]);
+ if (!hwndDiv) continue;
+
+ GetWindowRect(hwndDiv, &rw);
+ if (PtInRect(&rw, pt))
+ {
+ if (WM_SETCURSOR == uMsg)
+ {
+ SetCursor(LoadCursor(NULL, (IDC_HDELIM != id[i]) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+ }
+ else
+ {
+ SendMessage(hwndDiv, uMsg, wParam, MAKELPARAM(pt.x - rw.left, pt.y - rw.top));
+ return TRUE;
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ int whichlist = we_are_drag_and_dropping;
+ we_are_drag_and_dropping = 0;
+ ReleaseCapture();
+
+ POINT p;
+ p.x = GET_X_LPARAM(lParam);
+ p.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_ITEMRECORDLISTW;
+ m.p = p;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ itemRecordListW obj = {0, };
+ buildRecordListW(&obj, whichlist-1);
+
+ if (m.result > 0)
+ {
+ /*
+ if (whichlist == 1 || m_list2.GetSelectionMark() <= 0)
+ buildRecordListW(&obj, 0);
+ else if(whichlist == 2 || m_list3.GetSelectionMark() <= 0 || numFilters < 3)
+ buildRecordListW(&obj,1);
+ else
+ buildRecordListW(&obj,2);
+ */
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & obj;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ }
+ else
+ {
+ m.result = 0;
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result > 0)
+ {
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &obj);
+
+ /*
+ if (whichlist == 1 || m_list2.GetSelectionMark() <= 0)
+ buildRecordList(&obj, 0);
+ else if(whichlist == 2 || m_list3.GetSelectionMark() <= 0 || numFilters < 3)
+ buildRecordList(&obj, 1);
+ else
+ buildRecordList(&obj, 2);
+ */
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & objA;
+ GayString namebuf;
+ int wl = whichlist;
+ ViewFilter * filter1 = filter[wl-1];
+ {
+ int a = filter1->list->GetSelectionMark();
+ if (a < 0 || (filter1->HasTopItem() && filter1->list->GetSelected(0)))
+ {
+ filter1 = filter[0]; wl--;
+ }
+
+ if (wl > 0)
+ {
+ int x;
+ int cnt = 0;
+ for (x = filter1->HasTopItem()?1:0; x < filter1->Size(); x ++)
+ {
+ if (filter1->list->GetSelected(x))
+ {
+ if (cnt) namebuf.Append(", ");
+ if (cnt++ > 2)
+ {
+ namebuf.Append("..."); break;
+ }
+ namebuf.Append(AutoChar(filter1->GetText(x)));
+ }
+ }
+ }
+ }
+ if (namebuf.Get() && namebuf.Get()[0]) m.name = namebuf.Get();
+ if (m.name)
+ m.flags |= ML_HANDLEDRAG_FLAG_NAME;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ freeRecordList(&objA);
+ }
+
+ else
+ {
+ m.result = 0;
+ m.type = ML_TYPE_QUERYSTRING;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result > 0)
+ {
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ bool allSelected[3] = {false,false,false};
+ for (int k=0; k<numFilters; k++)
+ {
+ W_ListView &w = k==0?m_list1:(k==1?m_list2:m_list3);
+ if (filter[k]->HasTopItem()) allSelected[k] = (w.GetSelectionMark() <= 0);
+ else allSelected[k] = (w.GetSelectionMark() < 0);
+ }
+
+ if (whichlist == 1 || allSelected[1])
+ {
+ makeFilterQuery(&query,filter[0]);
+ }
+ else if (whichlist == 2 || allSelected[2] || numFilters < 3)
+ {
+ makeFilterQuery(&query,filter[0]);
+ makeFilterQuery(&query,filter[1]);
+ }
+ else
+ {
+ makeFilterQuery(&query,filter[0]);
+ makeFilterQuery(&query,filter[1]);
+ makeFilterQuery(&query,filter[2]);
+ }
+
+ m.data = (void*)query.Get();
+ m.result = 0;
+ GayString namebuf;
+ int wl = whichlist;
+ ViewFilter * filter1 = wl==2?filter[1]:filter[0];
+ {
+ int a = filter1->list->GetSelectionMark();
+ if (a < 0 || (filter1->HasTopItem() && filter1->list->GetSelected(0)))
+ {
+ if (wl==2)
+ {
+ filter1 = filter[0];
+ }
+ else m.name = WASABI_API_LNGSTRING(IDS_ALL_ARTISTS);
+ wl--;
+ }
+ if (wl > 0)
+ {
+ int x;
+ int cnt = 0;
+ for (x = filter1->HasTopItem()?1:0; x < filter1->Size(); x ++)
+ {
+ if (filter1->list->GetSelected(x))
+ {
+ if (cnt) namebuf.Append(", ");
+ if (cnt++ > 2)
+ {
+ namebuf.Append("..."); break;
+ }
+ namebuf.Append(AutoChar(filter1->GetText(x)));
+ }
+ }
+ }
+ }
+ if (namebuf.Get() && namebuf.Get()[0])
+ {
+ m.name = namebuf.Get();
+ m.flags |= ML_HANDLEDRAG_FLAG_NAME;
+ }
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ }
+ }
+ }
+ freeRecordList(&obj);
+ }
+ break;
+ case WM_CONTEXTMENU:
+ {
+ HWND hwndFrom = (HWND)wParam;
+ int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
+ POINT pt = {x,y};
+ int idFrom = GetDlgCtrlID(hwndFrom);
+ HWND hHeader;
+ W_ListView m_list;
+ int editFilter=-1;
+
+ if (idFrom == IDC_LIST1)
+ {
+ m_list = m_list1;
+ hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ editFilter = 0;
+ }
+ }
+ }
+ else if (idFrom == IDC_LIST2)
+ {
+ m_list = m_list2;
+ hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ editFilter = 1;
+ }
+ }
+ }
+ else if (idFrom == IDC_LIST3)
+ {
+ m_list = m_list3;
+ hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ editFilter = 2;
+ }
+ }
+ }
+ else
+ break;
+
+ if (editFilter != -1)
+ {
+ HMENU themenu = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU menu = filter[editFilter]->GetMenu(true,editFilter,g_view_metaconf,themenu);
+ POINT p;
+ GetCursorPos(&p);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, hwndDlg, NULL);
+ DestroyMenu(themenu);
+ filter[editFilter]->ProcessMenuResult(r,true,editFilter,g_view_metaconf,hwndDlg);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ break;
+ }
+
+ int pane=0;
+ if (numFilters > 1)
+ {
+ if (idFrom == IDC_LIST2 && m_list2.GetSelectionMark() >= 0) pane=1;
+ }
+ if (numFilters > 2)
+ {
+ if (idFrom == IDC_LIST3 && m_list3.GetSelectionMark() >= 0) pane=2;
+ }
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = m_list.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ m_list.GetItemRect(selected, &itemRect);
+ ClientToScreen(m_list.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(m_list.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ break;
+ }
+
+ HMENU menu = GetSubMenu(g_context_menus, 1);
+ sendto_hmenu = GetSubMenu(menu, 2);
+
+ s.mode = 0;
+ s.hwnd = 0;
+ s.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ s.ctx[1] = 1;
+ s.build_hMenu = sendto_hmenu;
+ }
+
+ wchar_t str[128] = {0};
+ StringCchPrintfW(str, 128, (!play_enq_rnd_alt?WASABI_API_LNGSTRINGW(IDS_PLAY_RANDOM_ITEM):L"%s"),
+ filter[pane]->GetNameSingularAlt(play_enq_rnd_alt?1:0));
+ FixAmps(str, 128);
+ MENUITEMINFOW mii =
+ {
+ sizeof(MENUITEMINFOW),
+ MIIM_TYPE | MIIM_ID,
+ MFT_STRING,
+ MFS_ENABLED,
+ ID_AUDIOWND_PLAYRANDOMITEM,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ str,
+ 0,
+ };
+ SetMenuItemInfoW(menu, ID_AUDIOWND_PLAYRANDOMITEM, false, &mii);
+
+ StringCchPrintfW(str, 128, (!play_enq_rnd_alt?WASABI_API_LNGSTRINGW(IDS_ENQUEUE_RANDOM_ITEM):L"%s"),
+ filter[pane]->GetNameSingularAlt(play_enq_rnd_alt?2:0));
+ FixAmps(str, 128);
+ mii.wID = ID_AUDIOWND_ENQUEUERANDOMITEM;
+ SetMenuItemInfoW(menu, ID_AUDIOWND_ENQUEUERANDOMITEM, false, &mii);
+
+ filter[pane]->DialogProc(hwndDlg,WM_USER+600,(WPARAM)menu,0x7000);
+ UpdateMenuItems(NULL, menu, IDR_VIEW_ACCELERATORS);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL);
+
+ filter[pane]->DialogProc(hwndDlg,WM_USER+601,(WPARAM)menu,0x7000);
+
+ if(r >= 0x7000 && r <= 0x8000) {
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ for (int i=0; i<=pane; i++)
+ makeFilterQuery(&query,filter[i]);
+ filter[pane]->DialogProc(hwndDlg,WM_USER+602,(WPARAM)query.Get(),r - 0x7000);
+ }
+
+ switch (r)
+ {
+ case ID_AUDIOWND_PLAYSELECTION:
+ playList(PLAY,pane);
+ break;
+ case ID_AUDIOWND_ENQUEUESELECTION:
+ playList(ENQUEUE,pane);
+ break;
+ case ID_AUDIOWND_PLAYRANDOMITEM:
+ case ID_AUDIOWND_ENQUEUERANDOMITEM:
+ playRandomList(r == ID_AUDIOWND_PLAYRANDOMITEM ? PLAY : ENQUEUE, pane);
+ break;
+ case ID_RATE_1:
+ case ID_RATE_2:
+ case ID_RATE_3:
+ case ID_RATE_4:
+ case ID_RATE_5:
+ case ID_RATE_0:
+ {
+ int rate = r - ID_RATE_1 + 1;
+ if (r == ID_RATE_0) rate = 0;
+ if(rateList(rate))
+ {
+ wchar_t buf[10] = {0};
+ wsprintfW(buf,L"%d",rate);
+ int s = -1;
+ while((s = filter[pane]->list->GetNextSelected(s)) != -1)
+ filter[pane]->MetaUpdate(s, DB_FIELDNAME_rating,buf);
+ }
+ PostMessage(hwndDlg, WM_APP + 4, (WPARAM)rate, (LPARAM)0);
+ }
+ break;
+ default:
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ // build my data.
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLISTW;
+ itemRecordListW myObj = {0, };
+ buildRecordListW(&myObj, pane);
+
+ s.data = (void*) & myObj;
+ LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+
+ if (result != 1)
+ {
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &myObj);
+
+ s.data = (void*) & objA;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+ freeRecordList(&objA);
+ }
+ freeRecordList(&myObj);
+ }
+ }
+ break;
+ }
+ if (s.mode)
+ {
+ s.mode = 4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ sendto_hmenu = 0;
+ EatKeyboard();
+ return 0;
+ }
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+
+ if (l->code == LVN_ODFINDITEMW)
+ {
+ ViewFilter * f;
+ if (l->idFrom == IDC_LIST2) f = filter[1];
+ else if (l->idFrom == IDC_LIST3) f = filter[2];
+ else f = filter[0];
+
+ NMLVFINDITEMW *t = (NMLVFINDITEMW *)lParam;
+ int i = t->iStart;
+ if (i >= f->Size()) i = 0;
+
+ int cnt = f->Size() - i;
+ if (t->lvfi.flags & LVFI_WRAP) cnt += i;
+
+ while (cnt-- > 0)
+ {
+ const wchar_t *name = f->GetText(i);
+ SKIP_THE_AND_WHITESPACEW(name)
+
+ if (t->lvfi.flags & (4 | LVFI_PARTIAL))
+ {
+ if (!StrCmpNIW(name, t->lvfi.psz, wcslen(t->lvfi.psz)))
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, i);
+ return 1;
+ }
+ }
+ else if (t->lvfi.flags & LVFI_STRING)
+ {
+ if (!lstrcmpiW(name, t->lvfi.psz))
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, i);
+ return 1;
+ }
+ }
+ else
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, -1);
+ return 1;
+ }
+ if (++i == f->Size()) i = 0;
+ }
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, -1);
+ return 1;
+ }
+ else if (l->code == LVN_GETDISPINFOW)
+ {
+ NMLVDISPINFOW *lpdi = (NMLVDISPINFOW*) lParam;
+ int item = lpdi->item.iItem;
+ if (item < 0) return 0;
+
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ if (l->idFrom == IDC_LIST3 && numFilters == 3)
+ lpdi->item.pszText = (wchar_t*)filter[2]->CopyText2(item,lpdi->item.iSubItem,lpdi->item.pszText,lpdi->item.cchTextMax);
+
+ if (l->idFrom == IDC_LIST2)
+ lpdi->item.pszText = (wchar_t*)filter[1]->CopyText2(item,lpdi->item.iSubItem,lpdi->item.pszText,lpdi->item.cchTextMax);
+
+ else if (l->idFrom == IDC_LIST1)
+ lpdi->item.pszText = (wchar_t*)filter[0]->CopyText2(item, lpdi->item.iSubItem, lpdi->item.pszText, lpdi->item.cchTextMax);
+ }
+ return 0;
+ }
+ else if (l->code == LVN_COLUMNCLICK)
+ {
+ NMLISTVIEW *p = (NMLISTVIEW*)lParam;
+ int which = l->idFrom == IDC_LIST3 ? 2 : (l->idFrom == IDC_LIST2 ? 1 : 0);
+ wchar_t buf[32] = {0}, buf2[32] = {0};
+
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d",filter[which]->GetConfigId(), which);
+ int l_sc = g_view_metaconf->ReadInt(buf, 0);
+
+ StringCchPrintfW(buf2, ARRAYSIZE(buf2), L"%hs_sort_dir_%d",filter[which]->GetConfigId(), which);
+ int l_sd = g_view_metaconf->ReadInt(buf2, 0);
+ if (p->iSubItem == l_sc) l_sd = !l_sd;
+ else l_sc = p->iSubItem;
+
+ g_view_metaconf->WriteInt(buf, l_sc);
+ g_view_metaconf->WriteInt(buf2, l_sd);
+
+ int s;
+ s = filter[which]->Size();
+ int dlgitem = (which==0)?IDC_LIST1:((which==1)?IDC_LIST2:IDC_LIST3);
+ SendMessage((HWND)SendMessage(GetDlgItem(hwndDlg,dlgitem), LVM_GETHEADER, 0, 0L),WM_ML_IPC,MAKEWPARAM(l_sc,!l_sd/* : !l_sd/*!l_sd : l_sd*/),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ filter[which]->SortResults(g_view_metaconf, which);
+ ListView_SetItemCount(l->hwndFrom, s);
+ InvalidateRect(l->hwndFrom, NULL, TRUE);
+ }
+ else if (l->idFrom == IDC_LIST1 || l->idFrom == IDC_LIST2 || l->idFrom == IDC_LIST3)
+ {
+ int pane = 0;
+ if (l->idFrom == IDC_LIST2) pane = 1;
+ if (l->idFrom == IDC_LIST3) pane = 2;
+
+ if (l->code == NM_DBLCLK)
+ {
+ // allow doubleclick on all, yes
+ int c = filter[pane]->list->GetCount(), x=0;
+ for (x = 0; x < c; x ++) if (filter[pane]->list->GetSelected(x)) break;
+
+ if (!x && pane>0 && filter[pane]->HasTopItem())
+ playList((!!g_config->ReadInt(L"enqueuedef", PLAY)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)),pane-1);
+ else if (x < c)
+ playList((!!g_config->ReadInt(L"enqueuedef", PLAY)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)),pane);
+ }
+ else if (l->code == LVN_ITEMCHANGED)
+ {
+ LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam;
+ if (ignore_selections || !((lv->uNewState ^ lv->uOldState) & LVIS_SELECTED)) return 0;
+ int t;
+ if (pane==0) t=167;
+ else if (pane==1) t=166;
+ else t=165;
+
+ KillTimer(hwndDlg, t);
+ SetTimer(hwndDlg, t, 100, NULL);
+ }
+ else if (l->code == LVN_BEGINDRAG)
+ {
+ if (pane==0) we_are_drag_and_dropping = 1;
+ else if (pane==1) we_are_drag_and_dropping = 2;
+ else we_are_drag_and_dropping = 3;
+ SetCapture(hwndDlg);
+ }
+ }
+ if (l->code == NM_RETURN)
+ {
+ extern void playFiles(int enqueue, int all);
+ playFiles(((!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000))), 1);
+ }
+ }
+ break;
+ case WM_USER+710:
+ if(rateList(wParam))
+ {
+ ViewFilter *f = (ViewFilter *)lParam;
+
+ // first check the validity of lParam
+ bool found=false;
+ for(int i=0; i<numFilters; i++)
+ if(filter[i] == f) found=true;
+ if(!found) break;
+
+ wchar_t buf[10] = {0};
+ wsprintfW(buf,L"%d",wParam);
+ int s = -1;
+ while((s = f->list->GetNextSelected(s)) != -1)
+ f->MetaUpdate(s, DB_FIELDNAME_rating,buf);
+ }
+ break;
+ case WM_DROPFILES:
+ {
+ extern void Window_OnDropFiles(HWND hwndDlg, HDROP hdrop);
+ Window_OnDropFiles(hwndDlg, (HDROP)wParam);
+ break;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_CLEAR:
+ SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, L"");
+ break;
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ KillTimer(hwndDlg, 205);
+ SetTimer(hwndDlg, 205, g_querydelay, NULL);
+ }
+ break;
+ case IDC_BUTTON_ARTMODE:
+ {
+ int changed=0;
+ int f[3]={0,0,0};
+ for (int i=0; i<numFilters; i++)
+ {
+ f[i] = GetFilter(m_query_mode,i);
+ if (f[i] == 6)
+ {
+ f[i] = 11; changed=true;
+ }
+ else if (f[i] == 11)
+ {
+ f[i] = 6; changed=true;
+ }
+ }
+
+ if (changed)
+ {
+ if (f[1] == 6) f[1]=0;
+ if (numFilters == 2) f[2]=0;
+ int m = f[0] | (f[1] << 8) | (f[2] << 16);
+
+ int par = mediaLibrary.GetSelectedTreeItem();
+ QueryList::iterator iter;
+ iter = m_query_list.find(par);
+ if (iter != m_query_list.end())
+ iter->second->mode = m;
+ saveQueryTree();
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ case IDC_BUTTON_VIEWMODE:
+ {
+ int presets[] =
+ {
+ MAKEVIEW_2FILTER(ARTIST,ALBUM),
+ MAKEVIEW_2FILTER(ARTIST,ALBUMART),
+ MAKEVIEW_2FILTER(ALBUMARTIST,ALBUM),
+ MAKEVIEW_2FILTER(ALBUMARTIST,ALBUMART),
+ MAKEVIEW_3FILTER(GENRE,ARTIST,ALBUM),
+ MAKEVIEW_2FILTER(GENRE,ALBUMART),
+ MAKEVIEW_3FILTER(YEAR,ARTIST,ALBUM),
+ MAKEVIEW_2FILTER(COMPOSER,ALBUM),
+ MAKEVIEW_3FILTER(PUBLISHER,ARTIST,ALBUM),
+ };
+ HMENU menu = CreatePopupMenu();
+ bool hasCheck=false;
+ for (int i=0; i < sizeof(presets)/sizeof(presets[0]); i++)
+ {
+ wchar_t buf[350] = {0}, filterName[128] = {0};
+ buf[0] = L'\0';
+ int l = GetNumFilters(presets[i]);
+ bool check = (l == GetNumFilters(m_query_mode));
+ for (int j=0; j<l; j++)
+ {
+ int f = GetFilter(presets[i],j)-1;
+ wcscat(buf,getFilterName(f, filterName, ARRAYSIZE(filterName)));
+ if (GetFilter(presets[i],j+1)) wcscat(buf,L"\\");
+ if (GetFilter(presets[i],j) != GetFilter(m_query_mode,j)) check=false;
+ }
+ AppendMenuW(menu,MF_STRING,i+1,buf);
+ if(check)
+ {
+ CheckMenuItem(menu,i+1,MF_CHECKED);
+ hasCheck=true;
+ }
+ }
+ AppendMenuW(menu,MF_STRING,0x4000,WASABI_API_LNGSTRINGW(IDS_OTHER2));
+ if(!hasCheck)
+ CheckMenuItem(menu,0x4000,MF_CHECKED);
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),&rc);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ rc.left, rc.bottom, hwndDlg, NULL);
+ DestroyMenu(menu);
+ if (r==0) break;
+ else if (r == 0x4000)
+ queryEditItem(mediaLibrary.GetSelectedTreeItem());
+ else
+ {
+ int par = mediaLibrary.GetSelectedTreeItem();
+ QueryList::iterator iter;
+ iter = m_query_list.find(par);
+ if (iter != m_query_list.end())
+ iter->second->mode = presets[r-1];;
+ saveQueryTree();
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ case IDC_BUTTON_COLUMNS:
+ {
+ HMENU themenu[3] = {0};
+ wchar_t *name[3] = {0}, filterName[128] = {0};
+ HMENU menu = CreatePopupMenu();
+
+ MENUITEMINFOW m={sizeof(m),MIIM_TYPE | MIIM_ID | MIIM_SUBMENU,MFT_STRING,0};
+ for (int i=0; i<numFilters; i++)
+ {
+ m.wID = i;
+ m.dwTypeData = name[i] = _wcsdup(getFilterName(GetFilter(m_query_mode,i)-1, filterName, ARRAYSIZE(filterName)));
+ themenu[i] = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ m.hSubMenu = filter[i]->GetMenu(true,i,g_view_metaconf,themenu[i]);
+ InsertMenuItemW(menu,i,FALSE,&m);
+ }
+
+ m.wID = 3;
+ m.dwTypeData = WASABI_API_LNGSTRINGW(IDS_TRACKS_MENU);
+ m.hSubMenu = GetSubMenu(themenu[0], 4);
+ InsertMenuItemW(menu,3,FALSE,&m);
+
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),&rc);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, rc.left,rc.bottom, hwndDlg, NULL);
+
+ DestroyMenu(menu);
+
+ if (r == ID_HEADERWND_CUSTOMIZECOLUMNS)
+ {
+ void customizeColumnsDialog(HWND hwndParent);
+ customizeColumnsDialog(hwndDlg);
+ }
+
+ for (int i=0; i<numFilters; i++)
+ {
+ filter[i]->ProcessMenuResult(r,true,i,g_view_metaconf,hwndDlg);
+ DestroyMenu(themenu[i]);
+ free(name[i]);
+ }
+ }
+ break;
+ }
+ break;
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return TRUE;
+ case WM_APP + 1: //sent by parent for resizing window
+ bgQuery_Stop();
+ getParentPlusSearchQuery(&l_query);
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, l_query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ ignore_selections = 1;
+ for (int i=0; i<numFilters; i++)
+ {
+ filter[i]->list->SetSelected(0);
+ ListView_SetItemCount(filter[i]->list->getwnd(), 0);
+ }
+ ignore_selections = 0;
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)0);
+ refresh -= 1;
+ break;
+ case WM_QUERYFILEINFO: if (m_media_hwnd) PostMessageW(m_media_hwnd, uMsg, wParam, lParam); break;
+ case WM_SHOWFILEINFO:
+ SendMessageW(GetParent(hwndDlg), uMsg, wParam, lParam); break;
+ case WM_USER + 66:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, SendMessage(GetParent(hwndDlg), uMsg, wParam, lParam));
+ return TRUE;
+ case WM_APP + 2: //sent by media child to get current query
+ if (lParam) *((const wchar_t **)(lParam)) = l_query.Get();
+ break;
+ case WM_APP + 3: // sent by bg thread to update our shit
+ if (wParam == 0x69)
+ {
+ if (lParam == 0 && numFilters >= 1)
+ {
+ if (UpdateFilterPane(0,"artist_sel_utf8"))
+ {
+ PostMessage(hwndDlg,WM_TIMER,167,0);
+ break;
+ }
+ }
+
+ if (lParam <= 1 && numFilters >= 2)
+ {
+ if (UpdateFilterPane(1,"album_sel_utf8"))
+ {
+ PostMessage(hwndDlg,WM_TIMER,166,0);
+ break;
+ }
+ }
+
+ if (numFilters >= 3)
+ {
+ if (UpdateFilterPane(2,"list3_sel_utf8"))
+ {
+ PostMessage(hwndDlg,WM_TIMER,165,0);
+ }
+ }
+ }
+ break;
+ // used for working around some quirks with the ratings so it'll update on-the-fly(ish)
+ case WM_APP + 4:
+ {
+ if(!lParam)
+ {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf,64,L"%d",wParam);
+ for(int i=0; i<numFilters; i++)
+ {
+ int s = -1;
+ while((s = filter[i]->list->GetNextSelected(s)) != -1)
+ {
+ filter[i]->MetaUpdate(s, DB_FIELDNAME_rating, buf);
+ SendMessage(filter[i]->list->getwnd(),LVM_REDRAWITEMS,s,s);
+ }
+ }
+ }
+ else
+ {
+ PostMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)1);
+ }
+ }
+ break;
+ case WM_APP + 5:
+ // TODO
+ break;
+ case WM_APP + 6: // handles the ml_cloud 'first pull' announces so we
+ { // can then show the cloud column & update the cache
+ SendMessage(m_media_hwnd, WM_APP + 6, wParam, lParam);
+ break;
+ }
+ case WM_DESTROY:
+ FlushCache();
+
+ if (numFilters >= 2) g_view_metaconf->WriteInt(L"adiv1pos", div_filterpos);
+ if (numFilters >= 3) g_view_metaconf->WriteInt(L"adiv3pos", div_filter2pos);
+
+ g_view_metaconf->WriteInt(L"adiv2pos", div_mediapos);
+ // save a list of selected artists and albums
+ for (int i=0; i<numFilters; i++)
+ {
+ GayStringW gs;
+ if (!(filter[i]->list->GetSelected(0) && filter[i]->HasTopItem()))
+ {
+ int c = filter[i]->list->GetCount(), delim = 0;
+ for (int x = filter[i]->HasTopItem()?1:0; x < c; x ++)
+ {
+ if (filter[i]->list->GetSelected(x))
+ {
+ if (delim) gs.Append(LDELIMSTR);
+ const wchar_t *p = filter[i]->GetText(x);
+ while (p && *p)
+ {
+ if (*p == *LDELIMSTR) gs.Append(LDELIMSTR LDELIMSTR);
+ else
+ {
+ wchar_t buf[2] = {*p, 0};
+ gs.Append(buf);
+ }
+ p++;
+ }
+ delim = 1;
+ }
+ }
+ }
+ char *confname = "artist_sel_utf8";
+ if (i==1) confname = "album_sel_utf8";
+ if (i==2) confname = "list3_sel_utf8";
+ g_view_metaconf->WriteString(confname, AutoChar(gs.Get(), CP_UTF8));
+ }
+ {
+ wchar_t buf[2048] = {0};
+ GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ g_view_metaconf->WriteString("lastquery_a_utf8", AutoChar(buf, CP_UTF8));
+ }
+
+ bgQuery_Stop();
+
+ for (int i=0; i<numFilters; i++)
+ {
+ filter[i]->SaveColumnWidths();
+ filter[i]->Empty();
+ filter[i]->ClearColumns();
+ delete filter[i];
+ }
+ numFilters=0;
+ {
+ SkinBitmap *s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),GWLP_USERDATA);
+ if (s)
+ {
+ void *bits = s->getBits();
+ if (bits) free(bits); delete s;
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),GWLP_USERDATA, 0);
+ }
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),GWLP_USERDATA);
+ if (s)
+ {
+ void *bits = s->getBits();
+ if (bits) free(bits); delete s;
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),GWLP_USERDATA, 0);
+ }
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),GWLP_USERDATA);
+ if (s)
+ {
+ void *bits = s->getBits();
+ if (bits) free(bits); delete s;
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),GWLP_USERDATA, 0);
+ }
+ }
+ break;
+ case WM_PAINT:
+ {
+ int tab[] =
+ {
+ IDC_HDELIM | DCW_DIVIDER, // 0
+ IDC_QUICKSEARCH | DCW_SUNKENBORDER, // 1
+ IDC_DIV1 | DCW_DIVIDER, // 2
+ IDC_LIST1 | DCW_SUNKENBORDER, //3
+ IDC_LIST2 | DCW_SUNKENBORDER, //4
+ IDC_LIST3 | DCW_SUNKENBORDER, //5
+ IDC_DIV2 | DCW_DIVIDER, //6
+ };
+ int size = 5;
+ if (numFilters == 3) size = 7;
+
+ if (m_nodrawtopborders) size = 2;
+ else
+ {
+ if (numFilters == 3)
+ {
+ if (adiv1_nodraw == 2) tab[4]=0;
+ if (adiv1_nodraw == 1) tab[3]=0;
+ if (adiv2_nodraw == 2) tab[5]=0;
+ if (adiv2_nodraw == 1) tab[4]=0;
+ }
+ else
+ {
+ if (adiv1_nodraw == 2) size = 4;
+ if (adiv1_nodraw == 1)
+ {
+ size = 4; tab[3] = tab[4];
+ }
+ }
+ }
+ dialogSkinner.Draw(hwndDlg, tab, size);
+ }
+ break;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_ML_CHILDIPC:
+ switch (lParam)
+ {
+ case ML_CHILDIPC_GO_TO_SEARCHBAR:
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ case ML_CHILDIPC_REFRESH_SEARCH:
+ refresh = (1 + numFilters);
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ }
+ break;
+
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/view_errorinfo.cpp b/Src/Plugins/Library/ml_local/view_errorinfo.cpp
new file mode 100644
index 00000000..5f7e71ec
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_errorinfo.cpp
@@ -0,0 +1,119 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw)
+{
+ RECT rc, rg;
+ HRGN rgn;
+
+ GetClientRect(hwnd, &rc);
+ SetRect(&rg, 0, 0, 0, 0);
+
+ rc.top += 2;
+ rc.right -=2;
+
+ if (rc.bottom <= rc.top || rc.right <= rc.left) return;
+
+ rgn = NULL;
+
+ HWND temp = GetDlgItem(hwnd, IDC_DB_ERROR);
+ GetWindowRect(temp, &rg);
+ SetWindowPos(temp, NULL, WASABI_API_APP->getScaleX(20), WASABI_API_APP->getScaleY(20),
+ rc.right - rc.left - WASABI_API_APP->getScaleX(40),
+ rc.bottom - rc.top - WASABI_API_APP->getScaleY(45),
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+
+ temp = GetDlgItem(hwnd, IDC_RESET_DB_ON_ERROR);
+ GetWindowRect(temp, &rg);
+ SetWindowPos(temp, NULL, ((rc.right - rc.left) - (rg.right - rg.left)) / 2,
+ rc.bottom - (rg.bottom - rg.top),
+ rg.right - rg.left, rg.bottom - rg.top,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ if (fRedraw)
+ {
+ UpdateWindow(hwnd);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ if (rgn)
+ {
+ OffsetRgn(rgn, rc.left, rc.top);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, rgn, RGN_OR);
+ }
+ }
+ ValidateRgn(hwnd, NULL);
+ if (rgn) DeleteObject(rgn);
+}
+
+INT_PTR CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a=dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DB_ERROR),(LPCWSTR)WASABI_API_LOADRESFROMFILEW(L"TEXT", MAKEINTRESOURCE((nde_error ? IDR_NDE_ERROR : IDR_DB_ERROR)), 0));
+ if (nde_error)
+ DestroyWindow(GetDlgItem(hwndDlg, IDC_RESET_DB_ON_ERROR));
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.hwndToSkin = hwndDlg;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_RESET_DB_ON_ERROR)
+ {
+ nukeLibrary(hwndDlg);
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER+66:
+ if (wParam == -1)
+ {
+ LayoutWindows(hwndDlg, TRUE);
+ }
+ return TRUE;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ dialogSkinner.Draw(hwndDlg, 0, 0);
+ }
+ return 0;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ }
+ return FALSE;
+}
diff --git a/Src/Plugins/Library/ml_local/view_media.cpp b/Src/Plugins/Library/ml_local/view_media.cpp
new file mode 100644
index 00000000..7c8aa07e
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_media.cpp
@@ -0,0 +1,4051 @@
+#include "main.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include <time.h>
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "../ml_pmp/pmp.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "../nde/nde.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../replicant/nu/AutoChar.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <math.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+#include "..\..\General\gen_ml/menufucker.h"
+#include "api_mldb.h"
+#include "../replicant/foundation/error.h"
+
+static wchar_t oldText[4096];
+
+static int IPC_LIBRARY_SENDTOMENU;
+static HINSTANCE cloud_hinst;
+const int ML_MSG_PDXS_STATUS = 0x1001;
+const int ML_MSG_PDXS_MIX = 0x1002;
+void RefreshMetadata(HWND parent);
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX, offsetY, customAllowed;
+int groupBtn = 1, enqueuedef = 0;
+static viewButtons view;
+HWND hwndSearchGlobal = 0;
+
+//timers
+#define TIMER_RATINGAUTOUNHOVER_ID 65520
+#define TIMER_RATINGAUTOUNHOVER_DELAY 200
+
+void FixAmps(wchar_t *str, size_t len)
+{
+ size_t realSize = 0;
+ size_t extra = 0;
+ wchar_t *itr = str;
+ while (itr && *itr)
+ {
+ if (itr && *itr == L'&')
+ extra++;
+ itr++;
+ realSize++;
+ }
+
+ extra = min(len - (realSize + 1), extra);
+
+ while (extra)
+ {
+ str[extra+realSize] = str[realSize];
+ if (str[realSize] == L'&')
+ {
+ extra--;
+ str[extra+realSize] = L'&';
+ }
+ realSize--;
+ }
+}
+
+void MakeDateString(__time64_t convertTime, wchar_t *dest, size_t destlen)
+{
+ SYSTEMTIME sysTime;
+ tm *newtime = _localtime64(&convertTime);
+ if (newtime)
+ {
+ sysTime.wYear = (WORD)(newtime->tm_year + 1900);
+ sysTime.wMonth = (WORD)(newtime->tm_mon + 1);
+ sysTime.wDayOfWeek = (WORD)newtime->tm_wday;
+ sysTime.wDay = (WORD)newtime->tm_mday;
+ sysTime.wHour = (WORD)newtime->tm_hour;
+ sysTime.wMinute = (WORD)newtime->tm_min;
+ sysTime.wSecond = (WORD)newtime->tm_sec;
+ sysTime.wMilliseconds = 0;
+
+ GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, destlen);
+
+ size_t dateSize = lstrlenW(dest);
+ dest += dateSize;
+ destlen -= dateSize;
+ if (destlen)
+ {
+ *dest++ = L' ';
+ destlen--;
+ }
+
+ GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, destlen);
+
+ //wcsftime(expire_time, 63, L"%b %d, %Y", _localtime64(&convertTime));
+ }
+ else
+ dest[0] = 0;
+}
+
+#define MAINTABLE_ID_CLOUD (unsigned char)-1
+const unsigned char extra_idsW[] =
+{
+ MAINTABLE_ID_ISPODCAST,
+ MAINTABLE_ID_PODCASTCHANNEL,
+ MAINTABLE_ID_PODCASTPUBDATE,
+ MAINTABLE_ID_GRACENOTEFILEID,
+ MAINTABLE_ID_GRACENOTEEXTDATA,
+ MAINTABLE_ID_LOSSLESS,
+ MAINTABLE_ID_CODEC,
+ MAINTABLE_ID_DIRECTOR,
+ MAINTABLE_ID_PRODUCER,
+ MAINTABLE_ID_WIDTH,
+ MAINTABLE_ID_HEIGHT,
+ MAINTABLE_ID_MIMETYPE,
+ 0,
+ MAINTABLE_ID_DATEADDED,
+ MAINTABLE_ID_CLOUD,
+};
+
+const ExtendedFields extended_fields =
+{
+ L"ispodcast",
+ L"podcastchannel",
+ L"podcastpubdate",
+ L"GracenoteFileID",
+ L"GracenoteExtData",
+ L"lossless",
+ L"codec",
+ L"director",
+ L"producer",
+ L"width",
+ L"height",
+ L"mime",
+ L"realsize",
+ L"dateadded",
+ L"cloud",
+};
+
+const wchar_t *extra_strsW[] =
+{
+ extended_fields.ispodcast,
+ extended_fields.podcastchannel,
+ extended_fields.podcastpubdate,
+ extended_fields.GracenoteFileID,
+ extended_fields.GracenoteExtData,
+ extended_fields.lossless,
+ extended_fields.codec,
+ extended_fields.director,
+ extended_fields.producer,
+ extended_fields.width,
+ extended_fields.height,
+ extended_fields.mimetype,
+ extended_fields.realsize,
+ extended_fields.dateadded,
+ extended_fields.cloud,
+};
+
+const int NUM_EXTRA_COLSW = sizeof(extra_idsW) / sizeof(*extra_idsW);
+
+bool isMixable(itemRecordW &song);
+static int predixisExist;
+
+static BOOL g_displaysearch = TRUE;
+static BOOL g_displaycontrols = TRUE;
+
+nde_scanner_t m_media_scanner = 0;
+
+W_ListView resultlist;
+static int resultSkin;
+
+void fileInfoDialogs(HWND hwndParent);
+void editInfo(HWND hwndParent);
+void customizeColumnsDialog(HWND hwndParent);
+
+static HWND m_hwnd;
+
+itemRecordListW itemCache;
+static int bgThread_Kill = 0;
+static HANDLE bgThread_Handle;
+static bool isMixablePresent = true;
+
+CloudFiles cloudFiles, cloudUploading;
+
+typedef struct
+{
+ UINT column_id;
+ char *config_name;
+ WORD defWidth;
+ WORD minWidth;
+ WORD maxWidth;
+}
+headerColumn;
+
+#define UNLIMITED_WIDTH ((WORD)-1)
+
+#define COLUMN_DEFMINWIDTH 12
+#define COLUMN_DEFMAXWIDTH UNLIMITED_WIDTH
+
+#define MAX_COLUMN_ORDER (MEDIAVIEW_COL_NUMS+1)
+
+static headerColumn columnList[MAX_COLUMN_ORDER - 1] =
+{
+ {IDS_ARTIST, "mv_col_artist", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TITLE, "mv_col_title", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_ALBUM, "mv_col_album", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_LENGTH, "mv_col_length", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TRACK_NUMBER, "mv_col_track", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_GENRE, "mv_col_genre", 75, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_YEAR, "mv_col_year", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILENAME, "mv_col_fn", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_RATING, "mv_col_rating", 65, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PLAY_COUNT, "mv_col_playcount", 70, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PLAYED_LAST, "mv_col_lastplay", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_LAST_UPDATED, "mv_col_lastupd", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILE_TIME, "mv_col_filetime", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_COMMENT, "mv_col_comment", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILE_SIZE, "mb_col_filesize", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_BITRATE, "mb_col_bitrate", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TYPE, "mb_col_type", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DISC, "mb_col_disc", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_ALBUM_ARTIST, "mb_col_albumartist", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILE_PATH, "mb_col_filepath", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_ALBUM_GAIN, "mb_col_albumgain", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TRACK_GAIN, "mb_col_trackgain", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PUBLISHER, "mb_col_publisher", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_COMPOSER, "mb_col_composer", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_EXTENSION, "mb_col_extension", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_IS_PODCAST, "mb_col_ispodcast", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PODCAST_CHANNEL, "mb_col_podcastchannel", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PODCAST_PUBLISH_DATE, "mb_col_podcastpubdate", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_BPM, "mb_col_bpm", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_CATEGORY, "mb_col_category", 75, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DIRECTOR, "mb_col_director", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PRODUCER, "mb_col_producer", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DIMENSION, "mb_col_dimension", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DATE_ADDED, "mb_col_dateadded", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_CLOUD, "mv_col_cloud", 27, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+};
+
+//default column order
+static signed char defColumnOrderCloud[MAX_COLUMN_ORDER] =
+{
+ MEDIAVIEW_COL_ARTIST,
+ MEDIAVIEW_COL_ALBUM,
+ MEDIAVIEW_COL_TRACK,
+ MEDIAVIEW_COL_CLOUD,
+ MEDIAVIEW_COL_TITLE,
+ MEDIAVIEW_COL_LENGTH,
+ MEDIAVIEW_COL_GENRE,
+ MEDIAVIEW_COL_RATING,
+ MEDIAVIEW_COL_PLAYCOUNT,
+ MEDIAVIEW_COL_LASTPLAY,
+ MEDIAVIEW_COL_YEAR,
+ -1
+};
+static signed char defColumnOrder[MAX_COLUMN_ORDER] =
+{
+ MEDIAVIEW_COL_ARTIST,
+ MEDIAVIEW_COL_ALBUM,
+ MEDIAVIEW_COL_TRACK,
+ MEDIAVIEW_COL_TITLE,
+ MEDIAVIEW_COL_LENGTH,
+ MEDIAVIEW_COL_GENRE,
+ MEDIAVIEW_COL_RATING,
+ MEDIAVIEW_COL_PLAYCOUNT,
+ MEDIAVIEW_COL_LASTPLAY,
+ MEDIAVIEW_COL_YEAR,
+ -1
+};
+static signed char columnOrder[MAX_COLUMN_ORDER];
+
+
+int WCSCMP_NULLOK(const wchar_t *pa, const wchar_t *pb)
+{
+ if (!pa) pa = L"";
+ else SKIP_THE_AND_WHITESPACEW(pa)
+
+ if (!pb) pb = L"";
+ else SKIP_THE_AND_WHITESPACEW(pb)
+
+ return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE /*| NORM_IGNORENONSPACE*/, pa, -1, pb, -1) - 2;
+// return lstrcmpi(pa, pb);
+}
+
+
+int FLOATWCMP_NULLOK(const wchar_t *pa, const wchar_t *pb)
+{
+ if (pa) SKIP_THE_AND_WHITESPACEW(pa)
+
+ if (pb) SKIP_THE_AND_WHITESPACEW(pb)
+
+ if ((!pa || !*pa) && (!pb || !*pb))
+ return 0;
+ if (!pa || !*pa)
+ return 1;
+ if (!pb || !*pb)
+ return -1;
+
+ _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
+ float a = (float)_wtof_l(pa,C_locale);
+ float b = (float)_wtof_l(pb,C_locale);
+
+ if (a > b)
+ return 1;
+ else if (a < b)
+ return -1;
+ else
+ return 0;
+}
+
+
+typedef struct
+{
+ resultsniff_funcW cb;
+ int user32;
+}
+bgThreadParms;
+
+static int bg_total_len_s;
+static __int64 bg_total_len_bytes;
+static LARGE_INTEGER querytime;
+
+static HMENU rate_hmenu = NULL;
+static HMENU cloud_hmenu = NULL;
+static HMENU sendto_hmenu = NULL;
+static librarySendToMenuStruct s;
+
+static RATINGCOLUMN ratingColumn;
+static WCHAR ratingBackText[128];
+
+#define IDC_LIST2HEADER 2001 // WM_INITDIALOG assign this is to the IDC_LIST2 header
+
+// internal messages
+#define IM_SYNCHEADERORDER (WM_USER + 0xFFF0)
+
+static DWORD WINAPI bgThreadQueryProc(void *tmp)
+{
+ bgThreadParms *p = (bgThreadParms*)tmp;
+ bg_total_len_s = 0;
+ bg_total_len_bytes = 0;
+ LARGE_INTEGER starttime, endtime;
+ QueryPerformanceCounter(&starttime);
+
+ bg_total_len_s = saveQueryToListW(g_view_metaconf, m_media_scanner, &itemCache,
+ &cloudFiles, &cloudUploading, p->cb, p->user32,
+ &bgThread_Kill, &bg_total_len_bytes);
+ QueryPerformanceCounter(&endtime);
+ querytime.QuadPart = endtime.QuadPart - starttime.QuadPart;
+
+ if (!bgThread_Kill) PostMessage(m_hwnd, WM_APP + 3, 0x69, 0);
+
+ return 0;
+}
+
+void bgQuery_Stop() // exported for other people to call since it is useful (eventually
+// we should have bgQuery pass the new query info along but I'll do that soon)
+{
+ if (bgThread_Handle)
+ {
+ bgThread_Kill = 1;
+ WaitForSingleObject(bgThread_Handle, INFINITE);
+ CloseHandle(bgThread_Handle);
+ bgThread_Handle = 0;
+ }
+ KillTimer(m_hwnd, 123);
+}
+
+static void bgQuery(resultsniff_funcW cb = 0, int user32 = 0) // only internal used
+{
+ bgQuery_Stop();
+ SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, WASABI_API_LNGSTRINGW(IDS_SCANNING));
+ StringCchCopyW(oldText, 4096, WASABI_API_LNGSTRINGW(IDS_SCANNING));
+
+ SetTimer(m_hwnd, 123, 200, NULL);
+
+ DWORD id;
+ static bgThreadParms parms;
+ parms.cb = cb;
+ parms.user32 = user32;
+ bgThread_Kill = 0;
+ bgThread_Handle = CreateThread(NULL, 0, bgThreadQueryProc, (LPVOID) & parms, 0, &id);
+}
+
+// this thing does not produce a fully valid itemRecordList. be afraid.
+static void copyFilesToItemCacheW(itemRecordListW *obj)
+{
+ if (bgThread_Handle) return ;
+
+ int cnt = itemCache.Size;
+ int i, l = cnt;
+ cnt = 0;
+ for (i = 0;i < l;i++)
+ {
+ if (resultlist.GetSelected(i)) cnt++;
+ }
+ obj->Alloc = obj->Size = 0;
+
+ if (!cnt) return ;
+
+ allocRecordList(obj, cnt, 0);
+ if (!obj->Items)
+ {
+ obj->Size = obj->Alloc = 0;
+ return ;
+ }
+
+ for (i = 0; i < itemCache.Size; i ++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ obj->Items[obj->Size++] = itemCache.Items[i];
+ // makes sure that we are providing filesize in kb as
+ // per spec even if we store it as __int64 internally
+ obj->Items[obj->Size-1].filesize /= 1024;
+ }
+ }
+}
+
+void playFiles(int enqueue, int all)
+{
+ if (bgThread_Handle) return ;
+
+ int cnt = 0;
+ int l = itemCache.Size;
+
+ int foo_all = 0; // all but play the only selected
+ int foo_selected = -1;
+
+ if (!enqueue && !all && g_config->ReadInt(L"viewplaymode", 1))
+ {
+ int selcnt = 0;
+ for (int i = 0;i < l;i++)
+ {
+ if (resultlist.GetSelected(i)) selcnt++;
+ }
+ if (selcnt == 1)
+ {
+ foo_all = -1;
+ }
+ }
+
+ for (int i = 0;i < l;i++)
+ {
+ if (foo_all || all || resultlist.GetSelected(i))
+ {
+ if (foo_all && foo_selected < 0 && resultlist.GetSelected(i)) foo_selected = i;
+
+ if (!cnt)
+ {
+ if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+ cnt++;
+ }
+
+ wchar_t title[2048] = {0};
+ TAG_FMT_EXT(itemCache.Items[i].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&itemCache.Items[i], title, 2048, 0);
+
+ enqueueFileWithMetaStructW s;
+ s.filename = itemCache.Items[i].filename;
+ s.title = title;
+ s.ext = NULL;
+ s.length = itemCache.Items[i].length;
+#ifndef _DEBUG
+ ndestring_retain(itemCache.Items[i].filename);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW_NDE);
+#else
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+#endif
+ }
+ }
+ if (cnt)
+ {
+ if (foo_selected >= 0)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, foo_selected, IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40047, 0); // stop button, literally
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40045, 0); // play button, literally
+ }
+ else if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ }
+}
+
+// out can never be bigger than in+1
+static void parsequicksearch(wchar_t *out, wchar_t *in) // parses a list into a list of terms that we are searching for
+{
+ int inquotes = 0, neednull = 0;
+ while (in && *in)
+ {
+ wchar_t c = *in++;
+ if (c != ' ' && c != '\t' && c != '\"')
+ {
+ neednull = 1;
+ *out++ = c;
+ }
+ else if (c == '\"')
+ {
+ inquotes = !inquotes;
+ if (!inquotes)
+ {
+ *out++ = 0;
+ neednull = 0;
+ }
+ }
+ else
+ {
+ if (inquotes) *out++ = c;
+ else if (neednull)
+ {
+ *out++ = 0;
+ neednull = 0;
+ }
+ }
+ }
+ *out++ = 0;
+ *out++ = 0;
+}
+
+void makeQueryStringFromText(GayStringW *query, wchar_t *text, int nf)
+{
+ int ispar = 0;
+ if (query->Get()[0])
+ {
+ ispar = 1;
+ query->Append(L"&(");
+ }
+
+ if (!_wcsnicmp(text, L"query:", 6))
+ query->Append(text + 6); // copy the query as is
+ else if (text[0] == L'?')
+ query->Append(text + 1);
+ else
+ {
+ int isAny = 0;
+ if (*text == L'*' && text[1] == L' ')
+ {
+ isAny = 1;
+ text += 2;
+ }
+ wchar_t tmpbuf[2048 + 32] = {0};
+ parsequicksearch(tmpbuf, text);
+
+ int x;
+ wchar_t *fields[] =
+ {
+ L"filename",
+ L"title",
+ L"artist",
+ L"album",
+ L"genre",
+ L"albumartist",
+ L"publisher",
+ L"composer",
+ };
+ wchar_t *p = tmpbuf;
+ while (p && *p)
+ {
+ size_t lenp = wcslen(p);
+
+ if (p == tmpbuf) query->Append(L"(");
+ else if (isAny) query->Append(L")|(");
+ else query->Append(L")&(");
+ if (p[0] == L'<' && p[wcslen(p) - 1] == L'>' && wcslen(p) > 2)
+ {
+ wchar_t *op = p;
+ while (op && *op)
+ {
+ if (*op == L'\'') *op = L'\"';
+ op++;
+ }
+
+ p[lenp - 1] = 0; // remove >
+ query->Append(p + 1);
+ }
+ else
+ {
+ for (x = 0; x < (int)min(sizeof(fields) / sizeof(fields[0]), nf); x ++)
+ {
+ wchar_t *field = fields[x];
+ if (x) query->Append(L"|");
+ query->Append(field);
+ query->Append(L" HAS \"");
+ GayStringW escaped;
+ queryStrEscape(p, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\"");
+ }
+ }
+ p += lenp + 1;
+ }
+ query->Append(L")");
+ }
+ if (ispar) query->Append(L")");
+}
+
+static void doQuery(HWND hwndDlg, wchar_t *text, int dobg = 1)
+{
+ bgQuery_Stop();
+
+ GayStringW query;
+ if (text[0]) makeQueryStringFromText(&query, text);
+
+ wchar_t *parent_query = NULL;
+ extern wchar_t* m_query;
+ parent_query = m_query;
+ SendMessage(GetParent(hwndDlg), WM_APP + 2, 0, (LPARAM)&parent_query);
+ GayStringW q;
+
+ if (parent_query && parent_query[0])
+ {
+ q.Set(L"(");
+ q.Append(parent_query);
+ q.Append(L")");
+ }
+ if (query.Get() && query.Get()[0])
+ {
+ if (q.Get()[0])
+ {
+ q.Append(L" & (");
+ q.Append(query.Get());
+ q.Append(L")");
+ }
+ else q.Set(query.Get());
+ }
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, q.Get());
+ LeaveCriticalSection(&g_db_cs);
+ if (dobg) bgQuery();
+}
+
+static void RecycleSelectedItems()
+{
+ int totalItems = resultlist.GetSelectedCount();
+
+ if (!totalItems)
+ return ;
+
+ SHFILEOPSTRUCTW fileOp;
+ fileOp.hwnd = m_hwnd;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = 0;
+ fileOp.pTo = 0;
+ fileOp.fFlags = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_USES_RECYCLEBIN) ? FOF_ALLOWUNDO : 0;
+ fileOp.fAnyOperationsAborted = 0;
+ fileOp.hNameMappings = 0;
+ fileOp.lpszProgressTitle = 0;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ int cchLen = totalItems * (MAX_PATH + 1) + 1;
+ wchar_t *files = new wchar_t[cchLen]; // need room for each file name, null terminated. then have to null terminate the whole list
+
+ if (files) // if malloc succeeded
+ {
+ wchar_t *curFile = files;
+ for (int i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename))
+ {
+ StringCchCopyW(curFile, cchLen, itemCache.Items[i].filename);
+ curFile += wcslen(itemCache.Items[i].filename) + 1;
+ }
+ }
+ }
+ if (curFile != files)
+ {
+ curFile[0] = 0; // null terminate
+
+ fileOp.pFrom = files;
+ fileOp.fAnyOperationsAborted = 0;
+ if (SHFileOperationW(&fileOp))
+ {
+ wchar_t title[64] = {0};
+ MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_ERROR_DELETING_FILES),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,64), MB_OK);
+ }
+ else
+ {
+ // only remove items if deletion was allowed
+ if (!fileOp.fAnyOperationsAborted)
+ {
+ for (int j = 0; j < itemCache.Size; j++)
+ {
+ if (resultlist.GetSelected(j))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[j].filename))
+ {
+ // Wasabi callback event for pre remove
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[j].filename, 0);
+
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ // Wasabi callback event for post remove
+ // ToDo: (BigG) Move outside of critical section somehow
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[j].filename, 0);
+
+ }
+ }
+ }
+ }
+ }
+ }
+
+ delete [] files;
+ }
+ else // if malloc failed ... maybe because there's too many items.
+ {
+ files = new wchar_t[MAX_PATH + 1 + 1]; // double null termination
+ if (!files) // if this malloc failed, just bail out
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return ;
+ }
+
+ fileOp.pFrom = files;
+
+ for (int i = 0;i < itemCache.Size;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename))
+ {
+ StringCchCopyW(files, MAX_PATH + 1 + 1, itemCache.Items[i].filename);
+ files[wcslen(itemCache.Items[i].filename) + 1] = 0; // double null terminate
+ fileOp.fAnyOperationsAborted = 0;
+ if (SHFileOperationW(&fileOp))
+ {
+ wchar_t mes[4096] = {0};
+ StringCchPrintfW(mes, 4096, WASABI_API_LNGSTRINGW(IDS_ERROR_DELETING_X), files);
+ MessageBoxW(m_hwnd, mes, WASABI_API_LNGSTRINGW(IDS_ERROR), MB_OK);
+ continue;
+ }
+ if (!fileOp.fAnyOperationsAborted)
+ {
+ // Wasabi callback event for pre remove
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[i].filename, 0);
+
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ // Wasabi callback event for post remove
+ // ToDo: (BigG) Move outside of critical section somehow
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[i].filename, 0);
+ }
+ }
+ }
+ delete files;
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty) NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+
+ resultlist.Clear();
+ emptyRecordList(&itemCache);
+
+ // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc),
+ // but really it is necessary for the view to be accurate.
+
+ SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view
+}
+
+static void removeSelectedItems(int physical)
+{
+ if (physical)
+ {
+ RecycleSelectedItems();
+ return ;
+ }
+
+ int hasdel = 0;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ for (int i = 0;i < itemCache.Size;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename))
+ {
+ wchar_t conf[32] = {0};
+ if (!hasdel && MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,conf,32), MB_YESNO | MB_ICONQUESTION) != IDYES)
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return ;
+ //FUCKO> need to eat the RETURN msg
+ //MSG msg;
+ //while(PeekMessage(&msg,m_hwnd,WM_COMMAND,WM_COMMAND,1));
+ }
+ if (!hasdel) // stop any background queries
+ {
+ bgQuery_Stop();
+ }
+ // Wasabi callback event for pre remove
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[i].filename, 0);
+
+ hasdel = 1;
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ // Wasabi callback event for post remove
+ // ToDo: (BigG) Move this outside of the critical section
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[i].filename, 0);
+ }
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty) NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+
+ if (!hasdel) return ;
+
+ resultlist.Clear();
+ emptyRecordList(&itemCache);
+
+ // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc),
+ // but really it is necessary for the view to be accurate.
+
+ SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view
+}
+
+static void exploreItemFolder(HWND hwndDlg)
+{
+ if (resultlist.GetSelectionMark() >= 0)
+ {
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ WASABI_API_EXPLORERFINDFILE->AddFile(itemCache.Items[i].filename);
+ }
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+static void removeDeadFiles(HWND hwndDlg)
+{
+ Scan_RemoveFiles(hwndDlg);
+
+ // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc),
+ // but really it is necessary for the view to be accurate.
+ SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view
+}
+
+static WNDPROC search_oldWndProc;
+static DWORD WINAPI search_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_KEYDOWN && wParam == VK_DOWN)
+ {
+ PostMessageW(GetParent(hwndDlg), WM_NEXTDLGCTL, (WPARAM)resultlist.getwnd(), (LPARAM)TRUE);
+ ListView_SetItemState(resultlist.getwnd(), 0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
+ }
+ return CallWindowProcW(search_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x3
+#define GROUP_SEARCH 0x1
+#define GROUP_STATUSBAR 0x2
+#define GROUP_MAIN 0x3
+
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_SEARCH, IDC_SEARCHCAPTION, IDC_CLEAR, IDC_QUICKSEARCH,
+ GROUP_STATUSBAR, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX, IDC_BUTTON_CREATEPLAYLIST, IDC_BUTTON_INFOTOGGLE, IDC_MIXABLE, IDC_MEDIASTATUS,
+ GROUP_MAIN, IDC_LIST2
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.bottom == rc.top || rc.right == rc.left) return;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_SEARCH:
+ if (g_displaysearch)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_CLEAR);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left,
+ rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaysearch;
+ break;
+ case GROUP_STATUSBAR:
+ if (g_displaycontrols)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_PLAY);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaycontrols;
+ break;
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_SEARCHCAPTION:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(2),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_CLEAR:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, rg.bottom - rg.top);
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_QUICKSEARCH:
+ pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_BUTTON_PLAY:
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_MIX:
+ case IDC_BUTTON_CREATEPLAYLIST:
+ if (IDC_BUTTON_MIX != pl->id || customAllowed)
+ {
+ if (groupBtn && pl->id == IDC_BUTTON_PLAY && enqueuedef == 1)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && pl->id == IDC_BUTTON_ENQUEUE && enqueuedef != 1)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && (pl->id == IDC_BUTTON_PLAY || pl->id == IDC_BUTTON_ENQUEUE) && customAllowed)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (pl->id == IDC_BUTTON_CREATEPLAYLIST && !AGAVE_API_PLAYLIST_GENERATOR)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ width, WASABI_API_APP->getScaleY(HIWORD(idealSize)));
+ pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_BUTTON_INFOTOGGLE:
+ switch (SendMessage(GetParent(hwnd), WM_USER + 66, 0, 0))
+ {
+ case 0xFF:
+ case 0xF0:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+
+ pl->flags |= (((rg.right - rg.left) - (ri.right - ri.left)) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ;
+ SETLAYOUTPOS(pl, rg.right - width - WASABI_API_APP->getScaleX(2),
+ rg.top, width, (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ }
+ break;
+ case IDC_MIXABLE:
+ if (predixisExist & 1)
+ {
+ SETLAYOUTPOS(pl, rg.right - (ri.right - ri.left), rg.top, (ri.right - ri.left), (rg.bottom - rg.top));
+ pl->flags |= ((rg.right - rg.left) - (ri.right - ri.left) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ }
+ break;
+ case IDC_MEDIASTATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > WASABI_API_APP->getScaleX(16)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_LIST2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + 1, (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && !fUpdateAll && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+
+ pl++;
+ }
+ else if (!fUpdateAll && (fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ if (pc->rgn) DeleteObject(pc->rgn);
+ }
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void updateInfoText(HWND hwndDlg, bool x = false)
+{
+ int a = SendMessage(GetParent(hwndDlg), WM_USER + 66, x ? -1 : 0, 0);
+ if (a == 0xff)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_INFOTOGGLE, WASABI_API_LNGSTRINGW(IDS_HIDE_INFO));
+ else if (a == 0xf0)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_INFOTOGGLE, WASABI_API_LNGSTRINGW(IDS_SHOW_INFO));
+ else ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_INFOTOGGLE), SW_HIDE);
+}
+
+static void initColumnsHeader(HWND hwndList)
+{
+ INT index, sortby;
+ LVCOLUMNW lvc = {0, };
+ if (!hwndList || !IsWindow(hwndList)) return;
+
+ SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L);
+
+ while (SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L));
+
+ sortby = g_view_metaconf->ReadInt(L"mv_sort_by", 1);
+
+ lvc.mask = LVCF_TEXT | LVCF_WIDTH;
+ lvc.pszText = L"";
+ lvc.cx = 30;
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc); // create dummy column
+
+ // TODO set to a zero width if not available
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1); // reset the cloud status column so it'll be correctly removed
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1);
+
+ for (index = 0; columnOrder[index] != -1; index++)
+ {
+ headerColumn *cl = &columnList[columnOrder[index]];
+ lvc.pszText = WASABI_API_LNGSTRINGW(cl->column_id);
+
+ lvc.cx = g_view_metaconf->ReadInt(AutoWide(cl->config_name), cl->defWidth);
+ if (lvc.cx < cl->minWidth) lvc.cx = cl->minWidth;
+
+ // update position of the cloud column icon
+ if (columnOrder[index] == MEDIAVIEW_COL_CLOUD)
+ {
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1);
+ lvc.cx = 0;
+ }
+ else
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), index);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)index);
+ lvc.cx = 27;
+ MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &lvc.cx);
+ }
+ }
+
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, 0xFFFF, (LPARAM)&lvc);
+
+ if (sortby == columnOrder[index]) MLSkinnedListView_DisplaySort(hwndList, index, !g_view_metaconf->ReadInt(L"mv_sort_dir", 0));
+ }
+ SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L); // Delete dummy column
+
+ for (index = 0; -1 != columnOrder[index] && MEDIAVIEW_COL_RATING != columnOrder[index]/* && MEDIAVIEW_COL_CLOUD != columnOrder[index]*/; index++);
+ if (-1 != index) SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, index, (LPARAM)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, index, 0L));
+
+ SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L);
+}
+
+static int m_last_selitem = -1;
+static int m_bgupdinfoviewerflag;
+
+extern void add_to_library(HWND wndparent);
+
+static INT_PTR CALLBACK needAddFilesProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ if (AGAVE_API_ITUNES_IMPORTER && AGAVE_API_ITUNES_IMPORTER->iTunesExists())
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IMPORT_ITUNES), TRUE);
+ else
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IMPORT_ITUNES), FALSE);
+
+ SetTimer(hwndDlg, 1, 1000, NULL);
+ return 1;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ case IDCANCEL:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK1)) g_config->WriteInt(L"noshowadddlg", 1);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case ID_ADD_FILES:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ add_to_library(hwndDlg);
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ }
+ break;
+ case IDC_IMPORT_ITUNES:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ if (AGAVE_API_ITUNES_IMPORTER)
+ AGAVE_API_ITUNES_IMPORTER->ImportFromiTunes(hwndDlg);
+
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ if (m_curview_hwnd) PostMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ }
+ break;
+ case IDC_BTN_LINK_PROMO:
+ if (BN_CLICKED == HIWORD(wParam)) ShellExecuteA(plugin.hwndWinampParent, "open", "https://help.winamp.com/hc/articles/8105244490772-Player-Overview", NULL, ".", 0);
+ break;
+ }
+ break;
+ case WM_TIMER:
+ if (g_table && NDE_Table_GetRecordsCount(g_table))
+ {
+ wchar_t buf[512] = {0};
+ StringCchPrintfW(buf, 512, WASABI_API_LNGSTRINGW(IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY), NDE_Table_GetRecordsCount(g_table));
+ SetDlgItemTextW(hwndDlg, IDC_TEXT, buf);
+ SetDlgItemTextW(hwndDlg, ID_ADD_FILES, WASABI_API_LNGSTRINGW(IDS_ADD_MORE));
+ }
+ break;
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ wchar_t wt[123] = {0};
+ int y;
+ RECT r;
+ HPEN hPen, hOldPen;
+ DWORD style;
+ GetDlgItemText(hwndDlg, (INT)wParam, wt, ARRAYSIZE(wt));
+
+ style = (DWORD)GetWindowLongPtrW(di->hwndItem, GWL_STYLE);
+ // draw text
+ SetTextColor(di->hDC, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+
+ memset(&r, 0, sizeof(r));
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_CALCRECT);
+
+
+ if (BS_RIGHT & style) r.left = max(di->rcItem.left+ 2, di->rcItem.right - r.right - 2);
+ else if (BS_LEFT & style) r.left = di->rcItem.left+ 2;
+ else r.left = ((di->rcItem.right - di->rcItem.left - 4) - r.right) / 2;
+
+ if (r.left < di->rcItem.left + 2)
+ {
+ r.left = di->rcItem.left + 2;
+ r.right = di->rcItem.right - 2;
+ }
+ else r.right += r.left;
+ if (r.right > di->rcItem.right - 2) r.right = di->rcItem.right - 2;
+
+
+ if (BS_TOP & style) r.top = di->rcItem.top;
+ else if(BS_VCENTER & style) r.top = ((di->rcItem.bottom - di->rcItem.top - 2) - r.bottom) /2;
+ else r.top = di->rcItem.bottom - 2 - r.bottom;
+
+ if (r.top < di->rcItem.top)
+ {
+ r.top = di->rcItem.top;
+ r.bottom = di->rcItem.bottom - 2;
+ }
+ else r.bottom += r.top;
+ if (r.bottom > di->rcItem.bottom - 2) r.bottom = di->rcItem.bottom - 2;
+
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_WORD_ELLIPSIS);
+
+ // draw underline
+ y = min(di->rcItem.bottom, r.bottom + 1);
+ hPen = CreatePen(PS_SOLID, 0, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ hOldPen = (HPEN) SelectObject(di->hDC, hPen);
+ MoveToEx(di->hDC, r.left, y, NULL);
+ LineTo(di->hDC, r.right, y);
+ SelectObject(di->hDC, hOldPen);
+ DeleteObject(hPen);
+ }
+ }
+ }
+
+ return 0;
+
+};
+
+static void SetStatusText(HWND hwndStatus, LPCWSTR *ppsz, INT count)
+{
+ WCHAR buffer[4096] = {0};
+ if (0 == count || !ppsz)
+ {
+ SetWindowText(hwndStatus, L"");
+ return;
+ }
+ buffer[0] = 0x00;
+ for (int i = 0; i < count; i++)
+ {
+ StringCchCatW(buffer, 4096, ppsz[i]);
+ StringCchCatW(buffer, 4096, L" ");
+ }
+ SetWindowTextW(hwndStatus, buffer);
+
+ StringCchCopyW(oldText, 4096, buffer);
+}
+
+static void SetRating(UINT iItem, INT newRating, HWND hwndList)
+{
+ if (0 == newRating) newRating = -1;
+ if (iItem < (UINT)itemCache.Size)
+ {
+ if (g_table && newRating != itemCache.Items[iItem].rating)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[iItem].filename))
+ {
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, newRating);
+ NDE_Scanner_Post(s);
+ itemCache.Items[iItem].rating = newRating;
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (newRating > 0)
+ {
+ wsprintfW(buf, L"%d", newRating);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(itemCache.Items[iItem].filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (g_table_dirty)
+ {
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ if (newRating == itemCache.Items[iItem].rating && hwndList)
+ {
+ ratingColumn.hwndList = hwndList;
+ ratingColumn.iItem = iItem;
+ ratingColumn.iSubItem = 600;
+ MLRatingColumn_Animate(plugin.hwndLibraryParent, &ratingColumn);
+ ListView_RedrawItems(resultlist.getwnd(), iItem, iItem);
+ // TODO: benski> update the top panes w/o refreshing, if possible
+ // CUT: PostMessage(GetParent(GetParent(hwndList)), WM_APP + 4, (WPARAM)newRating, (LPARAM)1);
+ }
+ }
+}
+
+/////////// Header Messages / Notifications
+static BOOL Header_OnItemChanging(HWND hwndDlg, NMHEADERW *phdr, LRESULT *pResult, UINT uMsg)
+{
+ if (phdr->pitem && (HDI_WIDTH & phdr->pitem->mask))
+ {
+ INT test;
+ test = columnList[columnOrder[phdr->iItem]].minWidth;
+ if (phdr->pitem->cxy < test) phdr->pitem->cxy = test;
+ test = columnList[columnOrder[phdr->iItem]].maxWidth;
+ if (test != UNLIMITED_WIDTH && phdr->pitem->cxy > test) phdr->pitem->cxy = test;
+
+ if (MEDIAVIEW_COL_RATING == columnOrder[phdr->iItem])
+ {
+ RATINGWIDTH rw;
+
+ rw.fStyle = RCS_DEFAULT;
+ rw.width = phdr->pitem->cxy;
+ if (MLRatingColumn_GetWidth(plugin.hwndLibraryParent, &rw)) phdr->pitem->cxy = rw.width;
+ if (0 == phdr->iItem)
+ {
+ RATINGBACKTEXT rbt;
+ rbt.pszText = ratingBackText;
+ rbt.cchTextMax = sizeof(ratingBackText)/sizeof(WCHAR);
+ rbt.nColumnWidth = phdr->pitem->cxy;
+ rbt.fStyle = RCS_DEFAULT;
+ MLRatingColumn_FillBackString(plugin.hwndLibraryParent, &rbt);
+ }
+ else ratingBackText[0] = 0x00;
+ }
+ else if (MEDIAVIEW_COL_CLOUD == columnOrder[phdr->iItem])
+ {
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))
+ phdr->pitem->cxy = 0;
+ else
+ {
+ INT width = phdr->pitem->cxy;
+ if (MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &width))
+ {
+ phdr->pitem->cxy = width;
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+static BOOL Header_OnEndDrag(HWND hwndDlg, NMHEADERW *phdr, LRESULT *pResult)
+{
+ PostMessageW(hwndDlg, IM_SYNCHEADERORDER, 0, (LPARAM)phdr->hdr.hwndFrom);
+ return FALSE;
+}
+
+static BOOL Header_OnRightClick(HWND hwndDlg, NMHDR *pnmh, LRESULT *pResult)
+{
+ HMENU menu = GetSubMenu(g_context_menus, 4);
+ POINT p;
+ GetCursorPos(&p);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, hwndDlg, NULL);
+ switch (r)
+ {
+ case ID_HEADERWND_CUSTOMIZECOLUMNS:
+ customizeColumnsDialog(hwndDlg);
+ break;
+ }
+ return FALSE;
+}
+
+/////////// ListView Messages / Notifications
+static BOOL ListView_OnItemChanged(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ if (pnmv->uNewState & LVIS_SELECTED)
+ {
+ //if (GetFocus()==resultlist.getwnd())
+ {
+ m_last_selitem = pnmv->iItem;
+ KillTimer(hwndDlg, 6600);
+ SetTimer(hwndDlg, 6600, 250, NULL);
+ }
+ }
+ else
+ {
+ if (isMixablePresent)
+ {
+ SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ isMixablePresent = false;
+ }
+ }
+ return FALSE;
+}
+
+static BOOL ListView_OnDoubleClick(HWND hwndDlg, NMITEMACTIVATE *pnmitem)
+{
+ playFiles((!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)), 0);
+ return FALSE;
+}
+
+void EatKeyboard()
+{
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+}
+
+static void Dialog_OnContextMenu(HWND hwndDlg, HWND hwndFrom, int x, int y)
+{
+ if (hwndFrom != resultlist.getwnd())
+ return;
+
+ POINT pt = {x,y};
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = resultlist.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ resultlist.GetItemRect(selected, &itemRect);
+ ClientToScreen(resultlist.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(resultlist.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(resultlist.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(resultlist.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return;
+ }
+
+ HMENU globmenu = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU menu = GetSubMenu(globmenu, 0);
+ int rate_idx = 9;
+ sendto_hmenu = GetSubMenu(menu, 2);
+ rate_hmenu = GetSubMenu(menu, rate_idx);
+
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ s.mode = 0;
+ s.hwnd = 0;
+ s.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ s.ctx[1] = 1;
+ s.build_hMenu = sendto_hmenu;
+ }
+
+ wchar_t *artist = NULL;
+ wchar_t *album = NULL;
+ int n = resultlist.GetSelectionMark();
+ if (n != -1)
+ {
+ artist = itemCache.Items[n].artist;
+ album = itemCache.Items[n].album;
+ wchar_t str[2048] = {0}, str2[128] = {0};
+ // (BigG): Check the ini settings for viewing vs playing and create the menu accordingly
+ // Keeping this here in case we want to use the ini setting:
+ //StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW( (g_viewnotplay != 0) ? IDS_VIEW_ALL_FILES_BY : IDS_PLAY_ALL_FILES_BY), artist ? artist : L"");
+
+ int len = lstrlenW(artist);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", artist);//WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), artist ? artist : L"");
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), artist ? artist : L"");
+ }
+ FixAmps(str, 2048);
+ MENUITEMINFOW mii =
+ {
+ sizeof(MENUITEMINFOW),
+ MIIM_TYPE | MIIM_ID,
+ MFT_STRING,
+ MFS_ENABLED,
+ 0x1234,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ str,
+ 0,
+ };
+
+ if (!(!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)))
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0};
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ InsertMenuItemW(menu, 0, FALSE, &m);
+
+ wchar_t a[100] = {0};
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100);
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_hmenu = CreatePopupMenu();
+ InsertMenuItemW(menu, 0, FALSE, &m);
+ }
+
+ if (artist && artist[0])
+ {
+ InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii);
+ }
+ // (BigG): Check the ini settings for viewing vs playing and create the menu accordingly
+ // Keeping this here in case we want to use the ini setting:
+ len = lstrlenW(album);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", album);
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_FROM), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_FROM), album ? album : L"");
+ }
+ FixAmps(str, 2048);
+ mii.cch = wcslen(str);
+ mii.wID = 0x1235;
+ if (album && album[0])
+ {
+ InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii);
+ }
+
+ {
+ mii.wID = 0xdeadbeef;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii);
+ }
+ }
+ else
+ {
+ EnableMenuItem(menu, ID_MEDIAWND_PLAYSELECTEDFILES, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_MEDIAWND_ENQUEUESELECTEDFILES, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, IDC_REFRESH_METADATA, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_MEDIAWND_REMOVEFROMLIBRARY, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_EDITITEMINFOS, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_MEDIAWND_EXPLOREFOLDER, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_PE_ID3, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, 2, MF_BYPOSITION | MF_GRAYED); //grays the "Add to playlist..." menu
+ EnableMenuItem(menu, rate_idx, MF_BYPOSITION | MF_GRAYED); //grays the "Rate..." menu
+ EnableMenuItem(menu, 13, MF_BYPOSITION | MF_GRAYED); //grays the "Remove..." menu
+ }
+
+ menufucker_t mf = {sizeof(mf),MENU_MEDIAVIEW,menu,0x3000,0x4000,0};
+ mf.extinf.mediaview.list = resultlist.getwnd();
+ mf.extinf.mediaview.items = &itemCache;
+ pluginMessage message_build = {ML_IPC_MENUFUCKER_BUILD,(intptr_t)&mf,0};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_build, ML_IPC_SEND_PLUGIN_MESSAGE);
+
+ Menu_SetRatingValue(rate_hmenu, 0);
+ UpdateMenuItems(hwndDlg, menu, IDR_VIEW_ACCELERATORS);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL);
+
+ pluginMessage message_result = {ML_IPC_MENUFUCKER_RESULT,(intptr_t)&mf,r,0};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_result, ML_IPC_SEND_PLUGIN_MESSAGE);
+
+ switch (r)
+ {
+ case ID_MEDIAWND_PLAYSELECTEDFILES:
+ case ID_MEDIAWND_ENQUEUESELECTEDFILES:
+ playFiles((r == ID_MEDIAWND_ENQUEUESELECTEDFILES), 0);
+ break;
+ case ID_MEDIAWND_SELECTALL:
+ {
+ LVITEM item = {0};
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ }
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ removeSelectedItems(0);
+ break;
+ case ID_EDITITEMINFOS:
+ if (resultlist.GetSelectedCount() > 0)
+ editInfo(hwndDlg);
+ break;
+ case ID_PE_ID3:
+ fileInfoDialogs(hwndDlg);
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)hwndFrom, (LPARAM)TRUE);
+ break;
+ case IDC_REFRESH_METADATA:
+ RefreshMetadata(hwndDlg);
+ break;
+ case 0x1234: // all files from selected artist
+ {
+ wchar_t tmp[2048] = {0};
+ GayStringW escaped;
+ queryStrEscape(artist, escaped);
+
+ // Keeping this here in case we want to use the ini setting later:
+ //if ( g_viewnotplay ) // (BigG): Check to see if we should play or view the current tracks
+ //{
+ StringCchPrintfW(tmp, 2048, L"?artist = \"%s\"", escaped.Get());
+ SetWindowTextW(hwndSearchGlobal, tmp);
+ //}
+ //else // Otherwise do the old behavior and just play it back
+ //{
+ // StringCchPrintfW(tmp, 2048, L"artist = \"%s\"", escaped.Get());
+ // main_playQuery(g_view_metaconf, tmp, 0);
+ //}
+
+ SetWindowTextW(hwndSearchGlobal, tmp);
+ }
+ break;
+ case 0x1235: // all files from selected album
+ {
+ wchar_t tmp[2048] = {0};
+ GayStringW escaped;
+ queryStrEscape(album, escaped);
+
+ // Keeping this here in case we want to use the ini setting later:
+ //if ( g_viewnotplay ) // (BigG): Check to see if we should play or view the current tracks
+ //{
+ StringCchPrintfW(tmp, 2048, L"?album = \"%s\"", escaped.Get());
+ SetWindowTextW(hwndSearchGlobal, tmp);
+ //}
+ //else // Otherwise do the old behavior and just play it back
+ //{
+ // StringCchPrintfW(tmp, 2048, L"album = \"%s\"", escaped.Get());
+ // main_playQuery(g_view_metaconf, tmp, 0);
+ //}
+ }
+ break;
+ case ID_RATE_1:
+ case ID_RATE_2:
+ case ID_RATE_3:
+ case ID_RATE_4:
+ case ID_RATE_5:
+ case ID_RATE_0:
+ {
+ int rate = r - ID_RATE_1 + 1;
+ if (r == ID_RATE_0) rate = 0;
+ int x;
+ int has = 0;
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ for (x = 0; x < itemCache.Size; x ++)
+ {
+ if (resultlist.GetSelected(x))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[x].filename))
+ {
+ has++;
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, rate);
+ NDE_Scanner_Post(s);
+ itemCache.Items[x].rating = rate;
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (rate > 0)
+ {
+ wsprintfW(buf, L"%d", rate);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(itemCache.Items[x].filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (g_table_dirty)
+ {
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (has)
+ {
+ ListView_RedrawItems(resultlist.getwnd(), 0, itemCache.Size - 1);
+ PostMessage(GetParent(hwndDlg), WM_APP + 4, (WPARAM)rate, (LPARAM)0);
+ }
+ }
+ break;
+ case ID_MEDIAWND_EXPLOREFOLDER:
+ exploreItemFolder(hwndDlg);
+ break;
+ case ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES:
+ removeDeadFiles(hwndDlg);
+ break;
+ case ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS:
+ RecycleSelectedItems();
+ break;
+ default:
+ {
+ if (cloud_hmenu && (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER)) // deals with cloud specific menus
+ {
+ // 0 = no change
+ // 1 = add cloud
+ // 2 = add local
+ // 4 = removed
+ int mode = 0;
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode);
+ int n = resultlist.GetSelectionMark();
+ if (n != -1)
+ {
+ switch (mode)
+ {
+ case 1:
+ setCloudValue(&itemCache.Items[n], L"5");
+ break;
+
+ case 2:
+ setCloudValue(&itemCache.Items[n], L"0");
+ break;
+
+ case 4:
+ setCloudValue(&itemCache.Items[n], L"4");
+ break;
+ }
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);
+ }
+ break;
+ }
+
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ // build my data.
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLISTW;
+ itemRecordListW myObj = {0, };
+ copyFilesToItemCacheW(&myObj); // does not dupe strings
+ s.data = (void*) & myObj;
+ LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & s, IPC_LIBRARY_SENDTOMENU);
+ if (result != 1)
+ {
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &myObj);
+ s.data = (void*) & objA;
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+ freeRecordList(&objA);
+ }
+ _aligned_free(myObj.Items);
+ }
+ }
+ break;
+ }
+ }
+
+ if (s.mode)
+ {
+ s.mode = 4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ sendto_hmenu = 0;
+ DestroyMenu(cloud_hmenu);
+ cloud_hmenu = 0;
+ DestroyMenu(globmenu);
+ UpdateWindow(hwndFrom);
+ EatKeyboard();
+}
+
+static BOOL ListView_OnFindItem(HWND hwndDlg, NMLVFINDITEMW *pfi, LRESULT *pResult, UINT uMsg)
+{
+ if (bgThread_Handle) return FALSE;
+
+ int i = pfi->iStart;
+ if (i >= itemCache.Size) i = 0;
+
+ int cnt = itemCache.Size - i;
+ if (pfi->lvfi.flags & LVFI_WRAP) cnt += i;
+
+ int by = g_view_metaconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST);
+
+ while (cnt-- > 0)
+ {
+ itemRecordW *thisitem = itemCache.Items + i;
+ wchar_t tmp[128] = {0};
+ wchar_t *name = 0;
+
+ switch (by)
+ {
+ case MEDIAVIEW_COL_FILENAME:
+ name = thisitem->filename + wcslen(thisitem->filename);
+ while (name >= thisitem->filename && *name != L'/' && *name != L'\\') name--;
+ break;
+ case MEDIAVIEW_COL_FULLPATH:
+ name = thisitem->filename;
+ break;
+ case MEDIAVIEW_COL_EXTENSION:
+ name = PathFindExtensionW(thisitem->filename);
+ if (name && *name)
+ name++;
+ break;
+ case MEDIAVIEW_COL_TITLE: name = thisitem->title; break;
+ case MEDIAVIEW_COL_COMMENT: name = thisitem->comment; break;
+ case MEDIAVIEW_COL_ARTIST: name = thisitem->artist; break;
+ case MEDIAVIEW_COL_ALBUM: name = thisitem->album; break;
+ case MEDIAVIEW_COL_GENRE: name = thisitem->genre; break;
+ case MEDIAVIEW_COL_YEAR:
+ tmp[0] = 0;
+ if (thisitem->year >= 0) StringCchPrintfW(tmp, 128, L"%04d", thisitem->year);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_TRACK:
+ tmp[0] = 0;
+ if (thisitem->track > 0)
+ {
+ if (thisitem->tracks > 0) StringCchPrintfW(tmp, 128, L"%d/%d", thisitem->track, thisitem->tracks);
+ else StringCchPrintfW(tmp, 128, L"%d", thisitem->track);
+ }
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_LENGTH:
+ tmp[0] = 0;
+ if (thisitem->length >= 0) StringCchPrintfW(tmp, 128, L"%d:%02d", thisitem->length / 60, thisitem->length % 60);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_RATING:
+ tmp[0] = 0;
+ if (thisitem->rating > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->rating);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_CLOUD:
+ {
+ tmp[0] = 0;
+ wchar_t *x = getRecordExtendedItem_fast(thisitem, extended_fields.cloud);
+ if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x);
+ name = tmp;
+ break;
+ }
+ case MEDIAVIEW_COL_PLAYCOUNT:
+ tmp[0] = 0;
+ if (thisitem->playcount > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->playcount);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_BITRATE:
+ if (thisitem->bitrate > 0)
+ StringCchPrintfW(name = tmp, 128, L"%d%s", thisitem->bitrate, WASABI_API_LNGSTRINGW(IDS_KBPS));
+ break;
+ case MEDIAVIEW_COL_BPM:
+ if (thisitem->bpm > 0)
+ StringCchPrintfW(name = tmp, 128, L"%d", thisitem->bpm);
+ break;
+ case MEDIAVIEW_COL_TYPE:
+ name = WASABI_API_LNGSTRINGW(thisitem->type ? IDS_VIDEO : IDS_AUDIO);
+ break;
+ case MEDIAVIEW_COL_DISC:
+ tmp[0] = 0;
+ if (thisitem->disc > 0)
+ {
+ if (thisitem->discs > 0)
+ StringCchPrintfW(tmp, 128, L"%d/%d", thisitem->disc, thisitem->discs);
+ else
+ StringCchPrintfW(tmp, 128, L"%d", thisitem->disc);
+ }
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_ALBUMARTIST:
+ name = thisitem->albumartist;
+ break;
+ case MEDIAVIEW_COL_PUBLISHER:
+ name = thisitem->publisher;
+ break;
+ case MEDIAVIEW_COL_COMPOSER:
+ name = thisitem->composer;
+ break;
+ case MEDIAVIEW_COL_ALBUMGAIN:
+ name = thisitem->replaygain_album_gain;
+ break;
+ case MEDIAVIEW_COL_TRACKGAIN:
+ name = thisitem->replaygain_track_gain;
+ break;
+ case MEDIAVIEW_COL_FILESIZE:
+ tmp[0] = 0;
+ if (thisitem->filesize > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->filesize);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_FILETIME:
+ tmp[0] = 0;
+ if (thisitem->filetime > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->filetime);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_LASTUPD:
+ tmp[0] = 0;
+ if (thisitem->lastupd > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->lastupd);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_DATEADDED:
+ {
+ tmp[0] = 0;
+ wchar_t *x = getRecordExtendedItem_fast(thisitem,extended_fields.dateadded);
+ if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x);
+ name = tmp;
+ break;
+ }
+ case MEDIAVIEW_COL_LASTPLAY:
+ tmp[0] = 0;
+ if (thisitem->lastplay > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->lastplay);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_ISPODCAST:
+ {
+ wchar_t * t = getRecordExtendedItem_fast(thisitem, extended_fields.ispodcast);
+ name = WASABI_API_LNGSTRINGW(t && wcscmp(t,L"1") ? IDS_PODCAST : IDS_NON_PODCAST);
+ }
+ break;
+ case MEDIAVIEW_COL_PODCASTCHANNEL:
+ name = getRecordExtendedItem_fast(thisitem,extended_fields.podcastchannel);
+ break;
+ case MEDIAVIEW_COL_PODCASTPUBDATE:
+ {
+ tmp[0] = 0;
+ wchar_t *x = getRecordExtendedItem_fast(thisitem,extended_fields.podcastpubdate);
+ if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x);
+ name = tmp;
+ }
+ break;
+ case MEDIAVIEW_COL_CATEGORY:
+ name = thisitem->category;
+ break;
+ case MEDIAVIEW_COL_DIRECTOR:
+ name = getRecordExtendedItem_fast(thisitem,extended_fields.director);
+ break;
+ case MEDIAVIEW_COL_PRODUCER:
+ name = getRecordExtendedItem_fast(thisitem,extended_fields.producer);
+ break;
+ case MEDIAVIEW_COL_DIMENSION:
+ {
+ tmp[0] = 0;
+ wchar_t *w = getRecordExtendedItem_fast(thisitem,extended_fields.width);
+ wchar_t *h = getRecordExtendedItem_fast(thisitem,extended_fields.height);
+ if (w && *w && h && *h) StringCchPrintfW(tmp, 128, L"%dx%d", _wtoi(w), _wtoi(h));
+ name = tmp;
+ break;
+ }
+ }
+
+ if (!name) name = L"";
+ else SKIP_THE_AND_WHITESPACEW(name)
+
+ if (pfi->lvfi.flags & (4 | LVFI_PARTIAL))
+ {
+ if (!StrCmpNIW(name, pfi->lvfi.psz, wcslen(pfi->lvfi.psz)))
+ {
+ *pResult = i;
+ return TRUE;
+ }
+ }
+ else if (pfi->lvfi.flags & LVFI_STRING)
+ {
+ if (!lstrcmpiW(name, pfi->lvfi.psz))
+ {
+ *pResult = i;
+ return TRUE;
+ }
+ }
+ else
+ {
+ *pResult = i;
+ return TRUE;
+ }
+ if (++i == itemCache.Size) i = 0;
+ }
+ *pResult = i;
+ return TRUE;
+}
+static BOOL ListView_OnGetDispInfo(HWND hwndDlg, NMLVDISPINFOW* pdi, UINT uMsg)
+{
+ LVITEMW *pItem;
+ itemRecordW *pRec;
+
+ pItem = &pdi->item;
+
+ if (bgThread_Handle)
+ {
+ if (0 == pItem->iItem && 0 == pItem->iSubItem && (LVIF_TEXT & pItem->mask))
+ {
+ static char bufpos = 0;
+ static int buflen = 17;
+ static wchar_t buffer[64];//L"Scanning _\0/-\\|";
+ if (!buffer[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_PLAIN,buffer,54);
+ StringCchCatW(buffer,64,L" _");
+ StringCchCatW(buffer+(buflen=lstrlenW(buffer)+1),64,L"/-\\|");
+ buflen+=4;
+ }
+ int pos = buflen - 5;;
+ buffer[pos - 1] = buffer[pos + (bufpos++&3) + 1];
+ pItem->pszText = buffer;
+ }
+ return FALSE;
+ }
+
+ if (pItem->iItem < 0 || pItem->iItem >= itemCache.Size) return FALSE;
+
+ pRec = itemCache.Items + pItem->iItem;
+
+ if (LVIF_TEXT & pItem->mask)
+ {
+ switch (columnOrder[pItem->iSubItem])
+ {
+ case MEDIAVIEW_COL_FILENAME: // show filename (Tho we'll show without the path cause paths are gay)
+ pItem->pszText = PathFindFileNameW(pRec->filename);
+ break;
+ case MEDIAVIEW_COL_FULLPATH: // show filename (Tho we'll show without the path cause paths are gay)
+ pItem->pszText = pRec->filename;
+ break;
+ case MEDIAVIEW_COL_EXTENSION:
+ pItem->pszText = PathFindExtensionW(pRec->filename);
+ if (pItem->pszText && *pItem->pszText) pItem->pszText++;
+ break;
+ case MEDIAVIEW_COL_TITLE:
+ pItem->pszText = pRec->title;
+ break;
+ case MEDIAVIEW_COL_COMMENT:
+ pItem->pszText = pRec->comment;
+ break;
+ case MEDIAVIEW_COL_ALBUM:
+ pItem->pszText = pRec->album;
+ break;
+ case MEDIAVIEW_COL_ARTIST:
+ pItem->pszText = pRec->artist;
+ break;
+ case MEDIAVIEW_COL_TRACK:
+ if (pRec->track > 0)
+ {
+ if (pRec->tracks > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d/%d", pRec->track, pRec->tracks);
+ else StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->track);
+ }
+ break;
+ case MEDIAVIEW_COL_GENRE:
+ pItem->pszText = pRec->genre;
+ break;
+ case MEDIAVIEW_COL_YEAR:
+ if (pRec->year >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%04d", pRec->year);
+ break;
+ case MEDIAVIEW_COL_LENGTH:
+ if (pRec->length >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d:%02d", pRec->length / 60, pRec->length % 60);
+ break;
+ case MEDIAVIEW_COL_RATING:
+ if (0 == pItem->iSubItem && ratingBackText[0]) pItem->pszText = ratingBackText;
+ else if (pRec->rating > 0 && pRec->rating <= 5) _itow(pRec->rating, pItem->pszText, 10);
+ break;
+ case MEDIAVIEW_COL_CLOUD:
+ {
+ wchar_t *t = getRecordExtendedItem_fast(pRec, extended_fields.cloud);
+ if (t && *t) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", _wtoi(t));
+ break;
+ }
+ case MEDIAVIEW_COL_PLAYCOUNT:
+ if (pRec->playcount >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->playcount);
+ else pItem->pszText = L"0";
+ break;
+ case MEDIAVIEW_COL_DISC:
+ if (pRec->disc > 0)
+ {
+ if (pRec->discs > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d/%d", pRec->disc, pRec->discs);
+ else StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->disc);
+ }
+ break;
+ case MEDIAVIEW_COL_ALBUMARTIST:
+ pItem->pszText = pRec->albumartist;
+ break;
+ case MEDIAVIEW_COL_PUBLISHER:
+ pItem->pszText = pRec->publisher;
+ break;
+ case MEDIAVIEW_COL_COMPOSER:
+ pItem->pszText = pRec->composer;
+ break;
+ case MEDIAVIEW_COL_ALBUMGAIN:
+ pItem->pszText = pRec->replaygain_album_gain;
+ break;
+ case MEDIAVIEW_COL_TRACKGAIN:
+ pItem->pszText = pRec->replaygain_track_gain;
+ break;
+ case MEDIAVIEW_COL_TYPE:
+ pItem->pszText = WASABI_API_LNGSTRINGW((pRec->type) ? IDS_VIDEO : IDS_AUDIO);
+ break;
+ case MEDIAVIEW_COL_BITRATE:
+ if (pRec->bitrate > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d%s", pRec->bitrate, WASABI_API_LNGSTRINGW(IDS_KBPS));
+ break;
+ case MEDIAVIEW_COL_BPM:
+ if (pRec->bpm > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->bpm);
+ break;
+ case MEDIAVIEW_COL_FILESIZE:
+ if (pRec->filesize != -1) WASABI_API_LNG->FormattedSizeString(pItem->pszText, pItem->cchTextMax, pRec->filesize);
+ break;
+ case MEDIAVIEW_COL_FILETIME:
+ MakeDateString(pRec->filetime, pItem->pszText, pItem->cchTextMax);
+ break;
+ case MEDIAVIEW_COL_LASTUPD:
+ MakeDateString(pRec->lastupd, pItem->pszText, pItem->cchTextMax);
+ break;
+ case MEDIAVIEW_COL_DATEADDED:
+ {
+ wchar_t * t = getRecordExtendedItem_fast(pRec,extended_fields.dateadded);
+ if (t && *t) MakeDateString(_wtoi64(t), pItem->pszText, pItem->cchTextMax);
+ break;
+ }
+ case MEDIAVIEW_COL_LASTPLAY:
+ if (pRec->lastplay > 0) MakeDateString(pRec->lastplay, pItem->pszText, pItem->cchTextMax);
+ break;
+ case MEDIAVIEW_COL_ISPODCAST:
+ {
+ wchar_t *t = getRecordExtendedItem_fast(pRec, extended_fields.ispodcast);
+ pItem->pszText = WASABI_API_LNGSTRINGW((t && t[0] == L'1' && !t[1]) ? IDS_PODCAST : IDS_NON_PODCAST);
+ break;
+ }
+ case MEDIAVIEW_COL_PODCASTCHANNEL:
+ pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.podcastchannel);
+ break;
+ case MEDIAVIEW_COL_PODCASTPUBDATE:
+ {
+ wchar_t * t = getRecordExtendedItem_fast(pRec,extended_fields.podcastpubdate);
+ if (t && *t) MakeDateString(_wtoi64(t), pItem->pszText, pItem->cchTextMax);
+ break;
+ }
+ case MEDIAVIEW_COL_CATEGORY:
+ pItem->pszText = pRec->category;
+ break;
+ case MEDIAVIEW_COL_DIRECTOR:
+ pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.director);
+ break;
+ case MEDIAVIEW_COL_PRODUCER:
+ pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.producer);
+ break;
+ case MEDIAVIEW_COL_DIMENSION:
+ {
+ wchar_t *w = getRecordExtendedItem_fast(pRec,extended_fields.width);
+ wchar_t *h = getRecordExtendedItem_fast(pRec,extended_fields.height);
+ if (w && *w && h && *h) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%dx%d", _wtoi(w), _wtoi(h));
+ break;
+ }
+ }
+ if (!pItem->pszText) pItem->pszText = L"";
+ }
+ return FALSE;
+}
+static BOOL ListView_OnColumnClick(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ int l_sc = g_view_metaconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST);
+ int l_sd = g_view_metaconf->ReadInt(L"mv_sort_dir", 0);
+ if (columnOrder[pnmv->iSubItem] == l_sc) l_sd = !l_sd;
+ else
+ {
+ /* JF> I like this better when the direction doesnt get reset every
+ time you choose a new column. To revert to old behavior, uncomment
+ this line:
+ l_sd=0;
+ */
+ l_sc = columnOrder[pnmv->iSubItem];
+ }
+
+ g_view_metaconf->WriteInt(L"mv_sort_by", l_sc);
+ g_view_metaconf->WriteInt(L"mv_sort_dir", l_sd);
+ MLSkinnedListView_DisplaySort(pnmv->hdr.hwndFrom, pnmv->iSubItem, !l_sd);
+ sortResults(g_view_metaconf, &itemCache);
+ resultlist.SetVirtualCount(0);
+ resultlist.SetVirtualCount(itemCache.Size); // TODO: we could set a limit here
+ ListView_RedrawItems(pnmv->hdr.hwndFrom, 0, itemCache.Size - 1);
+ return FALSE;
+}
+static BOOL ListView_OnBeginDrag(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ switch (columnOrder[pnmv->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ ratingColumn.hwndList = pnmv->hdr.hwndFrom;
+ ratingColumn.fStyle = RCS_DEFAULT;
+ ratingColumn.iItem = pnmv->iItem;
+ ratingColumn.iSubItem = pnmv->iSubItem;
+ ratingColumn.value = ((UINT)pnmv->iItem < (UINT)itemCache.Size) ? itemCache.Items[pnmv->iItem].rating : 0;
+ MLRatingColumn_BeginDrag(plugin.hwndLibraryParent, &ratingColumn);
+ break;
+ }
+
+ SetCapture(hwndDlg);
+ return FALSE;
+}
+
+static BOOL ListView_OnReturn(HWND hwndDlg, NMHDR *pnmh)
+{
+ SendMessage(hwndDlg, WM_COMMAND, ((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^(!!g_config->ReadInt(L"enqueuedef", 0)))
+ ? IDC_BUTTON_ENQUEUE : IDC_BUTTON_PLAY, 0);
+ return FALSE;
+}
+
+static BOOL ListView_OnCustomDraw(HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static BOOL bDrawFocus;
+ static RECT rcView;
+ static RATINGCOLUMNPAINT ratingColumnPaint;
+ static CLOUDCOLUMNPAINT cloudColumnPaint;
+
+ *pResult = CDRF_DODEFAULT;
+
+ switch (plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ *pResult |= CDRF_NOTIFYITEMDRAW;
+ CopyRect(&rcView, &plvcd->nmcd.rc);
+
+ ratingColumnPaint.fStyle = RCS_DEFAULT;
+ ratingColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ ratingColumnPaint.hdc = plvcd->nmcd.hdc;
+ ratingColumnPaint.prcView = &rcView;
+
+ cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ cloudColumnPaint.hdc = plvcd->nmcd.hdc;
+ cloudColumnPaint.prcView = &rcView;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ *pResult |= CDRF_NOTIFYSUBITEMDRAW;
+ bDrawFocus = (CDIS_FOCUS & plvcd->nmcd.uItemState);
+ if (bDrawFocus)
+ {
+ plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
+ *pResult |= CDRF_NOTIFYPOSTPAINT;
+ }
+ return TRUE;
+ case CDDS_ITEMPOSTPAINT:
+ if (bDrawFocus)
+ {
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc);
+ rc.left += 3;
+ DrawFocusRect(plvcd->nmcd.hdc, &rc);
+ plvcd->nmcd.uItemState |= CDIS_FOCUS;
+ bDrawFocus = FALSE;
+ }
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+
+ case(CDDS_SUBITEM | CDDS_ITEMPREPAINT):
+ switch (columnOrder[plvcd->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ if (bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break;
+ ratingColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ ratingColumnPaint.iSubItem = plvcd->iSubItem;
+ ratingColumnPaint.value = (plvcd->nmcd.dwItemSpec >= 0 && plvcd->nmcd.dwItemSpec < (UINT)itemCache.Size) ? itemCache.Items[plvcd->nmcd.dwItemSpec].rating : 0;
+ ratingColumnPaint.prcItem = &plvcd->nmcd.rc;
+ ratingColumnPaint.rgbBk = plvcd->clrTextBk;
+ ratingColumnPaint.rgbFg = plvcd->clrText;
+
+ if (MLRatingColumn_Paint(plugin.hwndLibraryParent, &ratingColumnPaint))
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ break;
+
+ case MEDIAVIEW_COL_CLOUD:
+ if (bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break;
+
+ int icon = 4;
+ wchar_t *t = getRecordExtendedItem_fast(&itemCache.Items[plvcd->nmcd.dwItemSpec], extended_fields.cloud);
+ if (t && *t) icon = _wtoi(t);
+
+ cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ cloudColumnPaint.iSubItem = plvcd->iSubItem;
+ cloudColumnPaint.value = icon;
+ cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
+ cloudColumnPaint.rgbBk = plvcd->clrTextBk;
+ cloudColumnPaint.rgbFg = plvcd->clrText;
+
+ if (MLCloudColumn_Paint(plugin.hwndLibraryParent, &cloudColumnPaint))
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static BOOL ListView_OnClick(HWND hwnDlg, NMITEMACTIVATE *pnmitem)
+{
+ if (bgThread_Handle) return FALSE;
+
+ if (pnmitem->iItem != -1)
+ {
+ switch (columnOrder[pnmitem->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ ratingColumn.hwndList = pnmitem->hdr.hwndFrom;
+ ratingColumn.ptAction = pnmitem->ptAction;
+ ratingColumn.bRedrawNow = TRUE;
+ ratingColumn.fStyle = RCS_DEFAULT;
+ if (!MLRatingColumn_Click(plugin.hwndLibraryParent, &ratingColumn)) return FALSE;
+ SetRating(ratingColumn.iItem, ratingColumn.value, ratingColumn.hwndList);
+ break;
+
+ case MEDIAVIEW_COL_CLOUD:
+ {
+ RECT itemRect = {0};
+ if (pnmitem->iSubItem)
+ ListView_GetSubItemRect(pnmitem->hdr.hwndFrom, pnmitem->iItem, pnmitem->iSubItem, LVIR_BOUNDS, &itemRect);
+ else
+ {
+ ListView_GetItemRect(pnmitem->hdr.hwndFrom, pnmitem->iItem, &itemRect, LVIR_BOUNDS);
+ itemRect.right = itemRect.left + ListView_GetColumnWidth(pnmitem->hdr.hwndFrom, pnmitem->iSubItem);
+ }
+
+ MapWindowPoints(pnmitem->hdr.hwndFrom, HWND_DESKTOP, (POINT*)&itemRect, 2);
+
+ HMENU cloud_menu = CreatePopupMenu();
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)itemCache.Items[pnmitem->iItem].filename, (intptr_t)&cloud_menu);
+ if (cloud_menu)
+ {
+ int r = DoTrackPopup(cloud_menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, itemRect.right, itemRect.top, pnmitem->hdr.hwndFrom, NULL);
+ if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER)
+ {
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+ int mode = 0; // deals with cloud specific menus
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode);
+ switch (mode)
+ {
+ case 1:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"5");
+ break;
+
+ case 2:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+
+ case 4:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+ }
+
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);
+ }
+ DestroyMenu(cloud_menu);
+ }
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static BOOL ListView_OnHotTrack(HWND hwndDlg, NMLISTVIEW *pnmlv, LRESULT *pResult)
+{
+ UINT iItem;
+
+ if (bgThread_Handle)
+ {
+ pnmlv->iItem = -1;
+ *pResult = TRUE;
+ return TRUE;
+ }
+
+ if (-1 == pnmlv->iItem && 0 == pnmlv->iSubItem)
+ {
+ LVHITTESTINFO lvhit;
+ lvhit.pt = pnmlv->ptAction;
+ SendMessageW(pnmlv->hdr.hwndFrom, LVM_HITTEST, 0, (LPARAM)&lvhit);
+ iItem = lvhit.iItem;
+ }
+ else iItem = pnmlv->iItem;
+
+ switch (columnOrder[pnmlv->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ ratingColumn.hwndList = pnmlv->hdr.hwndFrom;
+ ratingColumn.fStyle = RCS_DEFAULT;
+ ratingColumn.iItem = iItem;
+ ratingColumn.iSubItem = pnmlv->iSubItem;
+ ratingColumn.value = (iItem < (UINT)itemCache.Size) ? itemCache.Items[iItem].rating : 0;
+ ratingColumn.ptAction = pnmlv->ptAction;
+ ratingColumn.bRedrawNow = TRUE;
+ MLRatingColumn_Track(plugin.hwndLibraryParent, &ratingColumn);
+ break;
+ }
+
+ // LVS_EX_ONECLICKACTIVATE enabled - make listview select nothing
+ pnmlv->iItem = -1;
+ *pResult = TRUE;
+ return TRUE;
+}
+
+/////////// Dialog Messages / Notifications
+static void Dialog_OnDisplayChange(HWND hwndDlg)
+{
+ INT i;
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ for (i = 0; -1 != columnOrder[i] && MEDIAVIEW_COL_RATING != columnOrder[i] && MEDIAVIEW_COL_CLOUD != columnOrder[i]; i++);
+ if (-1 != columnOrder[i])
+ {
+ if (hwndList)
+ {
+ INT w = (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L);
+ SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, i, (LPARAM)w);
+ }
+ }
+ LayoutWindows(hwndDlg, TRUE);
+}
+
+static wchar_t tt_buf[256] = {L""};
+static int last_item = -1, last_icon = -1;
+LRESULT pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (uMsg == WM_NOTIFY)
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ switch (l->code)
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ int cloudcol = (int)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == cloudcol)
+ {
+ RECT r = {0};
+ if (lvh.iSubItem)
+ ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r);
+ else
+ {
+ ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS);
+ r.right = r.left + ListView_GetColumnWidth(hwnd, cloudcol);
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2);
+ SetWindowPos(l->hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ int cloudcol = (int)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == cloudcol)
+ {
+ LPNMTTDISPINFOW lpnmtdi = (LPNMTTDISPINFOW)lParam;
+ int icon = 4;
+ wchar_t *t = getRecordExtendedItem_fast(&itemCache.Items[lvh.iItem], extended_fields.cloud);
+ if (t && *t) icon = _wtoi(t);
+
+ if (last_item == lvh.iItem && last_icon == icon)
+ {
+ lpnmtdi->lpszText = tt_buf;
+ return 0;
+ }
+
+ if (icon == 4)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (icon == 5)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOADING_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else
+ {
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRACK_AVAILABLE, tt_buf, ARRAYSIZE(tt_buf));
+
+ nx_string_t *out_devicenames = 0;
+ size_t num_names = mlplugin->MessageProc(0x405, (INT_PTR)itemCache.Items[lvh.iItem].filename, (INT_PTR)&out_devicenames, 0);
+ if (num_names > 0)
+ {
+ for (size_t i = 0; i < num_names; i++)
+ {
+ if (i > 0) StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), L", ");
+ StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), out_devicenames[i]->string);
+ }
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ if (out_devicenames)
+ free(out_devicenames);
+ }
+ }
+ }
+ }
+ last_item = lvh.iItem;
+ last_icon = icon;
+ lpnmtdi->lpszText = tt_buf;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+void Dialog_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL Dialog_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0)
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem(hwndDlg, buttonId);
+ GetWindowRect(buttonHWND, &r);
+ UpdateMenuItems(hwndDlg, menu, IDR_VIEW_ACCELERATORS);
+ MLSkinnedButton_SetDropDownState(buttonHWND, TRUE);
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if (!(flags & BPM_WM_COMMAND)) tpmFlags |= TPM_RETURNCMD;
+ int x = DoTrackPopup(menu, tpmFlags, r.left, r.top, hwndDlg, NULL);
+ if ((flags & BPM_ECHO_WM_COMMAND) && x)
+ SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(x, 0), 0);
+ MLSkinnedButton_SetDropDownState(buttonHWND, FALSE);
+ return x;
+}
+
+static void Dialog_Play(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(g_context_menus2, 0);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ Dialog_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+static INT_PTR Dialog_OnInit(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
+{
+ // benski> this is just going here because it's very likely to get called. will only get compiled in debug mode.
+ assert(sizeof(extra_idsW) / sizeof(*extra_idsW) == sizeof(extra_strsW) / sizeof(*extra_strsW));
+
+ g_displaysearch = !(BOOL)lParam;
+
+ // Set the hwnd for the search to a global only if the multipane view hasnt set it yet, as it takes precedence to which box gets populated with the query
+ //if (!IsWindow(hwndSearchGlobal))
+ hwndSearchGlobal = GetDlgItem(hwndDlg, IDC_QUICKSEARCH);
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ HWND hwndList;
+ FLICKERFIX ff;
+ INT index;
+ INT ffcl[] = { IDC_CLEAR,
+ IDC_BUTTON_PLAY,
+ IDC_BUTTON_ENQUEUE,
+ IDC_BUTTON_MIX,
+ IDC_BUTTON_INFOTOGGLE,
+ IDC_BUTTON_CREATEPLAYLIST,
+ IDC_MIXABLE,
+ IDC_MEDIASTATUS,
+ };
+
+ m_hwnd = hwndDlg;
+ m_bgupdinfoviewerflag = 0;
+ columnOrder[0] = -1;
+ last_item = -1;
+ tt_buf[0] = 0;
+
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ int64_t *out_ids = 0;
+ nx_string_t *out_filenames = 0;
+ size_t num_files = mlplugin->MessageProc(0x404, (INT_PTR)&out_filenames, (INT_PTR)&out_ids, 0xDEADBEEF);
+ for (size_t i = 0; i < num_files; i++)
+ {
+ cloudFiles.push_back((wchar_t *)out_filenames[i]->string);
+ }
+ if (out_filenames)
+ {
+ free(out_filenames);
+ out_filenames = 0;
+ }
+ if (out_ids)
+ {
+ free(out_ids);
+ out_ids = 0;
+ }
+
+ HWND ml_pmp_window = FindWindowW(L"ml_pmp_window", NULL);
+ if (IsWindow(ml_pmp_window))
+ {
+ SendMessage(ml_pmp_window, WM_PMP_IPC, (WPARAM)&cloudUploading, PMP_IPC_GETCLOUDTRANSFERS);
+ wchar_t a[32] = {0};
+ StringCchPrintfW(a, 32, L"%d", cloudUploading.size());
+ }
+ }
+ }
+ }
+
+ EnterCriticalSection(&g_db_cs);
+ if (!m_media_scanner) m_media_scanner = NDE_Table_CreateScanner(g_table);
+ LeaveCriticalSection(&g_db_cs);
+
+ itemCache.Items = 0;
+ itemCache.Alloc = 0;
+ itemCache.Size = 0;
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (IsWindow(hwndList))
+ {
+ resultlist.setwnd(hwndList);
+ resultlist.ForceUnicode();
+
+ DWORD styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE; /*LVS_EX_ONECLICKACTIVATE - needed to hottracking work prior WinXp */
+ SendMessageW(hwndList, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+
+ HWND hwndHeader = (HWND)SendMessage(hwndList, LVM_GETHEADER, 0, 0L);
+ if (IsWindow(hwndHeader)) SetWindowLongPtrW(hwndHeader, GWLP_ID, IDC_LIST2HEADER);
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = hwndList;
+ skin.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ else resultlist.setwnd(NULL);
+
+ ff.mode = FFM_ERASEINPAINT;
+ for (index = 0; index < sizeof(ffcl) / sizeof(INT); index++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]);
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+
+ if (!g_displaysearch) // disable search box
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CLEAR), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_SEARCHCAPTION), SW_HIDE);
+ }
+
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1)
+ memcpy(columnOrder, defColumnOrder, sizeof(defColumnOrder));
+ else
+ memcpy(columnOrder, defColumnOrderCloud, sizeof(defColumnOrderCloud));
+ //Read the column order
+
+ Dialog_OnDisplayChange(hwndDlg);
+
+ int cloudcol = -1;
+ int l = g_view_metaconf->ReadInt(L"nbcolumns", 0);
+ if (l)
+ {
+ if (l > MAX_COLUMN_ORDER - 1) l = MAX_COLUMN_ORDER - 1;
+ index = 0;
+ for (; index < l; index++)
+ {
+ wchar_t tmp[128] = {0};
+ StringCchPrintfW(tmp, 128, L"column%d", index);
+ int v = g_view_metaconf->ReadInt(tmp, 0);
+ if (v == MEDIAVIEW_COL_CLOUD) cloudcol = index;
+ if (v < 0 || v >= MEDIAVIEW_COL_NUMS) v = 0;
+ columnOrder[index] = (BYTE)v;
+ }
+ columnOrder[index] = -1;
+ }
+
+ if (cloudcol == -1 && !g_view_metaconf->ReadInt(L"cloud", 1))
+ {
+ g_view_metaconf->WriteInt(L"cloud", 1);
+ for(int i = l; i != 0; i--)
+ {
+ columnOrder[i+1] = columnOrder[i];
+ if (i == 3)
+ {
+ columnOrder[i] = MEDIAVIEW_COL_CLOUD;
+ break;
+ }
+ }
+ }
+
+ if (!GetPropW(hwndList, L"pmp_list_proc")) {
+ SetPropW(hwndList, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(hwndList, GWLP_WNDPROC, (LONG_PTR)pmp_listview));
+ }
+ initColumnsHeader(hwndList);
+
+ char *pszTextA = (g_config->ReadInt(L"remembersearch", 0)) ? g_view_metaconf->ReadString("lastquery_utf8", "") : "";
+ AutoWide queryUnicode(pszTextA, CP_UTF8);
+ SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, queryUnicode);
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ doQuery(hwndDlg, queryUnicode, 0);
+
+ updateInfoText(hwndDlg);
+
+ search_oldWndProc = (WNDPROC)SetWindowLongPtrW(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), GWLP_WNDPROC, (LONG_PTR)search_newWndProc);
+
+ if (g_table && !NDE_Table_GetRecordsCount(g_table) && !g_config->ReadInt(L"noshowadddlg", 0))
+ {
+ SetTimer(hwndDlg, 5050, 1000, NULL);
+ }
+
+ groupBtn = g_config->ReadInt(L"groupbtn", 1);
+ enqueuedef = (g_config->ReadInt(L"enqueuedef", 0) == 1);
+
+ /// detect predixis
+ predixisExist = FALSE;
+ //predixis out - begin
+ //pluginMessage p = {ML_MSG_PDXS_STATUS, (INT_PTR)"test", 0, 0};
+ //pszTextA = (char *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ //predixisExist = (pszTextA != NULL && pszTextA[0] != 0);
+ //predixis out - end
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_MIX, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_local"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_MIX, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_MIX), (!customAllowed ? SW_HIDE : SW_SHOW));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_MIXABLE), (!(predixisExist & 1) ? SW_HIDE : SW_SHOW));
+
+ MLSKINWINDOW m = {0};
+ m.hwndToSkin = hwndDlg;
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+
+ m.skinType = SKINNEDWND_TYPE_BUTTON;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0);
+
+ const int buttonids[] = {IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX};
+ for (size_t i=0;i!=sizeof(buttonids)/sizeof(buttonids[0]);i++)
+ {
+ m.hwndToSkin = GetDlgItem(hwndDlg, buttonids[i]);
+ if (IsWindow(m.hwndToSkin)) MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+
+ const int buttonidz[] = {IDC_SEARCHCAPTION, IDC_QUICKSEARCH, IDC_MEDIASTATUS, IDC_CLEAR, IDC_BUTTON_INFOTOGGLE, IDC_BUTTON_CREATEPLAYLIST, IDC_MIXABLE};
+ for (size_t i=0;i!=sizeof(buttonidz)/sizeof(buttonidz[0]);i++)
+ {
+ m.hwndToSkin = GetDlgItem(hwndDlg, buttonidz[i]);
+ if (IsWindow(m.hwndToSkin)) MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+
+ Dialog_UpdateButtonText(hwndDlg, enqueuedef);
+ return FALSE;
+}
+
+static BOOL Dialog_OnNotify(HWND hwndDlg, INT idCtrl, NMHDR* pnmh, LRESULT *pResult)
+{
+ switch (pnmh->idFrom)
+ {
+ case IDC_LIST2:
+ switch (pnmh->code)
+ {
+ case LVN_ITEMCHANGED: return ListView_OnItemChanged(hwndDlg, (NMLISTVIEW*)pnmh);
+ case NM_DBLCLK: return ListView_OnDoubleClick(hwndDlg, (NMITEMACTIVATE*)pnmh);
+ case LVN_ODFINDITEMA:
+ case LVN_ODFINDITEMW: return ListView_OnFindItem(hwndDlg, (NMLVFINDITEMW*)pnmh, pResult, pnmh->code);
+ case LVN_GETDISPINFOA:
+ case LVN_GETDISPINFOW: return ListView_OnGetDispInfo(hwndDlg, (NMLVDISPINFOW*)pnmh, pnmh->code);
+ case LVN_COLUMNCLICK: return ListView_OnColumnClick(hwndDlg, (NMLISTVIEW*)pnmh);
+ case LVN_BEGINDRAG: return ListView_OnBeginDrag(hwndDlg, (NMLISTVIEW*)pnmh);
+ case NM_RETURN: return ListView_OnReturn(hwndDlg, pnmh);
+ case NM_CUSTOMDRAW: return ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)pnmh, pResult);
+ case LVN_HOTTRACK: return ListView_OnHotTrack(hwndDlg, (NMLISTVIEW*)pnmh, pResult);
+ case NM_CLICK: return ListView_OnClick(hwndDlg, (NMITEMACTIVATE*)pnmh);
+ }
+ break;
+ case IDC_LIST2HEADER:
+ switch (pnmh->code)
+ {
+ case NM_RCLICK: return Header_OnRightClick(hwndDlg, pnmh, pResult);
+ case HDN_ENDDRAG: return Header_OnEndDrag(hwndDlg, (NMHEADERW*)pnmh, pResult);
+ case HDN_ITEMCHANGINGA:
+ case HDN_ITEMCHANGINGW: return Header_OnItemChanging(hwndDlg, (NMHEADERW*)pnmh, pResult, pnmh->code);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static void Dialog_OnInitMenuPopup(HWND hwndDlg, HMENU hMenu, UINT nIndex, BOOL bSysMenu)
+{
+ if (hMenu && hMenu == s.build_hMenu && s.mode == 1)
+ {
+ myMenu = TRUE;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ s.mode = 2;
+ myMenu = FALSE;
+ }
+ if (rate_hmenu && hMenu == rate_hmenu)
+ {
+ int x;
+ int sel = 0;
+ for (x = 0; x < itemCache.Size; x ++)
+ {
+ if (resultlist.GetSelected(x))
+ {
+ int s = itemCache.Items[x].rating;
+ if (s == sel || !sel) sel = s;
+ if (s != sel) break;
+ }
+ }
+ if (-1 == sel) sel = 0;
+ Menu_SetRatingValue(rate_hmenu, sel);
+ }
+ if (cloud_hmenu && hMenu == cloud_hmenu)
+ {
+ int n = resultlist.GetSelectionMark();
+ if (n != -1 && !GetMenuItemCount(hMenu))
+ {
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)itemCache.Items[n].filename, (intptr_t)&cloud_hmenu);
+ }
+ }
+}
+
+static void Dialog_OnMouseMove(HWND hwndDlg, UINT nFlags, POINTS pts)
+{
+ if (GetCapture() == hwndDlg)
+ {
+ mlDropItemStruct m = {0};
+
+ POINTSTOPOINT(m.p, pts);
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&m.p, 1);
+
+ if (MLRatingColumn_Drag(plugin.hwndLibraryParent, &m.p)) return;
+
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+
+ }
+}
+static void Dialog_OnLButtonUp(HWND hwndDlg, UINT nFlags, POINTS pts)
+{
+ if (GetCapture() == hwndDlg)
+ {
+ mlDropItemStruct m = {0};
+
+ ReleaseCapture();
+
+ POINTSTOPOINT(m.p, pts);
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&m.p, 1);
+
+ ratingColumn.bCanceled = FALSE;
+ ratingColumn.ptAction = m.p;
+ ratingColumn.bRedrawNow = TRUE;
+
+ if (MLRatingColumn_EndDrag(plugin.hwndLibraryParent, &ratingColumn))
+ {
+ SetRating(ratingColumn.iItem, ratingColumn.value, ratingColumn.hwndList);
+ return;
+ }
+
+ m.type = ML_TYPE_ITEMRECORDLISTW;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+
+ if (m.result > 0) // try itemRecordListW
+ {
+ itemRecordListW myObj = {0, };
+ copyFilesToItemCacheW(&myObj);
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & myObj;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ _aligned_free(myObj.Items); // DO NOT empty this object, cause it doesnt own its data
+ }
+ else // if it didn't work, fall back to itemRecordList
+ {
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ m.result = 0;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result > 0)
+ {
+ itemRecordListW myObj = {0, };
+ copyFilesToItemCacheW(&myObj);
+
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &myObj);
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & objA;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ emptyRecordList(&objA);
+ freeRecordList(&objA);
+ _aligned_free(myObj.Items); // DO NOT empty this object, cause it doesnt own its data
+ }
+ }
+ }
+}
+
+class ItemRecordPlaylist : public ifc_playlist
+{
+public:
+ ItemRecordPlaylist(const itemRecordListW *_list)
+ {
+ list = _list;
+ }
+
+private:
+ size_t GetNumItems()
+ {
+ return list->Size;
+ }
+
+ size_t GetItem(size_t item, wchar_t *filename, size_t filenameCch)
+ {
+ if (item < (size_t)list->Size && list->Items[item].filename)
+ {
+ StringCchCopyW(filename, filenameCch, list->Items[item].filename);
+ return 1;
+ }
+ return 0;
+ }
+
+ size_t GetItemTitle(size_t item, wchar_t *title, size_t titleCch)
+ {
+ if (item < (size_t)list->Size && list->Items[item].filename)
+ {
+ TAG_FMT_EXT(list->Items[item].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&list->Items[item], title, titleCch, 0);
+ return 1;
+ }
+ return 0;
+ }
+
+ int GetItemLengthMilliseconds(size_t item)
+ {
+ if (item < (size_t)list->Size && list->Items[item].length>=0)
+ {
+ return list->Items[item].length * 1000;
+ }
+ return -1000;
+ }
+
+private:
+ const itemRecordListW *list;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS ItemRecordPlaylist
+START_DISPATCH;
+CB(IFC_PLAYLIST_GETNUMITEMS, GetNumItems)
+CB(IFC_PLAYLIST_GETITEM, GetItem)
+CB(IFC_PLAYLIST_GETITEMTITLE, GetItemTitle)
+CB(IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds)
+END_DISPATCH;
+#undef CBCLASS
+
+
+static void Dialog_OnCommand(HWND hwndDlg, UINT idCtrl, INT nCode, HWND hwndCtrl)
+{
+ if (GetFocus() != hwndSearchGlobal)
+ {
+ switch (idCtrl)
+ {
+ case IDC_CLEAR:
+ SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, L"");
+ break;
+ case IDC_BUTTON_INFOTOGGLE:
+ updateInfoText(hwndDlg, TRUE);
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE);
+ break;
+ case IDC_QUICKSEARCH:
+ if (nCode == EN_CHANGE)
+ {
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg, UPDATE_QUERY_TIMER_ID, g_querydelay, NULL);
+ }
+ break;
+ case ID_AUDIOWND_PLAYSELECTION:
+ case ID_QUERYWND_PLAYQUERY:
+ case IDC_BUTTON_PLAY:
+ case ID_MEDIAWND_PLAYSELECTEDFILES:
+ case ID_AUDIOWND_ENQUEUESELECTION:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_MEDIAWND_ENQUEUESELECTEDFILES:
+ case IDC_BUTTON_MIX:
+ {
+ if (nCode == MLBN_DROPDOWN)
+ {
+ Dialog_Play(hwndDlg, hwndCtrl, idCtrl);
+ }
+ else
+ {
+ int action;
+ if (idCtrl == IDC_BUTTON_PLAY || idCtrl == ID_MEDIAWND_PLAYSELECTEDFILES || idCtrl == ID_AUDIOWND_PLAYSELECTION)
+ {
+ action = (nCode == 1) ? g_config->ReadInt(L"enqueuedef", 0) == 1 : 0;
+ }
+ else if (idCtrl == IDC_BUTTON_ENQUEUE || idCtrl == ID_MEDIAWND_ENQUEUESELECTEDFILES || idCtrl == ID_AUDIOWND_ENQUEUESELECTION)
+ {
+ action = (nCode == 1) ? g_config->ReadInt(L"enqueuedef", 0) != 1 : 1;
+ }
+ else
+ break;
+
+ int i, l = itemCache.Size;
+ for (i = 0; i < l; i++) if (resultlist.GetSelected(i)) break;
+ playFiles(action/*idCtrl == IDC_BUTTON_ENQUEUE*/, i == l);
+ }
+ }
+ break;
+ case IDC_BUTTON_CREATEPLAYLIST:
+#if 0
+ // TODO consider exposing this option somehow...
+ if (AGAVE_API_PLAYLISTMANAGER) // This is the old Create Playlist button code
+ {
+ wchar_t fn[MAX_PATH] = {0};
+ wchar_t dir[MAX_PATH] = {0};
+ GetTempPathW(MAX_PATH,dir);
+ GetTempFileNameW(dir,L"ml_playlist",0,fn);
+ wcscat(fn,L".m3u8");
+
+ ItemRecordPlaylist playlist(&itemCache);
+ if (AGAVE_API_PLAYLISTMANAGER->Save(fn, &playlist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ mlAddPlaylist p={sizeof(p),NULL,fn,PL_FLAGS_IMPORT,-1,-1};
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&p,ML_IPC_PLAYLIST_ADD);
+ DeleteFileW(fn);
+ }
+ }
+#endif
+ if (AGAVE_API_PLAYLIST_GENERATOR)
+ {
+ const int number_selected = resultlist.GetSelectedCount(); // Total selected
+
+ if (number_selected > 0)
+ {
+ itemRecordListW recordList; // Record list
+ itemRecordW *records = new itemRecordW[number_selected]; // Array of records
+
+ int selectedRecordcounter = 0;
+ for (int i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i)) // See if the current item is selected or not
+ {
+ records[selectedRecordcounter] = itemCache.Items[i]; // If its selected then add it to our itemlist
+ selectedRecordcounter++;
+ }
+ }
+
+ recordList.Size = selectedRecordcounter; // Set the correct size of the record list
+ recordList.Items = records; // Set the array of records to the record list
+
+ AGAVE_API_PLAYLIST_GENERATOR->GeneratePlaylist(hwndDlg, &recordList); // Call the playlist API with the list of selected records
+
+ delete [] records; // Free up the array of records
+ }
+ else
+ {
+ wchar_t title[64] = {0};
+ MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_ERROR_PLG_SELECT_TRACKS), WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_PLAYLIST_GENERATOR,title,64), MB_OK | MB_ICONINFORMATION);
+ }
+ }
+ break;
+#if 0
+ case IDC_BUTTON_MIX:
+ {
+ itemRecordList list;
+ int i;
+ int ct = 0;
+ for (i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ ct++;
+ }
+ }
+ ZeroMemory(&list, sizeof(itemRecordList));
+ allocRecordList(&list, ct, 1);
+ list.Size = ct;
+ ct = 0;
+ for (i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ convertRecord(&list.Items[ct], &itemCache.Items[i]);
+ ct++;
+ }
+ }
+ pluginMessage p = {ML_MSG_PDXS_MIX, (INT_PTR) &list, 0, 0};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE);
+ emptyRecordList(&list);
+ freeRecordList(&list);
+ }
+ break;
+#endif
+ case ID_MEDIAWND_SELECTALL:
+ {
+ LVITEM item = {0};
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(resultlist.getwnd(), LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ }
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ removeSelectedItems(0);
+ break;
+ case ID_EDITITEMINFOS:
+ if (resultlist.GetSelectedCount() > 0)
+ editInfo(hwndDlg);
+ break;
+ case ID_PE_ID3:
+ fileInfoDialogs(hwndDlg);
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)resultlist.getwnd(), (LPARAM)TRUE);
+ break;
+ case IDC_REFRESH_METADATA:
+ RefreshMetadata(hwndDlg);
+ break;
+ case ID_MEDIAWND_EXPLOREFOLDER:
+ exploreItemFolder(hwndDlg);
+ break;
+ }
+ }
+ else
+ {
+ switch (idCtrl)
+ {
+ case IDC_QUICKSEARCH:
+ if (nCode == EN_CHANGE)
+ {
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg, UPDATE_QUERY_TIMER_ID, g_querydelay, NULL);
+ }
+ break;
+ case ID_MEDIAWND_SELECTALL:
+ SendMessageW(hwndSearchGlobal, EM_SETSEL, 0, -1);
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ {
+ DWORD start = -1, end = -1;
+ SendMessageW(hwndSearchGlobal, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
+ if (start != -1)
+ {
+ if (start == end)
+ {
+ SendMessageW(hwndSearchGlobal, EM_SETSEL, start, end + 1);
+ }
+ SendMessageW(hwndSearchGlobal, EM_REPLACESEL, TRUE, (LPARAM)"");
+ SendMessageW(hwndSearchGlobal, EM_SETSEL, start, start);
+ }
+ }
+ break;
+ }
+ }
+}
+
+static void Dialog_OnTimer(HWND hwndDlg, UINT_PTR idEvent, TIMERPROC fnTimer)
+{
+ switch (idEvent)
+ {
+ case 5050:
+ KillTimer(hwndDlg, 5050);
+ WASABI_API_DIALOGBOXW(IDD_NEEDADDFILES, hwndDlg, needAddFilesProc);
+ PostMessage(GetParent(hwndDlg), WM_APP + 1, (WPARAM)0, (LPARAM)0);
+ break;
+ case 6600:
+ KillTimer(hwndDlg, idEvent);
+ if (m_last_selitem >= 0 && m_last_selitem < itemCache.Size)
+ {
+ if (predixisExist & 1)
+ {
+ // Only single seeds are supported currently
+ if (resultlist.GetSelectedCount() == 1 && isMixable(itemCache.Items[m_last_selitem]))
+ {
+ SetDlgItemTextW(hwndDlg, IDC_MIXABLE, WASABI_API_LNGSTRINGW(IDS_MIXABLE));
+ isMixablePresent = true;
+ }
+ else SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ }
+ SendMessageW(GetParent(hwndDlg), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)itemCache.Items[m_last_selitem].filename);
+ m_last_selitem = -1;
+ }
+ break;
+ case 123:
+ if (bgThread_Handle)
+ {
+ HWND hwndList;
+ hwndList = resultlist.getwnd();
+ if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ ListView_RedrawItems(hwndList, 0, 0);
+ //UpdateWindow(hwndList);
+ }
+ break;
+ case UPDATE_QUERY_TIMER_ID:
+ {
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ wchar_t buf[2048] = {0};
+ GetWindowTextW(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), buf, ARRAYSIZE(buf));
+ doQuery(hwndDlg, buf);
+ }
+ break;
+ case UPDATE_RESULT_LIST_TIMER_ID:
+ {
+ ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+ }
+ break;
+ }
+}
+
+static void Dialog_OnDestroy(HWND hwndDlg)
+{
+ HWND hwndList;
+ INT i, j;
+ wchar_t buf[2048] = {0};
+
+ bgQuery_Stop();
+
+ GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ g_view_metaconf->WriteString("lastquery_utf8", AutoChar(buf, CP_UTF8));
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (hwndList && IsWindow(hwndList))
+ {
+ for (i = 0; columnOrder[i] != -1; i++)
+ {
+ headerColumn *cl = &columnList[columnOrder[i]];
+ g_view_metaconf->WriteInt(AutoWide(cl->config_name), SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L));
+ }
+ }
+
+ //Save the column order
+ for (i = 0; columnOrder[i] != -1; i++);
+ g_view_metaconf->WriteInt(L"nbcolumns", i);
+ for (j = 0; j < i; j++)
+ {
+ wchar_t tmp[128] = {0};
+ StringCchPrintfW(tmp, 128, L"column%d", j);
+ g_view_metaconf->WriteInt(tmp, columnOrder[j]);
+ }
+
+ freeRecordList(&itemCache);
+ itemCache.Items = 0;
+ itemCache.Alloc = 0;
+ itemCache.Size = 0;
+
+ cloudFiles.clear();
+ cloudUploading.clear();
+
+ hwndSearchGlobal = 0; // Set the hwnd for the search to a global to null so we know we are not in the single pane view
+}
+
+static void Dialog_OnWindowPosChanged(HWND hwndDlg, WINDOWPOS *pwp)
+{
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & pwp->flags) || (SWP_FRAMECHANGED & pwp->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & pwp->flags), 0 != (SWP_SHOWWINDOW & pwp->flags));
+ }
+}
+
+static void Dialog_OnSyncHeaderOrder(HWND hwndDlg, HWND hwndHeader)
+{
+ LVCOLUMNW column = {0};
+ wchar_t buffer[128] = {0};
+ signed char tempOrder[MAX_COLUMN_ORDER] = {0};
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ if (!hwndList) return;
+
+ CopyMemory(tempOrder, columnOrder, sizeof(tempOrder)/sizeof(signed char));
+
+ column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_IMAGE;
+ column.cchTextMax = sizeof(buffer) /sizeof(wchar_t);
+
+ SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L);
+
+ INT sort = MLSkinnedListView_GetSort(hwndList);
+ INT count = (INT)SendMessageW(hwndHeader, HDM_GETITEMCOUNT, 0, 0L);
+ if (count > 0)
+ {
+ INT index = count + 1, *pOrder = (INT*)calloc(1, sizeof(INT)*count);
+ if (pOrder && SendMessageW(hwndList, LVM_GETCOLUMNORDERARRAY, count, (LPARAM)pOrder))
+ {
+ INT order;
+ for (order = 0; order < count; order++)
+ {
+ column.pszText = buffer;
+ if (!SendMessageW(hwndList, LVM_GETCOLUMNW, pOrder[order], (LPARAM)&column)) continue;
+ column.iOrder = order;
+
+ // update position of the cloud column icon
+ if (tempOrder[pOrder[order]] == MEDIAVIEW_COL_CLOUD)
+ {
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1);
+ column.cx = 0;
+ }
+ else
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), order);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)order);
+ column.cx = 27;
+ MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &column.cx);
+ }
+ }
+
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, index++, (LPARAM)&column);
+ columnOrder[order] = tempOrder[pOrder[order]];
+
+ if (LOWORD(sort) == pOrder[order]) MLSkinnedListView_DisplaySort(hwndList, order, HIWORD(sort));
+ }
+ for (order = 0; order < count; order++) SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L);
+ }
+
+ for (index = 0; -1 != columnOrder[index] && MEDIAVIEW_COL_RATING != columnOrder[index] && MEDIAVIEW_COL_CLOUD != columnOrder[index]; index++);
+ if (-1 != columnOrder[index])
+ {
+ INT w = (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, index, 0L);
+ SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, index, (LPARAM)w);
+ }
+
+ if (pOrder) free(pOrder);
+ }
+
+ SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L);
+}
+
+static void Window_OnQueryFileInfo(HWND hwnd)
+{
+ INT index;
+ HWND hwndList = GetDlgItem(hwnd, IDC_LIST2);
+ index = (hwndList) ? (INT)SendMessage(hwndList, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_FOCUSED) : -1;
+ SendMessageW(GetParent(hwnd), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)((-1 != index && index < itemCache.Size) ? itemCache.Items[index].filename : L""));
+}
+
+void Window_OnDropFiles(HWND hwndDlg, HDROP hdrop)
+{
+ wchar_t temp[1024] = {0};
+ int y = DragQueryFileW(hdrop, 0xffffffff, temp, 1024);
+ if (y > 0)
+ {
+ wchar_t **paths = (wchar_t **)calloc(y, sizeof(wchar_t *));
+ int *guesses = (int *)calloc(y, sizeof(int));
+ int *metas = (int *)calloc(y, sizeof(int));
+ int *recs= (int *)calloc(y, sizeof(int));
+ if (paths && guesses && metas && recs)
+ {
+ size_t count=0;
+ for (int x = 0; x < y; x ++)
+ {
+ DragQueryFileW(hdrop, x, temp, 1024);
+ int guess = -1, meta = -1, rec = 1;
+ // do this for normal media drops
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(temp, &plCB))
+ {
+ autoscan_add_directory(temp, &guess, &meta, &rec, 0);
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ paths[count] = _wcsdup(temp);
+ guesses[count]=guess;
+ metas[count]=meta;
+ recs[count]=rec;
+ count++;
+ }
+ }
+ DragFinish(hdrop);
+ Scan_ScanFolders(hwndDlg, count, paths, guesses, metas, recs);
+ if (IsWindow(m_curview_hwnd)) SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ }
+ else
+ {
+ free(paths);
+ free(guesses);
+ free(metas);
+ free(recs);
+ }
+ }
+ else DragFinish(hdrop);
+}
+
+
+INT_PTR CALLBACK view_mediaDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ BOOL a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+
+ switch (uMsg)
+ {
+ case WM_INITMENUPOPUP: Dialog_OnInitMenuPopup(hwndDlg, (HMENU)wParam, LOWORD(lParam), HIWORD(lParam)); break;
+ case WM_DISPLAYCHANGE: Dialog_OnDisplayChange(hwndDlg); break;
+ case WM_INITDIALOG: return Dialog_OnInit(hwndDlg, (HWND)wParam, lParam);
+ case WM_MOUSEMOVE: Dialog_OnMouseMove(hwndDlg, (UINT)wParam, MAKEPOINTS(lParam)); break;
+ case WM_LBUTTONUP: Dialog_OnLButtonUp(hwndDlg, (UINT)wParam, MAKEPOINTS(lParam)); break;
+ case WM_DROPFILES: Window_OnDropFiles(hwndDlg, (HDROP)wParam);
+ case WM_COMMAND: Dialog_OnCommand(hwndDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ case WM_TIMER: Dialog_OnTimer(hwndDlg, (UINT_PTR) wParam, (TIMERPROC)lParam); break;
+ case WM_DESTROY: Dialog_OnDestroy(hwndDlg); break;
+ case WM_WINDOWPOSCHANGED: Dialog_OnWindowPosChanged(hwndDlg, (WINDOWPOS*)lParam); break;
+ case WM_ERASEBKGND: return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ case WM_CONTEXTMENU:
+ Dialog_OnContextMenu(hwndDlg, (HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ return 0;
+ case WM_NOTIFY:
+ {
+ LRESULT result;
+ result = 0L;
+ if (Dialog_OnNotify(hwndDlg, (INT)wParam, (NMHDR*)lParam, &result))
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return TRUE;
+ }
+ }
+ break;
+ case IM_SYNCHEADERORDER: Dialog_OnSyncHeaderOrder(hwndDlg, (HWND)lParam); break;
+ case WM_APP + 3: // send by bgthread
+ if (wParam == 0x666) m_bgupdinfoviewerflag = 1;
+ else if (wParam == 0x69)
+ {
+ bgQuery_Stop();
+
+ resultlist.SetVirtualCount(0);
+ resultlist.SetVirtualCount(itemCache.Size); // TODO: we could set a limit here
+ ListView_RedrawItems(resultlist.getwnd(), 0, itemCache.Size - 1);
+ UpdateWindow(resultlist.getwnd());
+
+ __int64 total_len_bytes = bg_total_len_bytes;
+
+ int total_length_s = (int)bg_total_len_s & 0x7FFFFFFF;
+ wchar_t buffer[4*64] = {0};
+ wchar_t *pb[4] = {0};
+ for (int i = 0; i < 4; i++) pb[i] = buffer + i * 64;
+
+
+ int index(0);
+
+ StringCchPrintfW(pb[index], 64, L"%d %s", itemCache.Size,
+ WASABI_API_LNGSTRINGW(itemCache.Size == 1 ? IDS_ITEM : IDS_ITEMS));
+ index++;
+
+ if (itemCache.Size)
+ {
+ int uncert = 0; //bg_total_len_s>>31;
+ if (total_length_s < 60*60) StringCchPrintfW(pb[index], 64, L"[%s%u:%02u]", uncert ? L"~" : L"", total_length_s / 60, total_length_s % 60);
+ else if (total_length_s < 60*60*24) StringCchPrintfW(pb[index], 64, L"[%s%u:%02u:%02u]", uncert ? L"~" : L"", total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ else
+ {
+ wchar_t days[16] = {0};
+ int total_days = total_length_s / (60 * 60 * 24);
+ total_length_s -= total_days * 60 * 60 * 24;
+ StringCchPrintfW(pb[index], 64,
+ WASABI_API_LNGSTRINGW(IDS_LENGTH_DURATION_STRING),
+ uncert ? L"~" : L"", total_days,
+ WASABI_API_LNGSTRINGW_BUF(total_days == 1 ? IDS_DAY : IDS_DAYS, days, 16),
+ total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ }
+ index++;
+ }
+
+ if (total_len_bytes)
+ {
+ StringCchCopyW(pb[index], 64, L"[");
+ WASABI_API_LNG->FormattedSizeString(pb[index] + 1, 64, total_len_bytes);
+ StringCchCatW(pb[index], 64, L"]");
+ index++;
+ }
+
+ unsigned int ms = (UINT)(querytime.QuadPart * 1000 / freq.QuadPart);
+ StringCchPrintfW(pb[index], 64, WASABI_API_LNGSTRINGW(IDS_IN_X_SEC), ms / 1000.0f);
+ index++;
+
+ SetStatusText(GetDlgItem(hwndDlg, IDC_MEDIASTATUS), (LPCWSTR*)pb, index);
+
+ if (m_bgupdinfoviewerflag)
+ {
+ m_bgupdinfoviewerflag = 0;
+ if (itemCache.Size > 0)
+ {
+ if (predixisExist)
+ {
+ if (resultlist.GetSelectedCount() == 1 && isMixable(itemCache.Items[0]))
+ {
+ SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ isMixablePresent = true;
+ }
+ else
+ {
+ SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ }
+ }
+ SendMessageW(GetParent(hwndDlg), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)itemCache.Items[0].filename);
+ }
+ }
+ }
+ break;
+ case WM_APP + 1:
+ bgQuery((resultsniff_funcW)wParam, (int)lParam);
+ break;
+ case WM_APP + 5:
+ {
+ // TODO
+ int done = (HIWORD(wParam) == 1);
+ int code = (LOWORD(wParam));
+ for (int i = 0; i < itemCache.Size; i ++)
+ {
+ // 0 = no change
+ // 1 = add cloud
+ // 2 = add local
+ // 4 = removed
+ if (!lstrcmpiW(itemCache.Items[i].filename, (wchar_t *)lParam))
+ {
+ if (!done)
+ {
+ setCloudValue(&itemCache.Items[i], L"5");
+ }
+ else
+ {
+ if (code == NErr_Success)
+ {
+ // uploaded ok
+ // TODO if going to another device, will need to alter this
+ setCloudValue(&itemCache.Items[i], L"0");
+ }
+ else
+ {
+ // re-query state
+ setCloudValue(&itemCache.Items[i], L"0");
+ }
+ }
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);
+ break;
+ }
+ }
+ break;
+ }
+ case WM_APP + 6: // handles the ml_cloud 'first pull' announces so we
+ { // can then show the cloud column & update the cache
+ initColumnsHeader(resultlist.getwnd());
+ bgQuery();//(resultsniff_funcW)wParam, (int)lParam);
+ break;
+ }
+ case WM_APP + 104:
+ {
+ Dialog_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST2 | DCW_SUNKENBORDER, IDC_QUICKSEARCH | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 1 + !!IsWindowVisible(GetDlgItem(hwndDlg, IDC_QUICKSEARCH)));
+ }
+ return 0;
+
+ case WM_ML_CHILDIPC:
+ switch (lParam)
+ {
+ case ML_CHILDIPC_GO_TO_SEARCHBAR:
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ case ML_CHILDIPC_REFRESH_SEARCH:
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ }
+ break;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+ case WM_QUERYFILEINFO: Window_OnQueryFileInfo(hwndDlg); break;
+ }
+ return FALSE;
+}
+
+//////////////////////////////// Customize columns dialog
+
+static signed char edit_columnOrder[MAX_COLUMN_ORDER];
+
+static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND m_curlistbox_hwnd, m_availlistbox_hwnd;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ memcpy(edit_columnOrder, columnOrder, sizeof(edit_columnOrder));
+ m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1);
+ m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, TRUE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, TRUE);
+ }
+
+ case WM_USER + 32:
+ {
+ for (int i = 0; edit_columnOrder[i] != -1; i++)
+ {
+ int c = edit_columnOrder[i];
+ headerColumn *cl = &columnList[c];
+ int column_id = cl->column_id;
+ if (column_id == IDS_CLOUD || column_id == IDS_CLOUD_HIDDEN)
+ {
+ // if no cloud support at all then we hide everything
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) continue;
+ column_id = ((!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) ? IDS_CLOUD_HIDDEN : IDS_CLOUD);
+ }
+ int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(column_id));
+ SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+
+ for (int i = 0; i < sizeof(columnList) / sizeof(headerColumn); i++)
+ {
+ headerColumn *cl = &columnList[i];
+ int j;
+ for (j = 0; edit_columnOrder[j] != -1 && edit_columnOrder[j] != i; j++);
+ if (edit_columnOrder[j] == -1)
+ {
+ int column_id = cl->column_id;
+ if (column_id == IDS_CLOUD || column_id == IDS_CLOUD_HIDDEN)
+ {
+ // if no cloud support at all then we hide everything
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) continue;
+ column_id = ((!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) ? IDS_CLOUD_HIDDEN : IDS_CLOUD);
+ }
+ int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(column_id));
+ SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, i);
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_DEFS:
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1)
+ memcpy(edit_columnOrder, defColumnOrder, sizeof(defColumnOrder));
+ else
+ memcpy(edit_columnOrder, defColumnOrderCloud, sizeof(defColumnOrderCloud));
+ SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(hwndDlg, WM_USER + 32, 0, 0);
+ break;
+ case IDC_LIST2:
+ if (HIWORD(wParam) != LBN_DBLCLK)
+ {
+ if (HIWORD(wParam) == LBN_SELCHANGE)
+ {
+ int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON2:
+ //add column
+ {
+ for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ int c = SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ int j;
+ for (j = 0;edit_columnOrder[j] != -1;j++);
+ edit_columnOrder[j] = (BYTE)c;
+ edit_columnOrder[j + 1] = -1;
+ SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+
+ int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0);
+ }
+ break;
+ case IDC_LIST1:
+ if (HIWORD(wParam) != LBN_DBLCLK)
+ {
+ if (HIWORD(wParam) == LBN_SELCHANGE)
+ {
+ int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON3:
+ //remove column
+ {
+ for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ int c = edit_columnOrder[i];
+ for (int j = i;edit_columnOrder[j] != -1;j++)
+ {
+ edit_columnOrder[j] = edit_columnOrder[j + 1];
+ }
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+
+ int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0);
+ }
+ break;
+ case IDC_BUTTON4:
+ //move column up
+ {
+ for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ BYTE c = edit_columnOrder[i - 1];
+ edit_columnOrder[i - 1] = edit_columnOrder[i];
+ edit_columnOrder[i] = c;
+
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON5:
+ //move column down
+ {
+ int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (int i = l - 2;i >= 0;i--)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ BYTE c = edit_columnOrder[i + 1];
+ edit_columnOrder[i + 1] = edit_columnOrder[i];
+ edit_columnOrder[i] = c;
+
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ }
+ break;
+ case IDOK:
+ {
+ HWND hwndList = resultlist.getwnd();
+ memcpy(columnOrder, edit_columnOrder, sizeof(edit_columnOrder));
+ if (hwndList)
+ {
+ initColumnsHeader(hwndList);
+ InvalidateRect(hwndList, NULL, TRUE);
+ UpdateWindow(hwndList);
+ }
+ }
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, FALSE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, FALSE);
+
+ WASABI_API_APP->app_removeAccelerators(hwndDlg);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void customizeColumnsDialog(HWND hwndParent)
+{
+ WASABI_API_DIALOGBOXW(IDD_CUSTCOLUMNS, hwndParent, custColumns_dialogProc);
+ EatKeyboard();
+}
+
+bool isMixable(itemRecordW &song)
+{
+ if (!song.filename) return false;
+
+ AutoChar charFn(song.filename);
+ pluginMessage p = {ML_MSG_PDXS_STATUS, (INT_PTR)(char *)charFn, 0, 0};
+ char *text = (char *)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & p, ML_IPC_SEND_PLUGIN_MESSAGE);
+ // Analyzed/Identified = mixable
+ return text && (text[0] == 'A' || text[0] == 'I');
+}
+
+void AccessingGracenoteHack(int p)
+{
+ if (p == 0)
+ {
+ GetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, oldText, 4096);
+ SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, L"Accessing Gracenote Database");
+ }
+ else
+ {
+ SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, oldText);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/view_miniinfo.cpp b/Src/Plugins/Library/ml_local/view_miniinfo.cpp
new file mode 100644
index 00000000..997efe63
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_miniinfo.cpp
@@ -0,0 +1,201 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+static int g_displayinfo = TRUE, g_displayinfochanged=FALSE;
+
+static HWND m_media_hwnd = NULL;
+static HWND m_info_hwnd = NULL;
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+#define IDC_WEBINFO 0x1000
+
+static void LayoutWindows(HWND hwnd, BOOL fShowInfo, BOOL fRedraw)
+{
+ RECT rc, ri, rg;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ SetRect(&rg, 0, 0, 0, 0);
+
+ if (rc.bottom - rc.top < WASABI_API_APP->getScaleY(180)) fShowInfo = FALSE;
+ if (rc.bottom <= rc.top || rc.right <= rc.left) return;
+
+ if (fShowInfo)
+ {
+ if (!IsWindow(m_info_hwnd)) // create browser
+ {
+ WEBINFOCREATE wic = {0};
+ wic.hwndParent = hwnd;
+ wic.uMsgQuery = WM_QUERYFILEINFO;
+ wic.x = rc.left;
+ wic.y = rc.bottom - WASABI_API_APP->getScaleY(101);
+ wic.cx = (rc.right - rc.left) - WASABI_API_APP->getScaleX(3);
+ wic.cy = WASABI_API_APP->getScaleY(100);
+ wic.ctrlId = IDC_WEBINFO;
+
+ m_info_hwnd = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_CREATEWEBINFO, (WPARAM)&wic);
+ if (IsWindow(m_info_hwnd))
+ {
+ SetWindowPos(m_info_hwnd, HWND_BOTTOM, 0, 0, 0, 0,
+ SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_NOCOPYBITS);
+ ShowWindow(m_info_hwnd, SW_SHOWNORMAL);
+ }
+ }
+ else
+ {
+ SetWindowPos(m_info_hwnd, NULL, rc.left, rc.bottom - WASABI_API_APP->getScaleY(101),
+ rc.right - WASABI_API_APP->getScaleX(3), WASABI_API_APP->getScaleY(100),
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+ }
+
+ if (IsWindow(m_info_hwnd))
+ {
+ rc.bottom -= WASABI_API_APP->getScaleY(104);
+ SetRect(&rg, rc.left, rc.bottom - WASABI_API_APP->getScaleY(101),
+ rc.right - WASABI_API_APP->getScaleX(10),
+ rc.bottom - WASABI_API_APP->getScaleY(1));
+ }
+ }
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ if (IsWindow(m_media_hwnd))
+ {
+ SetRect(&ri, rc.left, rc.top, rc.right, rc.bottom);
+ rgn = CreateRectRgn(0, 0, ri.right - ri.left, ri.bottom - ri.top);
+ SendMessage(m_media_hwnd, WM_USER + 0x201, MAKEWPARAM(offsetX, offsetY), (LPARAM)rgn);
+ SetWindowPos(m_media_hwnd, NULL, ri.left, ri.top, ri.right - ri.left, ri.bottom - ri.top,
+ SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOACTIVATE | ((!fRedraw) ? SWP_NOREDRAW : 0));
+ SendMessage(m_media_hwnd, WM_USER + 0x201, 0, 0L);
+ if (IsWindowVisible(m_media_hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(m_media_hwnd, NULL, FALSE))
+ {
+ GetUpdateRgn(m_media_hwnd, rgn, FALSE);
+ OffsetRgn(rgn, ri.left, ri.top);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (fRedraw)
+ {
+ UpdateWindow(hwnd);
+ if (IsWindow(m_media_hwnd)) UpdateWindow(m_media_hwnd);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ if (rgn)
+ {
+ OffsetRgn(rgn, rc.left, rc.top);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, rgn, RGN_OR);
+ }
+ }
+ ValidateRgn(hwnd, NULL);
+ if (rgn) DeleteObject(rgn);
+}
+
+INT_PTR CALLBACK view_miniinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a=dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+
+ switch(uMsg)
+ {
+ case WM_DISPLAYCHANGE:
+ if (IsWindow(m_media_hwnd)) PostMessageW(m_media_hwnd, WM_DISPLAYCHANGE, wParam, lParam);
+ if (IsWindow(m_info_hwnd)) PostMessageW(m_info_hwnd, WM_DISPLAYCHANGE, wParam, lParam);
+ break;
+
+ case WM_INITDIALOG:
+ {
+ g_displayinfo = g_view_metaconf->ReadInt(L"midivvis", 1);
+ g_displayinfochanged = FALSE;
+ m_media_hwnd = (lParam) ? WASABI_API_CREATEDIALOGW(((INT_PTR*)lParam)[1], hwndDlg, (DLGPROC)((INT_PTR*)lParam)[0]) : NULL;
+ SetWindowPos(m_media_hwnd, NULL, 0,0,0,0, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_SHOWWINDOW);
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.hwndToSkin = hwndDlg;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ return TRUE;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, g_displayinfo, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_APP+1: //sent by parent for resizing window
+ if (IsWindow(m_media_hwnd)) return SendMessage(m_media_hwnd,uMsg,wParam,lParam); // forward on
+ return 0;
+
+ case WM_APP+2: //sent by media child to get current query
+ return SendMessage(GetParent(hwndDlg),uMsg,wParam,lParam); // forward on
+
+ case WM_QUERYFILEINFO: if(IsWindow(m_media_hwnd)) PostMessageW(m_media_hwnd, uMsg, wParam, lParam); break;
+
+ case WM_SHOWFILEINFO:
+ if (IsWindow(m_info_hwnd))
+ {
+ WEBINFOSHOW wis = {0};
+ wis.pszFileName = (LPCWSTR)lParam;
+ wis.fFlags = (TRUE == wParam) ? WISF_FORCE : WISF_NORMAL;
+ SENDMLIPC(m_info_hwnd, ML_IPC_WEBINFO_SHOWINFO, (WPARAM)&wis);
+ }
+ case WM_USER+66:
+ if (wParam == -1)
+ {
+ g_displayinfo = !g_displayinfo;
+ g_displayinfochanged = !g_displayinfochanged;
+ if (IsWindow(m_info_hwnd)) ShowWindow(m_info_hwnd, (g_displayinfo) ? SW_SHOWNA : SW_HIDE);
+ LayoutWindows(hwndDlg, g_displayinfo, TRUE);
+ }
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT, (g_displayinfo) ? 0xff : 0xf0);
+ return TRUE;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ if (GetDlgItem(hwndDlg, IDC_WEBINFO))
+ {
+ static int tab[] = { IDC_WEBINFO | DCW_SUNKENBORDER };
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ else
+ dialogSkinner.Draw(hwndDlg, 0, 0);
+ }
+ return 0;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+
+ case WM_DESTROY:
+ if(g_displayinfochanged) g_view_metaconf->WriteInt(L"midivvis", g_displayinfo);
+ if (IsWindow(m_info_hwnd)) SENDMLIPC(m_info_hwnd, ML_IPC_WEBINFO_RELEASE, 0);
+ m_media_hwnd = NULL;
+ m_info_hwnd = NULL;
+ break;
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/wa_subclass.cpp b/Src/Plugins/Library/ml_local/wa_subclass.cpp
new file mode 100644
index 00000000..f5f20a08
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/wa_subclass.cpp
@@ -0,0 +1,314 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "resource.h"
+#include "../nu/listview.h"
+
+#define WINAMP_FILE_ADDTOLIBRARY 40344
+#define WINAMP_FILE_ADDCURRENTPLEDIT 40466
+#define WINAMP_SHOWLIBRARY 40379
+wchar_t *recent_fn = 0;
+static HMENU last_viewmenu = 0;
+WORD waMenuID = 0;
+
+extern W_ListView resultlist;
+extern volatile int no_lv_update;
+void UpdateLocalResultsCache(const wchar_t *filename);
+
+static void onPlayFileTrack()
+{
+ int autoaddplays = g_config->ReadInt(L"autoaddplays", 0);
+ int trackplays = g_config->ReadInt(L"trackplays", 1);
+ if (!trackplays && !autoaddplays)
+ return ;
+
+ if (g_table && recent_fn)
+ {
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, recent_fn, filename2);
+
+ if (found) // if it's in the table already
+ {
+ if (trackplays) // if we're tracking plays
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
+ int cnt = f ? NDE_IntegerField_GetValue(f) : 0;
+ time_t t = time(NULL);
+
+ db_setFieldInt(s, MAINTABLE_ID_PLAYCOUNT, ++cnt);
+ db_setFieldInt(s, MAINTABLE_ID_LASTPLAY, (int)t);
+ if (asked_for_playcount)
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_UPDTITLE);
+
+ // Issue a wasabi system callback after we have successfully added a file in the ml database
+ api_mldb::played_info info = {t, cnt};
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PLAYED, (size_t)recent_fn, (size_t)&info);
+
+ NDE_Scanner_Post(s);
+ // disabled in 5.65 (was added in 5.64?) as causing
+ // noticeable ui lockups on sync with large library
+ // so instead we'll risk it and flush on 50 instead
+ /*g_table_dirty = 0;
+ NDE_Table_Sync(g_table);*/
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ else // not found in main table, we are in the main table, time to add if set
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (autoaddplays)
+ {
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(recent_fn, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ addFileToDb(recent_fn, 0, meta, guess, 1, (int)time(NULL));
+ }
+ }
+
+ if (g_table_dirty > 50)
+ {
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ }
+
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void onStartPlayFileTrack(const wchar_t *filename, bool resume)
+{
+ if (!(wcsstr(filename, L"://") && _wcsnicmp(filename, L"cda://", 6) && _wcsnicmp(filename, L"file://", 7)))
+ {
+ int timer = -1, timer1 = -1, timer2 = -1;
+
+ KillTimer(plugin.hwndWinampParent, 8081);
+ if (!resume)
+ {
+ free(recent_fn);
+ recent_fn = _wcsdup(filename);
+ }
+
+ // wait for x seconds
+ if(g_config->ReadInt(L"trackplays_wait_secs",0))
+ {
+ timer1 = g_config->ReadInt(L"trackplays_wait_secs_lim",5)*1000;
+ }
+
+ // wait for x percent of the song (approx to a second)
+ if(g_config->ReadInt(L"trackplays_wait_percent",0))
+ {
+ basicFileInfoStructW bfiW = {0};
+ bfiW.filename = recent_fn;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfiW, IPC_GET_BASIC_FILE_INFOW);
+ if(bfiW.length > 0)
+ {
+ bfiW.length=bfiW.length*1000;
+ timer2 = (bfiW.length*g_config->ReadInt(L"trackplays_wait_percent_lim",50))/100;
+ }
+ }
+
+ // decide on which playback option will be the prefered duration (smallest wins)
+ if(timer1 != -1 && timer2 != -1)
+ {
+ if(timer1 > timer2)
+ {
+ timer = timer2;
+ }
+ if(timer2 > timer1)
+ {
+ timer = timer1;
+ }
+ }
+ else if(timer1 == -1 && timer2 != -1)
+ {
+ timer = timer2;
+ }
+ else if(timer2 == -1 && timer1 != -1)
+ {
+ timer = timer1;
+ }
+
+ // if no match or something went wrong then try to ensure the default timer value is used
+ SetTimer(plugin.hwndWinampParent, 8081, ((timer > 0)? timer : 350), NULL);
+ }
+}
+
+static void FileUpdated(const wchar_t *filename)
+{
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(filename, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ addFileToDb(filename, TRUE, meta, guess, 0, 0, true);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (g_table_dirty > 10)
+ {
+ g_table_dirty = 0;
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ if (!no_lv_update)
+ UpdateLocalResultsCache(filename);
+ }
+ ClearCache(filename);
+ ClearTitleHookCache();
+}
+
+LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_TIMER:
+ if (recent_fn && wParam == 8081)
+ {
+ if (SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_GETOUTPUTTIME) > 350)
+ {
+ KillTimer(hwndDlg, 8081);
+ if (SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_ISPLAYING) == 1)
+ {
+ onPlayFileTrack();
+ }
+ free(recent_fn);
+ recent_fn = 0;
+ }
+ }
+ break;
+ case WM_WA_IPC:
+ switch (lParam)
+ {
+ case IPC_CB_MISC:
+ if (wParam == IPC_CB_MISC_TITLE)
+ {
+ if(!SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_ISPLAYING))
+ {
+ KillTimer(hwndDlg, 8081);
+ }
+ }
+ else if (wParam == IPC_CB_MISC_PAUSE)
+ {
+ KillTimer(hwndDlg, 8081);
+ }
+ else if (wParam == IPC_CB_MISC_UNPAUSE)
+ {
+ if (recent_fn) onStartPlayFileTrack(recent_fn, true);
+ }
+ break;
+ case IPC_FILE_TAG_MAY_HAVE_UPDATEDW:
+ {
+ wchar_t *filename = (wchar_t *)wParam;
+ if (filename)
+ FileUpdated(filename);
+ }
+ break;
+ case IPC_FILE_TAG_MAY_HAVE_UPDATED:
+ {
+ char *filename = (char *)wParam;
+ if (filename)
+ FileUpdated(AutoWide(filename));
+ }
+ break;
+ case IPC_STOPPLAYING:
+ {
+ KillTimer(hwndDlg, 8081);
+ free(recent_fn);
+ recent_fn = 0;
+ }
+ break;
+ case IPC_GET_EXTENDED_FILE_INFO_HOOKABLE:
+ // guessing for metadata for when the library isn't open yet.
+ if (!g_table && wParam && !m_calling_getfileinfo) return doGuessProc(hwndDlg, uMsg, wParam, lParam);
+ break;
+ default:
+ {
+ if (lParam == IPC_CLOUD_ENABLED)
+ {
+ if (m_curview_hwnd) SendMessage(m_curview_hwnd, WM_APP + 6, 0, 0); //update current view
+ }
+ }
+ break;
+ }
+ break;
+ case WM_INITMENUPOPUP:
+ {
+ HMENU hmenuPopup = (HMENU) wParam;
+ if (hmenuPopup == wa_play_menu)
+ {
+ if (last_viewmenu)
+ {
+ RemoveMenu(wa_play_menu, waMenuID, MF_BYCOMMAND);
+ DestroyMenu(last_viewmenu);
+ last_viewmenu = NULL;
+ }
+
+ mlGetTreeStruct mgts = { 1000, 55000, -1};
+ last_viewmenu = (HMENU)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & mgts, ML_IPC_GETTREE);
+ if (last_viewmenu)
+ {
+ if (GetMenuItemCount(last_viewmenu) > 0)
+ {
+ int count = GetMenuItemCount(wa_play_menu);
+ MENUITEMINFOW menuItem = {sizeof(MENUITEMINFOW), MIIM_SUBMENU | MIIM_ID | MIIM_TYPE, MFT_STRING, MFS_ENABLED, waMenuID,
+ last_viewmenu, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_MEDIA_LIBRARY_VIEW_RESULTS), 0};
+ InsertMenuItemW(wa_play_menu, count, TRUE, &menuItem);
+ }
+ else
+ {
+ DestroyMenu(last_viewmenu);
+ last_viewmenu = 0;
+ }
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ case WM_SYSCOMMAND:
+ WORD lowP = LOWORD(wParam);
+ if (lowP == WINAMP_FILE_ADDTOLIBRARY)
+ {
+ if (!plugin.hwndLibraryParent || !IsWindowVisible(plugin.hwndLibraryParent))
+ {
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, MAKEWPARAM(WINAMP_SHOWLIBRARY, 0), 0L);
+ }
+ add_to_library(plugin.hwndLibraryParent);
+ }
+ else if (lowP == WINAMP_FILE_ADDCURRENTPLEDIT)
+ {
+ add_pledit_to_library();
+ }
+ else if (uMsg == WM_COMMAND && wParam > 45000 && wParam < 55000)
+ {
+ int n = wParam - 45000;
+ if (m_query_list[n])
+ {
+ mediaLibrary.SwitchToPluginView(n);
+ return 0;
+ }
+ }
+ else if (uMsg == WM_COMMAND && wParam > 55000 && wParam < 65000)
+ {
+ int n = wParam - 55000;
+ if (m_query_list[n])
+ {
+ queryItem *item = m_query_list[n];
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf, item->query, 0, 1);
+ return 0;
+ }
+ }
+ break;
+ }
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nft/api__ml_nft.h b/Src/Plugins/Library/ml_nft/api__ml_nft.h
new file mode 100644
index 00000000..6924cab3
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/api__ml_nft.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_API_ML_NFT_H
+#define NULLSOFT_API_ML_NFT_H
+
+#include <api/service/waServiceFactory.h>
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#endif // !NULLSOFT_API_ML_NFT_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nft/listview.cpp b/Src/Plugins/Library/ml_nft/listview.cpp
new file mode 100644
index 00000000..a3ceb22c
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/listview.cpp
@@ -0,0 +1,128 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+
+#include <windows.h>
+#include <commctrl.h>
+#include "listview.h"
+
+#ifdef GEN_ML_EXPORTS
+#include "main.h" // for getting the font
+#include "config.h"
+#endif
+// bp Comment: all the calls beginning "ListView_" are
+// MACROs defined in commctrl.h
+
+void W_ListView :: AddCol (char *text, int w)
+{
+ LVCOLUMN lvc={0,};
+ lvc.mask = LVCF_TEXT|LVCF_WIDTH;
+ lvc.pszText = text;
+ if (w) lvc.cx=w;
+ ListView_InsertColumn (m_hwnd, m_col, &lvc);
+ m_col++;
+}
+
+int W_ListView::GetColumnWidth (int col)
+{
+ if (col < 0 || col >= m_col) return 0;
+ return ListView_GetColumnWidth (m_hwnd, col);
+}
+
+
+int W_ListView::GetParam (int p)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = p;
+ ListView_GetItem (m_hwnd, &lvi);
+ return lvi.lParam;
+}
+
+int W_ListView::InsertItem (int p, char *text, int param)
+{
+ LVITEM lvi={0,};
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = p;
+ lvi.pszText = text;
+ lvi.cchTextMax=strlen (text);
+ lvi.lParam = param;
+ return ListView_InsertItem (m_hwnd, &lvi);
+}
+
+
+void W_ListView::SetItemText (int p, int si, char *text)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.iSubItem = si;
+ lvi.mask = LVIF_TEXT;
+ lvi.pszText = text;
+ lvi.cchTextMax = strlen (text);
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::SetItemParam (int p, int param)
+{
+ LVITEM lvi={0,};
+ lvi.iItem = p;
+ lvi.mask=LVIF_PARAM;
+ lvi.lParam=param;
+ ListView_SetItem (m_hwnd, &lvi);
+}
+
+void W_ListView::refreshFont ()
+{
+ if (m_font)
+ {
+ DeleteFont (m_font);
+ SetWindowFont (m_hwnd, NULL, FALSE);
+ }
+ m_font = NULL;
+
+ HWND h;
+#ifdef GEN_ML_EXPORTS
+ h=g_hwnd;
+#else
+ h=m_libraryparent;
+#endif
+ if (h && m_allowfonts)
+ {
+ int a=SendMessage (h, WM_USER+0x1000 /*WM_ML_IPC*/,66, 0x0600 /*ML_IPC_SKIN_WADLG_GETFUNC*/);
+ if (a)
+ {
+ m_font= (HFONT)a;
+ SetWindowFont (m_hwnd, m_font, FALSE);
+ }
+ }
+ InvalidateRect (m_hwnd, NULL, TRUE);
+}
+
+void W_ListView::setallowfonts (int allow)
+{
+ m_allowfonts=allow;
+}
+
+void W_ListView::setwnd (HWND hwnd)
+{
+ m_hwnd = hwnd;
+ if (hwnd)
+ {
+ ListView_SetExtendedListViewStyle (hwnd, LVS_EX_FULLROWSELECT|LVS_EX_UNDERLINEHOT );
+ refreshFont ();
+ }
+}
diff --git a/Src/Plugins/Library/ml_nft/listview.h b/Src/Plugins/Library/ml_nft/listview.h
new file mode 100644
index 00000000..5938a4dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/listview.h
@@ -0,0 +1,144 @@
+/*
+** Copyright (C) 2003 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+#if 0
+#ifndef _LISTVIEW_H_
+#define _LISTVIEW_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+class W_ListView
+{
+public:
+ W_ListView()
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ }
+ W_ListView(HWND hwnd)
+ {
+ m_hwnd=NULL;
+ m_col=0;
+ m_allowfonts=1;
+ m_font=NULL;
+#ifndef GEN_ML_EXPORTS
+ m_libraryparent=NULL;
+#endif
+ setwnd(hwnd);
+ }
+ ~W_ListView()
+ {
+ if (m_font) DeleteFont(m_font);
+ m_font=0;
+ }
+
+ void refreshFont();
+
+#ifndef GEN_ML_EXPORTS
+ void setLibraryParentWnd(HWND hwndParent)
+ {
+ m_libraryparent=hwndParent;
+ }// for Winamp Font getting stuff
+#endif
+ void setallowfonts(int allow=1);
+ void setwnd(HWND hwnd);
+ void AddCol(char *text, int w);
+ int GetCount(void)
+ {
+ return ListView_GetItemCount(m_hwnd);
+ }
+ int GetParam(int p);
+ void DeleteItem(int n)
+ {
+ ListView_DeleteItem(m_hwnd,n);
+ }
+ void Clear(void)
+ {
+ ListView_DeleteAllItems(m_hwnd);
+ }
+ int GetSelected(int x)
+ {
+ return(ListView_GetItemState(m_hwnd, x, LVIS_SELECTED) & LVIS_SELECTED)?1:0;
+ }
+
+ int GetSelectedCount()
+ {
+ return ListView_GetSelectedCount(m_hwnd);
+ }
+
+ int GetSelectionMark()
+ {
+ return ListView_GetSelectionMark(m_hwnd);
+ }
+ void SetSelected(int x)
+ {
+ ListView_SetItemState(m_hwnd,x,LVIS_SELECTED,LVIS_SELECTED);
+ }
+ int InsertItem(int p, char *text, int param);
+ void GetItemRect(int i, RECT *r)
+ {
+ ListView_GetItemRect(m_hwnd, i, r, LVIR_BOUNDS);
+ }
+ void SetItemText(int p, int si, char *text);
+ void SetItemParam(int p, int param);
+
+ void GetText(int p, int si, char *text, int maxlen)
+ {
+ ListView_GetItemText(m_hwnd, p, si, text, maxlen);
+ }
+ int FindItemByParam(int param)
+ {
+ LVFINDINFO fi={LVFI_PARAM,0,param};
+ return ListView_FindItem(m_hwnd,-1,&fi);
+ }
+ int FindItemByPoint(int x, int y)
+ {
+ int l=GetCount();
+ for (int i=0;i<l;i++)
+ {
+ RECT r;
+ GetItemRect(i, &r);
+ if (r.left<=x && r.right>=x && r.top<=y && r.bottom>=y) return i;
+ }
+ return -1;
+ }
+ int GetColumnWidth(int col);
+ HWND getwnd(void)
+ {
+ return m_hwnd;
+ }
+
+protected:
+ HWND m_hwnd;
+ HFONT m_font;
+ int m_col;
+ int m_allowfonts;
+#ifndef GEN_ML_EXPORTS
+ HWND m_libraryparent;
+#endif
+};
+
+#endif//_LISTVIEW_H_
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nft/main.cpp b/Src/Plugins/Library/ml_nft/main.cpp
new file mode 100644
index 00000000..aa7fcc71
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/main.cpp
@@ -0,0 +1,111 @@
+#define PLUGIN_VERSION L"0.1"
+
+#include <strsafe.h>
+
+#include "Main.h"
+#include "../nu/AutoWide.h"
+
+#include "../../General/gen_ml/menu.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+#include "../WAT/WAT.h"
+
+static int Init();
+static void Quit();
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_nft.dll)",
+ Init,
+ Quit,
+ nft_pluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+int nft_treeItem = 0;
+
+HCURSOR hDragNDropCursor;
+C_Config *g_config = 0;
+WNDPROC waProc = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_application *WASABI_API_APP = 0;
+
+static DWORD WINAPI wa_newWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
+{
+ if ( waProc )
+ return (DWORD)CallWindowProcW( waProc, hwnd, msg, wParam, lParam );
+ else
+ return (DWORD)DefWindowProc( hwnd, msg, wParam, lParam );
+}
+
+int Init()
+{
+ waProc = (WNDPROC)SetWindowLongPtrW( plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc );
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( AnonymousStatsGUID );
+ if ( sf )
+ AGAVE_API_STATS = reinterpret_cast<api_stats *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application *>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlNFTLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_NFT ), PLUGIN_VERSION );
+ plugin.description = (char *)szDescription;
+
+ wchar_t inifile[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins", inifile, MAX_PATH );
+ CreateDirectoryW( inifile, NULL );
+
+ mediaLibrary.BuildPath( L"Plugins\\gen_ml.ini", inifile, MAX_PATH );
+ g_config = new C_Config( inifile );
+
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.pszText = WASABI_API_LNGSTRINGW( IDS_NFT );
+ nis.item.pszInvariant = L"NFT Library";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.iSelectedImage = nis.item.iImage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_NFT );
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = { sizeof( NAVITEM ),0,NIMF_ITEMID, };
+ nvItem.hItem = MLNavCtrl_InsertItem( plugin.hwndLibraryParent, &nis );
+ MLNavItem_GetInfo( plugin.hwndLibraryParent, &nvItem );
+ nft_treeItem = nvItem.id;
+
+
+ return 0;
+}
+
+void Quit()
+{
+ if ( g_config != 0 )
+ delete g_config;
+}
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nft/main.h b/Src/Plugins/Library/ml_nft/main.h
new file mode 100644
index 00000000..fd93d9a7
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/main.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_NFT_MAIN_H
+#define NULLSOFT_NFT_MAIN_H
+
+#include <windows.h>
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "resource.h"
+#include <windowsx.h>
+#include "resource.h"
+#include "../winamp/wa_ipc.h"
+#include "../Plugins/General/gen_ml/ml.h"
+#include "../Plugins/General/gen_ml/config.h"
+#include "api__ml_nft.h"
+
+
+#define NFT_BASE_URL L"https://nftlib.winamp.com/"
+
+extern winampMediaLibraryPlugin plugin;
+INT_PTR nft_pluginMessageProc( int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3 );
+extern int nft_treeItem;
+
+
+
+#endif // !NULLSOFT_NFT_MAIN_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nft/ml_nft.rc b/Src/Plugins/Library/ml_nft/ml_nft.rc
new file mode 100644
index 00000000..1eefde12
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/ml_nft.rc
@@ -0,0 +1,128 @@
+// 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 (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+#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_VIEW_NFT DIALOGEX 0, 0, 184, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_NFT,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_TABSTOP,0,0,184,79
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_NFT BITMAP "resources\\NFT_16x16.bmp"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_VIEW_NFT, DIALOG
+ BEGIN
+ RIGHTMARGIN, 123
+ BOTTOMMARGIN, 61
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_VIEW_NFT AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_NFT "Nullsoft NFT Library v%s"
+ 65535 "{C1DE900F-8047-4B04-AE6B-8EACD9A9B8E1}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_NFT "NFT Library"
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_nft/ml_nft.vcxproj b/Src/Plugins/Library/ml_nft/ml_nft.vcxproj
new file mode 100644
index 00000000..9c3c7b60
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/ml_nft.vcxproj
@@ -0,0 +1,313 @@
+<?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>{31463181-D72E-4613-A306-8446F2946179}</ProjectGuid>
+ <RootNamespace>ml_nft</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <VcpkgEnabled>false</VcpkgEnabled>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_NFT_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_NFT_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_NFT_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_NFT_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;%(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>
+ <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="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="view.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="api__ml_nft.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_nft.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\nft_16x16.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\WAT\WAT.vcxproj">
+ <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</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_nft/ml_nft.vcxproj.filters b/Src/Plugins/Library/ml_nft/ml_nft.vcxproj.filters
new file mode 100644
index 00000000..121f1986
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/ml_nft.vcxproj.filters
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="view.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_nft.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{b4c6c983-ad68-4d2d-a542-7f1bf6b1c155}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{f072eba6-51ff-4c40-b136-bf3c79f3e120}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b04fff17-1af4-4d74-b609-169c1a32b097}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{56f5b459-ac0f-423d-b448-563135bf4bd8}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{0f139e2a-24a0-4f2f-8ef5-9f429704fc09}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{33c224c5-9689-4e39-9fc1-95b30d7f72fd}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{2a93abc4-8246-41d7-b991-d111d36d5353}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{b1c88e02-70f6-4a54-80fc-60a246835520}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_nft.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\nft_16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nft/resource.h b/Src/Plugins/Library/ml_nft/resource.h
new file mode 100644
index 00000000..9e1bc082
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/resource.h
@@ -0,0 +1,20 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_nft.rc
+//
+#define IDS_NFT 1
+#define IDD_VIEW_NFT 102
+#define IDC_LIST_NFT 1001
+#define IDB_TREEITEM_NFT 1003
+#define IDS_NULLSOFT_NFT 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 114
+#define _APS_NEXT_COMMAND_VALUE 40013
+#define _APS_NEXT_CONTROL_VALUE 1010
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_nft/resources/NFT_16x16.bmp b/Src/Plugins/Library/ml_nft/resources/NFT_16x16.bmp
new file mode 100644
index 00000000..b781254b
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/resources/NFT_16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_nft/resources/_NFT_16x16.png b/Src/Plugins/Library/ml_nft/resources/_NFT_16x16.png
new file mode 100644
index 00000000..9e27b323
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/resources/_NFT_16x16.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_nft/version.rc2 b/Src/Plugins/Library/ml_nft/version.rc2
new file mode 100644
index 00000000..102a3f3e
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 0,1,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", "0,1,0,0"
+ VALUE "InternalName", "Nullsoft NFT Library"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_nft.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_nft/view.cpp b/Src/Plugins/Library/ml_nft/view.cpp
new file mode 100644
index 00000000..7d03e13c
--- /dev/null
+++ b/Src/Plugins/Library/ml_nft/view.cpp
@@ -0,0 +1,388 @@
+#include <strsafe.h>
+
+#include "main.h"
+
+#include "../nu/DialogSkinner.h"
+#include "../nu/ListView.h"
+
+#include "../../General/gen_ml/ml_ipc.h"
+#include "../../General/gen_ml/menu.h"
+
+#include "../WAT/WAT.h"
+
+INT_PTR CALLBACK view_NFTDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static W_ListView m_nft_list;
+static HWND m_headerhwnd, m_hwnd;
+extern C_Config *g_config;
+extern char *g_ext_list;
+static int customAllowed;
+static viewButtons view;
+
+C_Config *g_wa_config = 0;
+char *g_ext_list = 0;
+
+// used for the send-to menu bits
+static INT_PTR IPC_LIBRARY_SENDTOMENU;
+static librarySendToMenuStruct s;
+BOOL myMenu = FALSE;
+
+extern HMENU g_context_menus, g_context_menus2;
+extern HCURSOR hDragNDropCursor;
+
+
+static int NFT_contextMenu( INT_PTR param1, HWND hHost, POINTS pts )
+{
+ return TRUE;
+}
+
+static int pluginHandleIpcMessage(int msg, int param)
+{
+ return (int)SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, param, msg );
+}
+
+INT_PTR nft_pluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_NO_CONFIG)
+ {
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_TREE_ONCREATEVIEW && param1 == nft_treeItem )
+ {
+ return (INT_PTR)WASABI_API_CREATEDIALOGW(IDD_VIEW_NFT, (HWND)param2, view_NFTDialogProc);
+ }
+ else if (message_type == ML_MSG_NAVIGATION_CONTEXTMENU)
+ {
+ return NFT_contextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT || message_type == ML_MSG_TREE_ONDROPTARGET)
+ {
+ // set with droptarget defaults =)
+ UINT_PTR type = 0,data = 0;
+
+ if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)nft_pluginMessageProc) return 0;
+
+ type=(int)param1;
+ data = (int)param2;
+ }
+ else
+ {
+ if (param1 != nft_treeItem ) return 0;
+
+ type=(int)param2;
+ data=(int)param3;
+
+ if (!data)
+ {
+ return (type == ML_TYPE_ITEMRECORDLISTW || type == ML_TYPE_ITEMRECORDLIST ||
+ type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES ||
+ type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW ||
+ type == ML_TYPE_CDTRACKS ||
+ type == ML_TYPE_PLAYLIST || type == ML_TYPE_PLAYLISTS) ? 1 : -1;
+ }
+ }
+ }
+
+
+ return 0;
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, GROUP_MAIN, IDC_LIST_NFT
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if ( rc.right == rc.left || rc.bottom == rc.top )
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_LIST_NFT:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1),
+ rg.right - rg.left + WASABI_API_APP->getScaleY(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for(pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static BOOL NFT_OnDisplayChange()
+{
+ ListView_SetTextColor(m_nft_list.getwnd(),dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(m_nft_list.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(m_nft_list.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+ m_nft_list.SetFont(dialogSkinner.GetFont());
+ LayoutWindows(m_hwnd, TRUE);
+ return 0;
+}
+
+
+enum
+{
+ BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+static BOOL NFT_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
+{
+ return FALSE;
+}
+
+static BOOL NFT_OnDestroy(HWND hwnd)
+{
+ m_hwnd = 0;
+
+ return FALSE;
+}
+
+static BOOL NFT_OnNotify(HWND hwnd, NMHDR *notification)
+{
+ if (notification->idFrom== IDC_LIST_NFT )
+ {
+ if (notification->code == LVN_BEGINDRAG)
+ {
+ SetCapture(hwnd);
+ }
+ }
+ return FALSE;
+}
+
+static BOOL NFT_OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
+{
+ m_hwnd = hwndDlg;
+ m_nft_list.setwnd(GetDlgItem(hwndDlg, IDC_LIST_NFT ));
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+
+ NFT_OnDisplayChange();
+
+ m_headerhwnd = ListView_GetHeader( m_nft_list.getwnd() );
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ customAllowed = FALSE;
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ m.hwndToSkin = hwndDlg;
+
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+
+
+ ShellExecuteW( NULL, L"open", NFT_BASE_URL, NULL, NULL, SW_SHOWNORMAL );
+
+ delete g_wa_config;
+
+ SetTimer( hwndDlg, 100, 15, NULL );
+
+
+ return TRUE;
+}
+
+INT_PTR CALLBACK view_NFTDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam);
+ if (a) return a;
+
+ switch(uMsg)
+ {
+ HANDLE_MSG(hwndDlg, WM_INITDIALOG, NFT_OnInitDialog);
+ HANDLE_MSG(hwndDlg, WM_COMMAND, NFT_OnCommand);
+ HANDLE_MSG(hwndDlg, WM_DESTROY, NFT_OnDestroy);
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST_NFT |DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ myMenu = TRUE;
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ myMenu = FALSE;
+ }
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ return NFT_OnDisplayChange();
+
+ case WM_NOTIFY:
+ return NFT_OnNotify(hwndDlg, (LPNMHDR)lParam);
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/common.cpp b/Src/Plugins/Library/ml_nowplaying/common.cpp
new file mode 100644
index 00000000..f6d2f03c
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/common.cpp
@@ -0,0 +1,147 @@
+#include "./common.h"
+#include "./wasabi.h"
+
+#include <strsafe.h>
+
+LPWSTR Plugin_MallocString(size_t cchLen)
+{
+ return (LPWSTR)calloc(cchLen, sizeof(WCHAR));
+}
+
+void Plugin_FreeString(LPWSTR pszString)
+{
+ if (NULL != pszString)
+ {
+ free(pszString);
+ }
+}
+
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen)
+{
+ return (LPWSTR)realloc(pszString, sizeof(WCHAR) * cchLen);
+}
+
+LPWSTR Plugin_CopyString(LPCWSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenW(pszSource) + 1;
+
+ LPWSTR copy = Plugin_MallocString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(WCHAR) * cchSource);
+ }
+ return copy;
+}
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen)
+{
+ return (LPSTR)calloc(cchLen, sizeof(CHAR));
+}
+
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenA(pszSource) + 1;
+
+ LPSTR copy = Plugin_MallocAnsiString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(CHAR) * cchSource);
+ }
+ return copy;
+
+}
+void Plugin_FreeAnsiString(LPSTR pszString)
+{
+ Plugin_FreeString((LPWSTR)pszString);
+}
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
+{
+ INT cchBuffer = WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, lpDefaultChar, lpUsedDefaultChar);
+ if (0 == cchBuffer) return NULL;
+
+ LPSTR buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, buffer, cchBuffer, lpDefaultChar, lpUsedDefaultChar))
+ {
+ Plugin_FreeAnsiString(buffer);
+ return NULL;
+ }
+ return buffer;
+}
+
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte)
+{
+ if (NULL == lpMultiByteStr) return NULL;
+ INT cchBuffer = MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0);
+ if (NULL == cchBuffer) return NULL;
+
+ if (cbMultiByte > 0) cchBuffer++;
+
+ LPWSTR buffer = Plugin_MallocString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, buffer, cchBuffer))
+ {
+ Plugin_FreeString(buffer);
+ return NULL;
+ }
+
+ if (cbMultiByte > 0)
+ {
+ buffer[cchBuffer - 1] = L'\0';
+ }
+ return buffer;
+}
+
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource)
+{
+ return (IS_INTRESOURCE(pszResource)) ?
+ (LPWSTR)pszResource :
+ Plugin_CopyString(pszResource);
+}
+
+void Plugin_FreeResString(LPWSTR pszResource)
+{
+ if (!IS_INTRESOURCE(pszResource))
+ Plugin_FreeString(pszResource);
+}
+
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString)
+{
+ if (NULL == pszBuffer)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ if (NULL == pszString)
+ {
+ pszBuffer[0] = L'\0';
+ }
+ else if (IS_INTRESOURCE(pszString))
+ {
+ if (NULL == WASABI_API_LNG)
+ hr = E_FAIL;
+ else
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszString, pszBuffer, cchBufferMax);
+ }
+ else
+ {
+ hr = StringCchCopy(pszBuffer, cchBufferMax, pszString);
+ }
+ return hr;
+}
+
+void Plugin_SafeRelease(IUnknown *pUnk)
+{
+ if (NULL != pUnk)
+ pUnk->Release();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/common.h b/Src/Plugins/Library/ml_nowplaying/common.h
new file mode 100644
index 00000000..8e93665d
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/common.h
@@ -0,0 +1,58 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_COMMON_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_COMMON_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/trace.h"
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#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 MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWL_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+
+LPWSTR Plugin_MallocString(size_t cchLen);
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
+void Plugin_FreeString(LPWSTR pszString);
+LPWSTR Plugin_CopyString(LPCWSTR pszSource);
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen);
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
+void Plugin_FreeAnsiString(LPSTR pszString);
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
+void Plugin_FreeResString(LPWSTR pszResource);
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
+
+void Plugin_SafeRelease(IUnknown *pUnk);
+
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_COMMON_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/external.cpp b/Src/Plugins/Library/ml_nowplaying/external.cpp
new file mode 100644
index 00000000..add50723
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/external.cpp
@@ -0,0 +1,110 @@
+#include "main.h"
+#include "./resource.h"
+#include "./external.h"
+
+ExternalDispatch::ExternalDispatch()
+ : ref(1)
+{
+}
+
+ExternalDispatch::~ExternalDispatch()
+{
+}
+
+HRESULT ExternalDispatch::CreateInstance(ExternalDispatch **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ *instance = new ExternalDispatch();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+LPCWSTR ExternalDispatch::GetName()
+{
+ return L"NowPlaying";
+}
+
+
+ULONG ExternalDispatch::AddRef(void)
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+
+ULONG ExternalDispatch::Release(void)
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+STDMETHODIMP ExternalDispatch::QueryInterface(REFIID riid, void **ppvObject)
+{
+ if (NULL == ppvObject) return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = static_cast<IDispatch*>(this);
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = static_cast<IUnknown*>(this);
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ if (_wcsicmp(rgszNames[i], L"hidden") == 0)
+ rgdispid[i] = DISPATCH_HIDDEN;
+ else
+ {
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+ }
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT ExternalDispatch::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT ExternalDispatch::Invoke(DISPID dispId, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch(dispId)
+ {
+ case DISPATCH_HIDDEN:
+ if (NULL != pvarResult)
+ {
+ HWND hLibrary = Plugin_GetLibrary();
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_BOOL;
+ V_BOOL(pvarResult) = (NULL == hLibrary || FALSE == SENDMLIPC(hLibrary, ML_IPC_IS_VISIBLE, 0));
+ }
+ return S_OK;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/external.h b/Src/Plugins/Library/ml_nowplaying/external.h
new file mode 100644
index 00000000..fe9e4a9e
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/external.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_EXTERNAL_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_EXTERNAL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ExternalDispatch : public IDispatch
+{
+
+public:
+ typedef enum
+ {
+ DISPATCH_HIDDEN = 777,
+ } DispatchCodes;
+
+protected:
+ ExternalDispatch();
+ ~ExternalDispatch();
+
+public:
+ static HRESULT CreateInstance(ExternalDispatch **instance);
+ static LPCWSTR GetName();
+
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+protected:
+ ULONG ref;
+
+};
+
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_EXTERNAL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/handler.cpp b/Src/Plugins/Library/ml_nowplaying/handler.cpp
new file mode 100644
index 00000000..2d47a94e
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/handler.cpp
@@ -0,0 +1,47 @@
+#include "main.h"
+#include "service.h"
+#include "navigation.h"
+#include "handler.h"
+#include "../Agave/URIHandler/svc_urihandler.h"
+#include <api/service/waservicefactory.h>
+#include "api.h"
+#include "../ml_online/config.h"
+#include "../replicant/nu/Autowide.h"
+
+int NowPlayingURIHandler::ProcessFilename(const wchar_t *filename)
+{
+ if (!_wcsnicmp(filename, L"winamp://Now Playing", 20) || !_wcsnicmp(filename, L"winamp://Now%20Playing", 22))
+ {
+ size_t index = 0;
+ if (filename[12] == L' ')
+ index = 20;
+ else
+ index = 22;
+
+ wchar_t fullUrl[1024] = L"http://client.winamp.com/nowplaying";
+ lstrcpynW(fullUrl, AutoWide(g_config->ReadString("nowplayingurl", "http://client.winamp.com/nowplaying")), ARRAYSIZE(fullUrl));
+
+ if (filename[index] != 0)
+ {
+ StringCchCatW(fullUrl, 1024, filename + index);
+ }
+ Navigation_ShowService(SERVICE_ID, fullUrl, NAVFLAG_FORCEACTIVE | NAVFLAG_ENSUREMLVISIBLE | NAVFLAG_ENSUREITEMVISIBLE);
+ return HANDLED_EXCLUSIVE;
+ }
+ return NOT_HANDLED;
+}
+
+int NowPlayingURIHandler::IsMine(const wchar_t *filename)
+{
+ if (!_wcsnicmp(filename, L"winamp://Now Playing", 20 ) || !_wcsnicmp(filename, L"winamp://Now%20Playing", 22))
+ return HANDLED;
+ else
+ return NOT_HANDLED;
+}
+
+#define CBCLASS NowPlayingURIHandler
+START_DISPATCH;
+CB(PROCESSFILENAME, ProcessFilename);
+CB(ISMINE, IsMine);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/handler.h b/Src/Plugins/Library/ml_nowplaying/handler.h
new file mode 100644
index 00000000..d5021422
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/handler.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "../Agave/URIHandler/svc_urihandler.h"
+
+// {7A8BAF83-3995-4550-B15B-12D297A9220E}
+static const GUID ml_nowplaying_uri_handler =
+{ 0x7a8baf83, 0x3995, 0x4550, { 0xb1, 0x5b, 0x12, 0xd2, 0x97, 0xa9, 0x22, 0xe } };
+
+class NowPlayingURIHandler : public svc_urihandler
+{
+public:
+ static const char *getServiceName() { return "Now Playing URI Handler"; }
+ static GUID getServiceGuid() { return ml_nowplaying_uri_handler; }
+ int ProcessFilename(const wchar_t *filename);
+ int IsMine(const wchar_t *filename); // just like ProcessFilename but don't actually process
+
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/local_menu.cpp b/Src/Plugins/Library/ml_nowplaying/local_menu.cpp
new file mode 100644
index 00000000..33d3da7f
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/local_menu.cpp
@@ -0,0 +1,68 @@
+#include "main.h"
+#include "./local_menu.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./navigation.h"
+#include "../gen_ml/ml_ipc_0313.h"
+#include "../nu/menuHelpers.h"
+
+#define SUBMENU_NAVIGATIONCONTEXT 0
+
+static HMENU Menu_GetNavigationContext(HMENU baseMenu)
+{
+ HMENU hMenu = GetSubMenu(baseMenu, SUBMENU_NAVIGATIONCONTEXT);
+ if (NULL == hMenu) return NULL;
+
+ hMenu = MenuHelper_DuplcateMenu(hMenu);
+ if (NULL == hMenu) return NULL;
+
+ HNAVITEM hActive = Navigation_GetActive(NULL);
+ if (NULL != hActive)
+ {
+ EnableMenuItem(hMenu, ID_NAVIGATION_OPEN, MF_BYCOMMAND | MF_GRAYED | MF_DISABLED);
+ }
+ else
+ {
+ EnableMenuItem(hMenu, ID_NAVIGATION_OPEN, MF_BYCOMMAND | MF_ENABLED);
+ SetMenuDefaultItem(hMenu, ID_NAVIGATION_OPEN, FALSE);
+ }
+
+ return hMenu;
+}
+
+HMENU Menu_GetMenu(UINT menuKind)
+{
+ HMENU baseMenu = WASABI_API_LOADMENUW(IDR_CONTEXTMENU);
+ if (NULL == baseMenu)
+ return NULL;
+
+ switch(menuKind)
+ {
+ case MENU_NAVIGATIONCONTEXT:
+ {
+ HMENU menu = Menu_GetNavigationContext(baseMenu);
+ if (!GetModuleHandle(L"ml_online.dll"))
+ {
+ if (DeleteMenu(menu, ID_PLUGIN_PREFERENCES, MF_BYCOMMAND))
+ {
+ DeleteMenu(menu, 2, MF_BYPOSITION);
+ }
+ }
+ return menu;
+ }
+ }
+
+ return NULL;
+}
+
+BOOL Menu_ReleaseMenu(HMENU hMenu, UINT menuKind)
+{
+ if (NULL == hMenu) return FALSE;
+
+ switch(menuKind)
+ {
+ case MENU_NAVIGATIONCONTEXT:
+ return DestroyMenu(hMenu);
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/local_menu.h b/Src/Plugins/Library/ml_nowplaying/local_menu.h
new file mode 100644
index 00000000..bd33edcc
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/local_menu.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_MENU_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_MENU_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#define MENU_NAVIGATIONCONTEXT 0
+
+HMENU Menu_GetMenu(UINT menuKind);
+BOOL Menu_ReleaseMenu(HMENU hMenu, UINT menuKind);
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/main.cpp b/Src/Plugins/Library/ml_nowplaying/main.cpp
new file mode 100644
index 00000000..4b5f2944
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/main.cpp
@@ -0,0 +1,160 @@
+#include "main.h"
+#include "./navigation.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./external.h"
+#include "./wasabiCallback.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../winamp/wa_ipc.h"
+#include "handler.h"
+#include "../nu/Singleton.h"
+#include "../ml_online/config.h"
+#include <strsafe.h>
+
+static NowPlayingURIHandler uri_handler;
+static SingletonServiceFactory<svc_urihandler, NowPlayingURIHandler> uri_handler_factory;
+
+static DWORD externalCookie = 0;
+static SysCallback *wasabiCallback = NULL;
+C_Config *g_config = NULL;
+
+static INT Plugin_Init(void);
+static void Plugin_Quit(void);
+static INT_PTR Plugin_MessageProc(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+EXTERN_C winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_nowplaying.dll)",
+ Plugin_Init,
+ Plugin_Quit,
+ Plugin_MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+HINSTANCE Plugin_GetInstance(void)
+{
+ return plugin.hDllInstance;
+}
+
+HWND Plugin_GetWinamp(void)
+{
+ return plugin.hwndWinampParent;
+}
+
+HWND Plugin_GetLibrary(void)
+{
+ return plugin.hwndLibraryParent;
+}
+
+void initConfigCache()
+{
+ wchar_t iniFileName[2048] = {0};
+ mediaLibrary.BuildPath(L"Plugins\\ml", iniFileName, 2048);
+ CreateDirectory(iniFileName, NULL);
+ mediaLibrary.BuildPath(L"Plugins\\ml\\ml_online.ini", iniFileName, 2048);
+ AutoChar charFn(iniFileName);
+ g_config = new C_Config(AutoChar(iniFileName));
+}
+
+static INT Plugin_Init(void)
+{
+ if (!WasabiApi_Initialize(Plugin_GetInstance()))
+ return 1;
+
+ if (NULL == OMBROWSERMNGR)
+ {
+ WasabiApi_Release();
+ return 2;
+ }
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ initConfigCache();
+
+ if (NULL != WASABI_API_LNG)
+ {
+ 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;
+ }
+
+ ExternalDispatch *externalDispatch;
+ if (SUCCEEDED(ExternalDispatch::CreateInstance(&externalDispatch)))
+ {
+ DispatchInfo dispatchInfo;
+ dispatchInfo.id = 0;
+ dispatchInfo.name =(LPWSTR)externalDispatch->GetName();
+ dispatchInfo.dispatch = externalDispatch;
+
+ if (0 == SENDWAIPC(Plugin_GetWinamp(), IPC_ADD_DISPATCH_OBJECT, (WPARAM)&dispatchInfo))
+ externalCookie = dispatchInfo.id;
+
+ externalDispatch->Release();
+ }
+
+ if(NULL != WASABI_API_SYSCB && NULL == wasabiCallback &&
+ SUCCEEDED(WasabiCallback::CreateInstance((WasabiCallback**)&wasabiCallback)))
+ {
+ WASABI_API_SYSCB->syscb_registerCallback(wasabiCallback, 0);
+ for (;;)
+ {
+ SysCallback *callback = WASABI_API_SYSCB->syscb_enum(SysCallback::BROWSER, 0);
+ if (NULL == callback || callback == wasabiCallback)
+ {
+ if (NULL != callback)
+ callback->Release();
+ break;
+ }
+
+ WASABI_API_SYSCB->syscb_deregisterCallback(callback);
+ WASABI_API_SYSCB->syscb_registerCallback(callback, 0);
+ callback->Release();
+ }
+ }
+
+ uri_handler_factory.Register(plugin.service, &uri_handler);
+ Navigation_Initialize();
+
+ return 0;
+}
+
+static void Plugin_Quit(void)
+{
+ if (NULL != wasabiCallback)
+ {
+ if (NULL != WASABI_API_SYSCB)
+ WASABI_API_SYSCB->syscb_deregisterCallback(wasabiCallback);
+ wasabiCallback->Release();
+ wasabiCallback = NULL;
+ }
+
+ if (0 != externalCookie)
+ {
+ HWND hWinamp = Plugin_GetWinamp();
+ SENDWAIPC(hWinamp, IPC_REMOVE_DISPATCH_OBJECT, (WPARAM)externalCookie);
+ externalCookie = 0;
+ }
+ uri_handler_factory.Deregister(plugin.service);
+ WasabiApi_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;
+}
+
+EXTERN_C __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/main.h b/Src/Plugins/Library/ml_nowplaying/main.h
new file mode 100644
index 00000000..2141525e
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/main.h
@@ -0,0 +1,22 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_MAIN_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_MAIN_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../gen_ml/ml.h"
+#include "./common.h"
+
+#define PLUGIN_VERSION_MAJOR 4
+#define PLUGIN_VERSION_MINOR 1
+
+HINSTANCE Plugin_GetInstance(void);
+HWND Plugin_GetWinamp(void);
+HWND Plugin_GetLibrary(void);
+
+#include "../ml_online/config.h"
+extern C_Config *g_config;
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_MAIN_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.rc b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.rc
new file mode 100644
index 00000000..fea60a30
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.rc
@@ -0,0 +1,99 @@
+// 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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENU MENU
+BEGIN
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Open", ID_NAVIGATION_OPEN
+ MENUITEM "Open in &New Window", ID_NAVIGATION_OPENNEWWINDOW
+ MENUITEM SEPARATOR
+ MENUITEM "&Preferences", 40006
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Now Playing v%d.%02d"
+ 65535 "{7F31F590-6602-45c9-B3F8-F61AE05BD1D3}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_SERVICE_NAME "Now Playing"
+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_nowplaying/ml_nowplaying.sln b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.sln
new file mode 100644
index 00000000..cf52a91e
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.sln
@@ -0,0 +1,30 @@
+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_nowplaying", "ml_nowplaying.vcxproj", "{CB59733D-8D69-4DC4-AB67-C831DAE5A80F}"
+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
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Debug|Win32.Build.0 = Debug|Win32
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Debug|x64.ActiveCfg = Debug|x64
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Debug|x64.Build.0 = Debug|x64
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Release|Win32.ActiveCfg = Release|Win32
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Release|Win32.Build.0 = Release|Win32
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Release|x64.ActiveCfg = Release|x64
+ {CB59733D-8D69-4DC4-AB67-C831DAE5A80F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0DF91B23-5CCB-4CD1-8A07-03B96BB9324E}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj
new file mode 100644
index 00000000..0c9effee
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj
@@ -0,0 +1,280 @@
+<?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>{CB59733D-8D69-4DC4-AB67-C831DAE5A80F}</ProjectGuid>
+ <RootNamespace>ml_nowplaying</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>true</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>..\..\..\external_dependencies\vcpkg</VcpkgInstalledDir>
+ <VcpkgUseStatic>true</VcpkgUseStatic>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>..\..\..\external_dependencies\vcpkg</VcpkgInstalledDir>
+ <VcpkgUseStatic>true</VcpkgUseStatic>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>..\..\..\external_dependencies\vcpkg</VcpkgInstalledDir>
+ <VcpkgUseStatic>true</VcpkgUseStatic>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>..\..\..\external_dependencies\vcpkg</VcpkgInstalledDir>
+ <VcpkgUseStatic>true</VcpkgUseStatic>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;TEST_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName).dll</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\
+xcopy /Y /D $(OutDir)*.pdb ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;TEST_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName).dll</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\
+xcopy /Y /D $(OutDir)*.pdb ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_WIN32_WINNT=0x601;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName).dll</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>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_WIN32_WINNT=0x601;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName).dll</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>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)*.dll ..\..\..\..\..\Winamp_$(PlatformShortName)_$(Configuration)\plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\ml_online\config.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menuHelpers.cpp" />
+ <ClCompile Include="..\..\..\nu\trace.cpp" />
+ <ClCompile Include="common.cpp" />
+ <ClCompile Include="external.cpp" />
+ <ClCompile Include="handler.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="navigation.cpp" />
+ <ClCompile Include="service.cpp" />
+ <ClCompile Include="wasabi.cpp" />
+ <ClCompile Include="wasabiCallback.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\ml_online\config.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="common.h" />
+ <ClInclude Include="external.h" />
+ <ClInclude Include="handler.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="navigation.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="service.h" />
+ <ClInclude Include="wasabi.h" />
+ <ClInclude Include="wasabiCallback.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_nowplaying.rc" />
+ <ResourceCompile Include="png.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\omBrowser\omBrowser.vcxproj">
+ <Project>{fc74db95-5008-4d22-9147-1c052f43cdd3}</Project>
+ </ProjectReference>
+ <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_nowplaying/ml_nowplaying.vcxproj.filters b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj.filters
new file mode 100644
index 00000000..046d44c4
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/ml_nowplaying.vcxproj.filters
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="common.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ml_online\config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="external.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="handler.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="..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\gen_ml\menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nu\menuHelpers.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="navigation.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="service.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nu\trace.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wasabi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wasabiCallback.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="common.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\ml_online\config.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="external.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="handler.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="..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\gen_ml\menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="navigation.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="service.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="wasabi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="wasabiCallback.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="png.rc">
+ <Filter>Header Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="ml_nowplaying.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{a1f12bb1-c2c0-49d3-b74a-5f8bbac3dee6}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{8fef1ee3-d193-47aa-872e-843f5178bcc5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{f17e7245-8101-4787-b60f-874085ddb8a7}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/navigation.cpp b/Src/Plugins/Library/ml_nowplaying/navigation.cpp
new file mode 100644
index 00000000..8f662db5
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/navigation.cpp
@@ -0,0 +1,684 @@
+#include "main.h"
+#include "./navigation.h"
+#include "./resource.h"
+#include "./wasabi.h"
+#include "./service.h"
+#include "../omBrowser/browserView.h"
+#include "../winamp/wa_ipc.h"
+#include "../replicant/nu/Autowide.h"
+#include "../gen_ml/ml_ipc_0313.h"
+#include "./local_menu.h"
+#include "../gen_ml/menu.h"
+#include <strsafe.h>
+
+#define NAVITEM_PREFIX L"nowplaying_svc_"
+
+#define E_NAVITEM_UNKNOWN E_NOINTERFACE
+
+typedef struct __NAVENUMRESULT
+{
+ HNAVITEM hItem;
+ OmService *service;
+ UINT serviceId;
+ LPCWSTR pszPrefix;
+ INT cchPrefix;
+ HWND hLibrary;
+ NAVITEM itemInfo;
+ WCHAR szBuffer[256];
+} NAVENUMRESULT;
+
+typedef struct __FORCEURLDATA
+{
+ UINT serviceId;
+ LPWSTR url;
+} FORCEURLDATA;
+
+#define FORCEURLPROP L"MLNOWPLAYING_FORCEURL"
+
+static void Navigation_RemoveForceUrl()
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return;
+
+ FORCEURLDATA *data = (FORCEURLDATA*)GetProp(hLibrary, FORCEURLPROP);
+ RemoveProp(hLibrary, FORCEURLPROP);
+ if (NULL != data)
+ {
+ Plugin_FreeString(data->url);
+ free(data);
+ }
+}
+
+static HRESULT Navigation_SetForceUrl(UINT serviceId, LPCWSTR pszUrl)
+{
+ if (NULL == pszUrl) return E_INVALIDARG;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_FAIL;
+
+ FORCEURLDATA *data = (FORCEURLDATA*)GetProp(hLibrary, FORCEURLPROP);
+
+ if (NULL != data)
+ {
+ Plugin_FreeString(data->url);
+ if (data->serviceId != serviceId)
+ {
+ free(data);
+ data = NULL;
+ }
+ }
+
+ if (NULL == data)
+ {
+ data = (FORCEURLDATA*)calloc(1, sizeof(FORCEURLDATA));
+ if (NULL == data) return E_OUTOFMEMORY;
+ data->serviceId = serviceId;
+ }
+
+ data->url = Plugin_CopyString(pszUrl);
+ if (NULL == data->url || FALSE == SetProp(hLibrary, FORCEURLPROP, data))
+ {
+ Navigation_RemoveForceUrl();
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+static HRESULT Navigation_GetForceUrl(UINT serviceId, const wchar_t **ppszUrl)
+{
+ if (NULL == ppszUrl) return E_POINTER;
+ *ppszUrl = NULL;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_FAIL;
+
+ FORCEURLDATA *data = (FORCEURLDATA*)GetProp(hLibrary, FORCEURLPROP);
+
+ if (NULL == data || data->serviceId != serviceId)
+ return E_NOINTERFACE;
+
+ *ppszUrl = data->url;
+ return S_OK;
+}
+
+static INT Navigation_GetIconIndex(LPCWSTR pszImage)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return -1;
+
+ HMLIMGLST hmlilNavigation = MLNavCtrl_GetImageList(hLibrary);
+ if (NULL == hmlilNavigation) return -1;
+
+ MLIMAGESOURCE mlis;
+ ZeroMemory(&mlis, sizeof(mlis));
+ mlis.cbSize = sizeof(mlis);
+ mlis.hInst = NULL;
+ mlis.bpp = 24;
+ mlis.lpszName = pszImage;
+ mlis.type = SRC_TYPE_PNG;
+ mlis.flags = ISF_FORCE_BPP | ISF_PREMULTIPLY | ISF_LOADFROMFILE;
+
+ MLIMAGELISTITEM item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.hmlil = hmlilNavigation;
+ item.filterUID = MLIF_FILTER3_UID;
+ item.pmlImgSource = &mlis;
+
+ return MLImageList_Add(hLibrary, &item);
+}
+
+static HNAVITEM Navigation_CreateItem(HWND hLibrary, HNAVITEM hParent, OmService *service)
+{
+ if (NULL == hLibrary || NULL == service)
+ return NULL;
+
+ WCHAR szName[256] = {0}, szInvariant[64] = {0};
+ if (FAILED(service->GetName(szName, ARRAYSIZE(szName))))
+ return NULL;
+
+ if (FAILED(StringCchPrintf(szInvariant, ARRAYSIZE(szInvariant), NAVITEM_PREFIX L"%u", service->GetId())))
+ return NULL;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.hInsertAfter = NULL;
+ nis.hParent = hParent;
+
+ WCHAR szIcon[512] = {0};
+ INT iIcon = (SUCCEEDED(service->GetIcon(szIcon, ARRAYSIZE(szIcon)))) ?
+ Navigation_GetIconIndex(szIcon) : -1;
+
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT | NIMF_PARAM;
+ if (-1 != iIcon)
+ nis.item.mask |= (NIMF_IMAGE | NIMF_IMAGESEL);
+
+ nis.item.id = 0;
+ nis.item.pszText = szName;
+ nis.item.pszInvariant = szInvariant;
+ nis.item.style = NIS_ALLOWCHILDMOVE;
+ nis.item.styleMask = nis.item.style;
+ nis.item.lParam = (LPARAM)service;
+ nis.item.iImage = iIcon;
+ nis.item.iSelectedImage = iIcon;
+
+ HNAVITEM hItem = MLNavCtrl_InsertItem(hLibrary, &nis);
+ if (NULL != hItem)
+ service->AddRef();
+
+ return hItem;
+}
+
+static HNAVITEM Navigation_GetMessageItem(INT msg, INT_PTR param1)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ HNAVITEM hItem = (msg < ML_MSG_NAVIGATION_FIRST) ? MLNavCtrl_FindItemById(hLibrary, param1) : (HNAVITEM)param1;
+ return hItem;
+}
+
+static HRESULT Navigation_GetService(HWND hLibrary, HNAVITEM hItem, OmService **service)
+{
+ WCHAR szBuffer[64] = {0};
+
+ if (NULL == service) return E_POINTER;
+ *service = NULL;
+
+ if (NULL == hLibrary || NULL == hItem) return E_INVALIDARG;
+
+ NAVITEM itemInfo = {0};
+ itemInfo.cbSize = sizeof(NAVITEM);
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT;
+
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+
+ INT cchInvariant = lstrlen(szBuffer);
+ INT cchPrefix = ARRAYSIZE(NAVITEM_PREFIX) - 1;
+ if (cchInvariant <= cchPrefix ||
+ CSTR_EQUAL != CompareString(CSTR_INVARIANT, 0, NAVITEM_PREFIX, cchPrefix, szBuffer, cchPrefix))
+ {
+ return E_NAVITEM_UNKNOWN;
+ }
+
+ *service = (OmService*)itemInfo.lParam;
+ (*service)->AddRef();
+ return S_OK;
+}
+
+
+static BOOL CALLBACK Navigation_ItemEnumerator(HNAVITEM hItem, LPARAM param)
+{
+ if (NULL == hItem) return TRUE;
+ NAVENUMRESULT *result = (NAVENUMRESULT*)param;
+ if (NULL == result) return FALSE;
+
+ result->itemInfo .hItem = hItem;
+ if (FALSE != MLNavItem_GetInfo(result->hLibrary, &result->itemInfo) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, result->itemInfo.pszInvariant, result->cchPrefix,
+ result->pszPrefix, result->cchPrefix))
+ {
+ OmService *service = (OmService*)result->itemInfo.lParam;
+ if (NULL != service && service->GetId() == result->serviceId)
+ {
+ result->hItem = hItem;
+ result->service = service;
+ service->AddRef();
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static HRESULT Navigation_CreateView(HNAVITEM hItem, HWND hParent, HWND *hView)
+{
+ if (NULL == hView) return E_POINTER;
+ *hView = NULL;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ if (NULL == hItem || NULL == hParent) return E_INVALIDARG;
+
+ HRESULT hr;
+
+ OmService *service = NULL;
+ hr = Navigation_GetService(hLibrary, hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ if (NULL == OMBROWSERMNGR)
+ hr = E_UNEXPECTED;
+
+ if (SUCCEEDED(hr))
+ {
+ hr = OMBROWSERMNGR->Initialize(NULL, Plugin_GetWinamp());
+ if (SUCCEEDED(hr))
+ {
+ LPCWSTR forceUrl;
+ if (FAILED(Navigation_GetForceUrl(service->GetId(), &forceUrl)))
+ forceUrl = NULL;
+
+ hr = OMBROWSERMNGR->CreateView(service, hParent, forceUrl, 0, hView);
+ Navigation_RemoveForceUrl();
+ }
+ }
+
+ wchar_t nowplayingurl[1024] = {0};
+ // May 2022 - this service url is dead and would need either fixing up or replacing
+ lstrcpynW(nowplayingurl, AutoWide(g_config->ReadString("nowplayingurl", "http://client.winamp.com/nowplaying")), ARRAYSIZE(nowplayingurl));
+ service->SetUrl(nowplayingurl[0] ? nowplayingurl : SERVICE_HOMEURL);
+ service->Release();
+ }
+ return hr;
+}
+
+static BOOL Navigation_GetViewRect(RECT *rect)
+{
+ if (NULL == rect) return FALSE;
+
+ HWND hWinamp = Plugin_GetWinamp();
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hWinamp || NULL == hLibrary)
+ return FALSE;
+
+ HWND hFrame = (HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == hFrame)
+ hFrame = hLibrary;
+
+ return GetWindowRect(hFrame, rect);
+}
+
+static HRESULT Navigation_CreatePopup(HNAVITEM hItem, HWND *hWindow)
+{
+ if (NULL == hWindow) return E_POINTER;
+ *hWindow = NULL;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ if (NULL == hItem) return E_INVALIDARG;
+
+ HRESULT hr;
+
+ OmService *service;
+ hr = Navigation_GetService(hLibrary, hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ HWND hWinamp = Plugin_GetWinamp();
+
+ if (NULL == OMBROWSERMNGR)
+ hr = E_UNEXPECTED;
+
+ if (SUCCEEDED(hr))
+ {
+ hr = OMBROWSERMNGR->Initialize(NULL, hWinamp);
+ if (SUCCEEDED(hr))
+ {
+ RECT rect;
+ if (FALSE == Navigation_GetViewRect(&rect))
+ hr = E_FAIL;
+
+ if (SUCCEEDED(hr))
+ {
+ rect.left += 16;
+ rect.top += 16;
+
+ hr = OMBROWSERMNGR->CreatePopup(service, rect.left, rect.top,
+ rect.right - rect.left, rect.bottom - rect.top, hWinamp, NULL, 0, hWindow);
+ }
+ }
+ }
+
+ service->Release();
+ }
+
+ return hr;
+}
+
+
+static void Navigation_OnDestroy()
+{
+ Navigation_RemoveForceUrl();
+
+ if (NULL != OMBROWSERMNGR)
+ {
+ OMBROWSERMNGR->Finish();
+ }
+}
+
+static void Navigation_OpenPreferences()
+{
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(GetModuleHandle(L"ml_online.dll"), "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ mlplugin->MessageProc(ML_MSG_CONFIG, 0, 0, 0);
+ }
+ else
+ SendMessage(Plugin_GetWinamp(), WM_WA_IPC, (WPARAM)-1, IPC_OPENPREFSTOPAGE);
+ }
+ else
+ SendMessage(Plugin_GetWinamp(), WM_WA_IPC, (WPARAM)-1, IPC_OPENPREFSTOPAGE);
+}
+
+static HRESULT Navigation_ShowContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts)
+{
+ if (NULL == hItem || NULL == hHost)
+ return E_INVALIDARG;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ HRESULT hr;
+
+ OmService *service;
+ hr = Navigation_GetService(hLibrary, hItem, &service);
+ if (FAILED(hr)) return hr;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(hLibrary, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU hMenu = Menu_GetMenu(MENU_NAVIGATIONCONTEXT);
+ if (NULL != hMenu)
+ {
+ INT commandId = Menu_TrackPopup(hLibrary, hMenu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ pt.x, pt.y, hHost, NULL);
+
+ Menu_ReleaseMenu(hMenu, MENU_NAVIGATIONCONTEXT);
+
+ switch(commandId)
+ {
+ case ID_NAVIGATION_OPEN:
+ MLNavItem_Select(hLibrary, hItem);
+ break;
+
+ case ID_NAVIGATION_OPENNEWWINDOW:
+ {
+ HWND hWindow;
+ if (SUCCEEDED(Navigation_CreatePopup(hItem, &hWindow)))
+ {
+ ShowWindow(hWindow, SW_SHOWNORMAL);
+ }
+ }
+ break;
+ case ID_NAVIGATION_HELP:
+ SENDWAIPC(Plugin_GetWinamp(), IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8105304048660-The-Winamp-Media-Library");
+ break;
+
+ case ID_PLUGIN_PREFERENCES:
+ Navigation_OpenPreferences();
+ break;
+ }
+ }
+
+ service->Release();
+
+ return hr;
+}
+
+BOOL Navigation_Initialize(void)
+{
+ OmService *service;
+ HWND hLibrary = Plugin_GetLibrary();
+
+ MLNavCtrl_BeginUpdate(hLibrary, NUF_LOCK_TOP);
+
+ if (SUCCEEDED(OmService::CreateInstance(&service)))
+ {
+ HNAVITEM hParent = NULL;
+ Navigation_CreateItem(hLibrary, hParent, service);
+ service->Release();
+ }
+
+ MLNavCtrl_EndUpdate(hLibrary);
+
+ return TRUE;
+}
+
+static void Navigation_OnDeleteItem(HNAVITEM hItem)
+{
+ if (NULL == hItem) return;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return;
+
+ OmService *service;
+ if (SUCCEEDED(Navigation_GetService(hLibrary, hItem, &service)))
+ {
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(NAVITEM);
+ itemInfo.hItem = hItem;
+ itemInfo.mask = NIMF_PARAM;
+ itemInfo.lParam = 0L;
+ MLNavItem_SetInfo(hLibrary, &itemInfo);
+
+ service->Release(); // create
+ service->Release(); // Navigation_GetService
+ }
+}
+
+BOOL Navigation_ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result)
+{
+ if (msg == ML_MSG_NO_CONFIG)
+ {
+ if (!GetModuleHandle(L"ml_online.dll"))
+ {
+ *result = TRUE;
+ return TRUE;
+ }
+ }
+ else if (msg == ML_MSG_CONFIG)
+ {
+ Navigation_OpenPreferences();
+ *result = TRUE;
+ return TRUE;
+ }
+
+ if (msg < ML_MSG_TREE_BEGIN || msg > ML_MSG_TREE_END)
+ return FALSE;
+
+ switch(msg)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ {
+ HWND hView;
+ HNAVITEM hItem = Navigation_GetMessageItem(msg, param1);
+ HRESULT hr = Navigation_CreateView(hItem, (HWND)param2, &hView);
+ if (SUCCEEDED(hr))
+ {
+ *result = (INT_PTR)hView;
+ return TRUE;
+ }
+ }
+ break;
+
+ case ML_MSG_NAVIGATION_ONDESTROY:
+ Navigation_OnDestroy();
+ break;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ HNAVITEM hItem = Navigation_GetMessageItem(msg, param1);
+ HRESULT hr = Navigation_ShowContextMenu(hItem, (HWND)param2, MAKEPOINTS(param3));
+ if (SUCCEEDED(hr))
+ {
+ *result = TRUE;
+ return TRUE;
+ }
+ }
+ break;
+
+ case ML_MSG_NAVIGATION_ONDELETE:
+ {
+ HNAVITEM hItem = Navigation_GetMessageItem(msg, param1);
+ Navigation_OnDeleteItem(hItem);
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+HNAVITEM Navigation_FindService(UINT serviceId, OmService **serviceOut)
+{
+ NAVENUMRESULT result;
+ result.hItem = NULL;
+ result.service = NULL;
+
+ result.serviceId = serviceId;
+ result.pszPrefix = NAVITEM_PREFIX;
+ result.cchPrefix = lstrlen(result.pszPrefix);
+
+ result.hLibrary = Plugin_GetLibrary();
+ result.itemInfo.cbSize = sizeof(result.itemInfo);
+ result.itemInfo.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
+ result.itemInfo.cchInvariantMax = ARRAYSIZE(result.szBuffer);
+ result.itemInfo.pszInvariant = result.szBuffer;
+
+ NAVCTRLENUMPARAMS param;
+ param.enumProc = Navigation_ItemEnumerator;
+ param.hItemStart = NULL;
+ param.lParam = (LPARAM)&result;
+
+ if (NULL != result.hLibrary)
+ MLNavCtrl_EnumItems(result.hLibrary, &param);
+
+ if (NULL != serviceOut)
+ *serviceOut = result.service;
+ else if (NULL != result.service)
+ result.service->Release();
+
+ return result.hItem;
+}
+
+HRESULT Navigation_ShowService(UINT serviceId, LPCWSTR pszUrl, UINT navFlags)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary)
+ return E_FAIL;
+
+ OmService *service;
+ HNAVITEM hItem = Navigation_FindService(serviceId, &service);
+ if (NULL == hItem)
+ return E_FAIL;
+
+ OmService *activeService;
+ HWND hView = Navigation_GetActiveView(&activeService);
+ if (NULL == hView || activeService->GetId() != service->GetId())
+ {
+ hView = NULL;
+ activeService = NULL;
+ }
+
+ HRESULT hr = S_OK;
+
+ if (NULL != hView)
+ {
+ if (NULL == pszUrl && 0 != (NAVFLAG_FORCEACTIVE & navFlags))
+ pszUrl = NAVIGATE_HOME;
+
+ if (NULL != pszUrl && FALSE == BrowserView_Navigate(hView, pszUrl, TRUE))
+ hr = E_FAIL;
+ }
+ else
+ {
+ if (NULL != pszUrl)
+ hr = Navigation_SetForceUrl(serviceId, pszUrl);
+ else
+ Navigation_RemoveForceUrl();
+
+ if (SUCCEEDED(hr) && FALSE == MLNavItem_Select(hLibrary, hItem))
+ {
+ Navigation_RemoveForceUrl();
+ hr = E_FAIL;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (0 != (NAVFLAG_ENSUREITEMVISIBLE & navFlags))
+ MLNavItem_EnsureVisible(hLibrary, hItem);
+
+ if (0 != (NAVFLAG_ENSUREMLVISIBLE & navFlags))
+ SENDMLIPC(hLibrary, ML_IPC_ENSURE_VISIBLE, 0L);
+ }
+
+ service->Release();
+ if (NULL != activeService)
+ activeService->Release();
+
+ return hr;
+
+}
+HNAVITEM Navigation_GetActive(OmService **serviceOut)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+
+ OmService *service;
+ HNAVITEM hActive = (NULL != hLibrary) ? MLNavCtrl_GetSelection(hLibrary) : NULL;
+ if (NULL == hActive || FAILED(Navigation_GetService(hLibrary, hActive, &service)))
+ {
+ hActive = NULL;
+ service = NULL;
+ }
+
+ if (NULL != serviceOut)
+ *serviceOut = service;
+
+ return hActive;
+}
+
+HWND Navigation_GetActiveView(OmService **serviceOut)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary)
+ {
+ if (NULL != serviceOut) *serviceOut = NULL;
+ return NULL;
+ }
+
+
+ HWND hView =((HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0));
+ if (NULL != hView)
+ {
+ WCHAR szBuffer[128] = {0};
+ if (!GetClassName(hView, szBuffer, ARRAYSIZE(szBuffer)) || CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, szBuffer, -1, L"Nullsoft_omBrowserView", -1))
+ hView = NULL;
+ }
+
+ OmService *service;
+ HNAVITEM hActive = (NULL != hLibrary) ? MLNavCtrl_GetSelection(hLibrary) : NULL;
+ if (NULL == hView || FALSE == BrowserView_GetService(hView, &service))
+ {
+ hView = NULL;
+ service = NULL;
+ }
+
+ if (NULL != serviceOut)
+ *serviceOut = service;
+ else if (NULL != service)
+ service->Release();
+
+ return hView;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/navigation.h b/Src/Plugins/Library/ml_nowplaying/navigation.h
new file mode 100644
index 00000000..d37264db
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/navigation.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_NAVIGATION_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_NAVIGATION_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef LPVOID HNAVITEM;
+class OmService;
+
+
+BOOL Navigation_Initialize(void);
+BOOL Navigation_ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result);
+
+#define NAVFLAG_NORMAL 0x0000
+#define NAVFLAG_ENSUREITEMVISIBLE 0x0001
+#define NAVFLAG_ENSUREMLVISIBLE 0x0002
+#define NAVFLAG_FORCEACTIVE 0x0004
+
+HRESULT Navigation_ShowService(UINT serviceId, LPCWSTR pszUrl, UINT navFlags);
+HNAVITEM Navigation_FindService(UINT serviceId, OmService **serviceOut);
+HNAVITEM Navigation_GetActive(OmService **serviceOut);
+HWND Navigation_GetActiveView(OmService **serviceOut);
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_NAVIGATION_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/png.rc b/Src/Plugins/Library/ml_nowplaying/png.rc
new file mode 100644
index 00000000..ac25fbdd
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/png.rc
@@ -0,0 +1,7 @@
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Data
+//
+IDR_SERVICE_ICON RCDATA
+".\\resources\\serviceIcon.png" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/resource.h b/Src/Plugins/Library/ml_nowplaying/resource.h
new file mode 100644
index 00000000..74da0171
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/resource.h
@@ -0,0 +1,23 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_nowplaying.rc
+//
+#define IDS_SERVICE_NAME 1
+#define IDR_CONTEXTMENU 101
+#define IDR_SERVICE_ICON 20000
+#define ID_NAVIGATION_OPENNEWWINDOW 40000
+#define ID_NAVIGATION_OPEN 40001
+#define ID_NAVIGATION_HELP 40005
+#define ID_PLUGIN_PREFERENCES 40006
+#define IDS_PLUGIN_NAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 105
+#define _APS_NEXT_COMMAND_VALUE 40007
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_nowplaying/resources/serviceIcon.png b/Src/Plugins/Library/ml_nowplaying/resources/serviceIcon.png
new file mode 100644
index 00000000..13d47f3b
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/resources/serviceIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_nowplaying/service.cpp b/Src/Plugins/Library/ml_nowplaying/service.cpp
new file mode 100644
index 00000000..5d37ec21
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/service.cpp
@@ -0,0 +1,171 @@
+#include "main.h"
+#include "./service.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "../replicant/nu/Autowide.h"
+#include "../winamp/wa_ipc.h"
+#include <strsafe.h>
+
+#define IS_INVALIDISPATCH(__disp) (((IDispatch *)1) == (__disp) || NULL == (__disp))
+
+OmService::OmService(UINT nId)
+ : ref(1), id(nId), name(NULL), url(NULL), icon(NULL)
+{
+}
+
+OmService::~OmService()
+{
+ Plugin_FreeResString(name);
+ Plugin_FreeString(url);
+ Plugin_FreeResString(icon);
+}
+
+HRESULT OmService::CreateInstance(OmService **instance)
+{
+ if (NULL == instance) return E_POINTER;
+ *instance = NULL;
+
+ OmService *service = new OmService(SERVICE_ID);
+ if (NULL == service) return E_OUTOFMEMORY;
+
+ wchar_t nowplayingurl[1024] = {0};
+ lstrcpynW(nowplayingurl, AutoWide(g_config->ReadString("nowplayingurl","")), ARRAYSIZE(nowplayingurl));
+
+ service->SetName(MAKEINTRESOURCE(IDS_SERVICE_NAME));
+ service->SetUrl((nowplayingurl[0] ? nowplayingurl : SERVICE_HOMEURL));
+ service->SetIcon(MAKEINTRESOURCE(IDR_SERVICE_ICON));
+
+ *instance = service;
+ return S_OK;
+}
+
+size_t OmService::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t OmService::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int OmService::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_OmService))
+ *object = static_cast<ifc_omservice*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+unsigned int OmService::GetId()
+{
+ return id;
+}
+
+HRESULT OmService::GetName(wchar_t *pszBuffer, int cchBufferMax)
+{
+ return Plugin_CopyResString(pszBuffer, cchBufferMax, name);
+}
+
+HRESULT OmService::GetUrl(wchar_t *pszBuffer, int cchBufferMax)
+{
+ return StringCchCopyEx(pszBuffer, cchBufferMax, url, NULL, NULL, STRSAFE_IGNORE_NULLS);
+}
+
+HRESULT OmService::GetIcon(wchar_t *pszBuffer, int cchBufferMax)
+{
+ if (NULL != icon && IS_INTRESOURCE(icon))
+ {
+ WCHAR szPath[2*MAX_PATH] = {0};
+ if (0 == GetModuleFileName(Plugin_GetInstance(), szPath, ARRAYSIZE(szPath)))
+ return E_FAIL;
+
+ return StringCchPrintf(pszBuffer, cchBufferMax, L"res://%s/#%d/#%d", szPath, RT_RCDATA, icon);
+ }
+
+ return StringCchCopyEx(pszBuffer, cchBufferMax, icon, NULL, NULL, STRSAFE_IGNORE_NULLS);
+}
+
+HRESULT OmService::GetExternal(IDispatch **ppDispatch)
+{
+ if (NULL == ppDispatch)
+ return E_POINTER;
+
+ *ppDispatch = NULL;
+
+ HWND hWinamp = Plugin_GetWinamp();
+ if (NULL == hWinamp)
+ return E_UNEXPECTED;
+
+ // So far we do not use JSAPI2 in nowplaying
+ // // try JSAPI2 first
+ // WCHAR szBuffer[64] = {0};
+ // if (SUCCEEDED(StringCchPrintfW(szBuffer, ARRAYSIZE(szBuffer), L"%u", id)))
+ // *ppDispatch = (IDispatch*)SENDWAIPC(hWinamp, IPC_JSAPI2_GET_DISPATCH_OBJECT, (WPARAM)szBuffer);
+
+ if (IS_INVALIDISPATCH(*ppDispatch))
+ { // try JSAPI1
+ *ppDispatch = (IDispatch*)SENDWAIPC(hWinamp, IPC_GET_DISPATCH_OBJECT, 0);
+ if (IS_INVALIDISPATCH(*ppDispatch))
+ { // Fail
+ *ppDispatch = NULL;
+ return E_FAIL;
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT OmService::SetName(LPCWSTR pszName)
+{
+ Plugin_FreeResString(name);
+ name = Plugin_DuplicateResString(pszName);
+ return S_OK;
+}
+
+HRESULT OmService::SetUrl(LPCWSTR pszUrl)
+{
+ Plugin_FreeString(url);
+ url = Plugin_CopyString(pszUrl);
+ return S_OK;
+}
+
+HRESULT OmService::SetIcon(LPCWSTR pszIcon)
+{
+ Plugin_FreeResString(icon);
+ icon = Plugin_DuplicateResString(pszIcon);
+ return S_OK;
+}
+
+#define CBCLASS OmService
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+CB(API_GETID, GetId)
+CB(API_GETNAME, GetName)
+CB(API_GETURL, GetUrl)
+CB(API_GETICON, GetIcon)
+CB(API_GETEXTERNAL, GetExternal)
+END_DISPATCH;
+#undef CBCLASS
+
+
diff --git a/Src/Plugins/Library/ml_nowplaying/service.h b/Src/Plugins/Library/ml_nowplaying/service.h
new file mode 100644
index 00000000..292a4628
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/service.h
@@ -0,0 +1,53 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_SERVICE_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_SERVICE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <ifc_omservice.h>
+
+#define SERVICE_ID 101
+#define SERVICE_HOMEURL L"http://client.winamp.com/nowplaying?v=5.9&icid=navigationtree"
+
+class OmService : public ifc_omservice
+{
+
+protected:
+ OmService(UINT nId);
+ ~OmService();
+
+public:
+ static HRESULT CreateInstance(OmService **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_omservice */
+ unsigned int GetId();
+ HRESULT GetName(wchar_t *pszBuffer, int cchBufferMax);
+ HRESULT GetUrl(wchar_t *pszBuffer, int cchBufferMax);
+ HRESULT GetExternal(IDispatch **ppDispatch);
+ HRESULT GetIcon(wchar_t *pszBuffer, int cchBufferMax);
+
+public:
+ HRESULT SetName(LPCWSTR pszName);
+ HRESULT SetUrl(LPCWSTR pszUrl);
+ HRESULT SetIcon(LPCWSTR pszIcon);
+
+protected:
+ RECVS_DISPATCH;
+
+protected:
+ ULONG ref;
+ UINT id;
+ LPWSTR name;
+ LPWSTR url;
+ LPWSTR icon;
+};
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_SERVICE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/version.rc2 b/Src/Plugins/Library/ml_nowplaying/version.rc2
new file mode 100644
index 00000000..47a11f64
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 4,0,1,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", "4,0,1,0"
+ VALUE "InternalName", "Nullsoft Nowplaying"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_nowplaying.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_nowplaying/wasabi.cpp b/Src/Plugins/Library/ml_nowplaying/wasabi.cpp
new file mode 100644
index 00000000..7a071143
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/wasabi.cpp
@@ -0,0 +1,82 @@
+#include "main.h"
+#include "./wasabi.h"
+#include <api/service/waservicefactory.h>
+
+static ULONG wasabiRef = 0;
+
+api_application *WASABI_API_APP = NULL;
+api_language *WASABI_API_LNG = NULL;
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = NULL;
+obj_ombrowser *browserManager = NULL;
+api_syscb *WASABI_API_SYSCB = NULL;
+ifc_omutility *omUtility = NULL;
+
+HINSTANCE WASABI_API_LNG_HINST = NULL;
+HINSTANCE WASABI_API_ORIG_HINST = NULL;
+EXTERN_C winampMediaLibraryPlugin plugin;
+
+void *Wasabi_QueryInterface(REFGUID interfaceGuid)
+{
+ waServiceFactory *serviceFactory = plugin.service->service_getServiceByGuid(interfaceGuid);
+ return (NULL != serviceFactory) ? serviceFactory->getInterface() : NULL;
+}
+
+void Wasabi_ReleaseInterface(REFGUID interfaceGuid, void *pInstance)
+{
+ waServiceFactory *serviceFactory = plugin.service->service_getServiceByGuid(interfaceGuid);
+ if (NULL != serviceFactory) serviceFactory->releaseInterface(pInstance);
+}
+
+void Wasabi_SafeRelease(Dispatchable *pDisp)
+{
+ if (NULL != pDisp)
+ pDisp->Release();
+}
+
+BOOL WasabiApi_Initialize(HINSTANCE hInstance)
+{
+ WASABI_API_APP = QueryWasabiInterface(api_application, applicationApiServiceGuid);
+ WASABI_API_SYSCB = QueryWasabiInterface(api_syscb, syscbApiServiceGuid);
+ WASABI_API_LNG = QueryWasabiInterface(api_language, languageApiGUID);
+ AGAVE_API_JSAPI2_SECURITY = QueryWasabiInterface(JSAPI2::api_security, JSAPI2::api_securityGUID);
+ OMBROWSERMNGR = QueryWasabiInterface(obj_ombrowser, OBJ_OmBrowser);
+ OMUTILITY = QueryWasabiInterface(ifc_omutility, IFC_OmUtility);
+
+ if (NULL != WASABI_API_LNG)
+ WASABI_API_START_LANG(hInstance, MlNowPlayingLangGUID);
+
+ WasabiApi_AddRef();
+ return TRUE;
+}
+
+static void WasabiApi_Uninitialize()
+{
+ ReleaseWasabiInterface(applicationApiServiceGuid, WASABI_API_APP);
+ ReleaseWasabiInterface(syscbApiServiceGuid, WASABI_API_SYSCB);
+ ReleaseWasabiInterface(languageApiGUID, WASABI_API_LNG);
+ ReleaseWasabiInterface(JSAPI2::api_securityGUID, AGAVE_API_JSAPI2_SECURITY);
+ ReleaseWasabiInterface(OBJ_OmBrowser, OMBROWSERMNGR);
+ ReleaseWasabiInterface(IFC_OmUtility, OMUTILITY);
+
+ WASABI_API_APP = NULL;
+ WASABI_API_LNG = NULL;
+ AGAVE_API_JSAPI2_SECURITY = NULL;
+}
+
+ULONG WasabiApi_AddRef()
+{
+ return InterlockedIncrement((LONG*)&wasabiRef);
+}
+
+ULONG WasabiApi_Release()
+{
+ if (0 == wasabiRef)
+ return wasabiRef;
+
+ LONG r = InterlockedDecrement((LONG*)&wasabiRef);
+ if (0 == r)
+ {
+ WasabiApi_Uninitialize();
+ }
+ return r;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/wasabi.h b/Src/Plugins/Library/ml_nowplaying/wasabi.h
new file mode 100644
index 00000000..72d17983
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/wasabi.h
@@ -0,0 +1,42 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_WASABI_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_WASABI_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_securityApi;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_securityApi
+
+#include <obj_ombrowser.h>
+extern obj_ombrowser *browserManager;
+#define OMBROWSERMNGR browserManager
+
+#include <api/syscb/api_syscb.h>
+#define WASABI_API_SYSCB sysCallbackApi
+
+#include <ifc_omutility.h>
+extern ifc_omutility *omUtility;
+#define OMUTILITY omUtility
+
+BOOL WasabiApi_Initialize(HINSTANCE hInstance);
+ULONG WasabiApi_AddRef(void);
+ULONG WasabiApi_Release(void);
+
+void *Wasabi_QueryInterface(REFGUID interfaceGuid);
+void Wasabi_ReleaseInterface(REFGUID interfaceGuid, void *pInstance);
+
+#define QueryWasabiInterface(__interfaceType, __interfaceGuid) ((##__interfaceType*)Wasabi_QueryInterface(__interfaceGuid))
+#define ReleaseWasabiInterface(__interfaceGuid, __interfaceInstance) (Wasabi_ReleaseInterface((__interfaceGuid), (__interfaceInstance)))
+
+void Wasabi_SafeRelease(Dispatchable *pDisp);
+
+#endif // NULLSOFT_NOWPLAYING_PLUGIN_WASABI_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/wasabiCallback.cpp b/Src/Plugins/Library/ml_nowplaying/wasabiCallback.cpp
new file mode 100644
index 00000000..54e9d3a0
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/wasabiCallback.cpp
@@ -0,0 +1,90 @@
+#include "main.h"
+#include "./wasabiCallback.h"
+#include "./navigation.h"
+#include "./service.h"
+#include "../replicant/nu/Autowide.h"
+
+WasabiCallback::WasabiCallback()
+ : ref(1)
+{
+}
+
+WasabiCallback::~WasabiCallback()
+{
+}
+
+HRESULT WasabiCallback::CreateInstance(WasabiCallback **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ *instance = new WasabiCallback();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+size_t WasabiCallback::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t WasabiCallback::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int WasabiCallback::QueryInterface(GUID interface_guid, void **object)
+{
+ return 0;
+}
+
+FOURCC WasabiCallback::GetEventType()
+{
+ return SysCallback::BROWSER;
+}
+
+int WasabiCallback::Notify(int msg, intptr_t param1, intptr_t param2)
+{
+ switch (msg)
+ {
+ case BrowserCallback::ONOPENURL:
+ return OpenURL(reinterpret_cast<const wchar_t*>(param1), reinterpret_cast<bool *>(param2));
+ }
+ return 0;
+}
+
+int WasabiCallback::OpenURL(const wchar_t *url, bool *override)
+{
+ WCHAR szTemplate[1024] = L"http://client.winamp.com/nowplaying";
+ INT cchTemplate = ARRAYSIZE(szTemplate) - 1;
+ lstrcpynW(szTemplate, AutoWide(g_config->ReadString("nowplayingurl", "http://client.winamp.com/nowplaying")), ARRAYSIZE(szTemplate));
+
+ if (NULL != url &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, url, cchTemplate, szTemplate, cchTemplate))
+ {
+ if (SUCCEEDED(Navigation_ShowService(SERVICE_ID, url,
+ NAVFLAG_FORCEACTIVE | NAVFLAG_ENSUREMLVISIBLE | NAVFLAG_ENSUREITEMVISIBLE)))
+ {
+ *override = true;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define CBCLASS WasabiCallback
+START_DISPATCH;
+ CB(ADDREF, AddRef);
+ CB(RELEASE, Release);
+ CB(QUERYINTERFACE, QueryInterface);
+ CB(SYSCALLBACK_GETEVENTTYPE, GetEventType);
+ CB(SYSCALLBACK_NOTIFY, Notify);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_nowplaying/wasabiCallback.h b/Src/Plugins/Library/ml_nowplaying/wasabiCallback.h
new file mode 100644
index 00000000..3add0b9b
--- /dev/null
+++ b/Src/Plugins/Library/ml_nowplaying/wasabiCallback.h
@@ -0,0 +1,42 @@
+#ifndef NULLSOFT_NOWPLAYING_PLUGIN_WASABI_CALLBACK_HEADER
+#define NULLSOFT_NOWPLAYING_PLUGIN_WASABI_CALLBACK_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <api/syscb/callbacks/syscb.h>
+#include <api/syscb/callbacks/browsercb.h>
+
+class WasabiCallback : public SysCallback
+{
+protected:
+ WasabiCallback();
+ ~WasabiCallback();
+
+public:
+ static HRESULT CreateInstance(WasabiCallback **instance);
+
+public:
+ /*** Dispatchable ***/
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /*** SysCallback ***/
+ FOURCC GetEventType();
+ int Notify(int msg, intptr_t param1 = 0, intptr_t param2 = 0);
+
+protected:
+ // set *override = true to prevent the URL from being opened
+ // leave it alone otherwise (in case someone else wanted to override it)
+ int OpenURL(const wchar_t *url, bool *override);
+
+protected:
+ RECVS_DISPATCH;
+
+protected:
+ ULONG ref;
+};
+
+#endif //NULLSOFT_NOWPLAYING_PLUGIN_WASABI_CALLBACK_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/BufferCache.h b/Src/Plugins/Library/ml_online/BufferCache.h
new file mode 100644
index 00000000..fdc3b1e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/BufferCache.h
@@ -0,0 +1,13 @@
+#ifndef NULLSOFT_BUFFERCACHEH
+#define NULLSOFT_BUFFERCACHEH
+#include <time.h>
+#include "../nu/GrowBuf.h"
+
+class Buffer_GrowBuf : public GrowBuf
+{
+public:
+ Buffer_GrowBuf() : expire_time(0) {}
+ time_t expire_time;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/JSAPI2_Creator.cpp b/Src/Plugins/Library/ml_online/JSAPI2_Creator.cpp
new file mode 100644
index 00000000..d6d99442
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/JSAPI2_Creator.cpp
@@ -0,0 +1,93 @@
+#include "api__ml_online.h"
+#include "JSAPI2_Creator.h"
+#include "jsapi2_omcom.h"
+#include "resource.h"
+
+
+IDispatch *JSAPI2_Creator::CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info)
+{
+ if (!wcscmp(name, L"OnlineServices"))
+ return new JSAPI2::OnlineServicesAPI(key, info);
+ else
+ return 0;
+}
+
+int JSAPI2_Creator::PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data)
+{
+ if (group && !wcscmp(group, L"onlineservices"))
+ {
+ return JSAPI2::svc_apicreator::AUTHORIZATION_DENY;
+ }
+ else
+ return JSAPI2::svc_apicreator::AUTHORIZATION_UNDEFINED;
+}
+
+#define CBCLASS JSAPI2_Creator
+START_DISPATCH;
+CB(JSAPI2_SVC_APICREATOR_CREATEAPI, CreateAPI);
+CB(JSAPI2_SVC_APICREATOR_PROMPTFORAUTHORIZATION, PromptForAuthorization);
+END_DISPATCH;
+#undef CBCLASS
+
+static JSAPI2_Creator jsapi2_svc;
+static const char serviceName[] = "Online Services Javascript Objects";
+
+// {4B0FA456-2B3B-4584-A96C-54765EA46448}
+static const GUID jsapi2_factory_guid =
+{ 0x4b0fa456, 0x2b3b, 0x4584, { 0xa9, 0x6c, 0x54, 0x76, 0x5e, 0xa4, 0x64, 0x48 } };
+
+FOURCC JSAPI2Factory::GetServiceType()
+{
+ return jsapi2_svc.getServiceType();
+}
+
+const char *JSAPI2Factory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID JSAPI2Factory::GetGUID()
+{
+ return jsapi2_factory_guid;
+}
+
+void *JSAPI2Factory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// WASABI_API_SVC->service_lock(this, (void *)ifc);
+ return &jsapi2_svc;
+}
+
+int JSAPI2Factory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int JSAPI2Factory::ReleaseInterface(void *ifc)
+{
+ //WASABI_API_SVC->service_unlock(ifc);
+ return 1;
+}
+
+const char *JSAPI2Factory::GetTestString()
+{
+ return 0;
+}
+
+int JSAPI2Factory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS JSAPI2Factory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/JSAPI2_Creator.h b/Src/Plugins/Library/ml_online/JSAPI2_Creator.h
new file mode 100644
index 00000000..4190b391
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/JSAPI2_Creator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class JSAPI2Factory : public waServiceFactory
+{
+ public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface( int global_lock );
+ int SupportNonLockingInterface();
+ int ReleaseInterface( void *ifc );
+ const char *GetTestString();
+ int ServiceNotify( int msg, int param1, int param2 );
+
+ protected:
+ RECVS_DISPATCH;
+};
+
+
+class JSAPI2_Creator : public JSAPI2::svc_apicreator
+{
+ IDispatch *CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info);
+ int PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data);
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_online/JnetCOM.cpp b/Src/Plugins/Library/ml_online/JnetCOM.cpp
new file mode 100644
index 00000000..e93506f3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/JnetCOM.cpp
@@ -0,0 +1,656 @@
+#include "JnetCOM.h"
+#include "main.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include <algorithm>
+#include <map>
+#include "config.h"
+#include <time.h>
+#include "../nu/MediaLibraryInterface.h"
+#include "api__ml_online.h"
+#include <api/service/waServiceFactory.h>
+#include <strsafe.h>
+
+extern C_Config *g_config;
+
+BufferMap buffer_map;
+
+enum
+{
+ DISP_JNET_INIT = 9323,
+ DISP_JNET_DOWNLOAD,
+ DISP_JNET_STATUS,
+ DISP_JNET_SIZE,
+ DISP_JNET_BUFFER,
+ DISP_JNET_BUFFER_RAW,
+ DISP_JNET_POST,
+};
+
+HANDLE DuplicateCurrentThread()
+{
+ HANDLE fakeHandle = GetCurrentThread();
+ HANDLE copiedHandle = 0;
+ HANDLE processHandle = GetCurrentProcess();
+ DuplicateHandle(processHandle, fakeHandle, processHandle, &copiedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ return copiedHandle;
+}
+
+#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
+HRESULT JnetCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ CHECK_ID("Init", DISP_JNET_INIT)
+ CHECK_ID("Download", DISP_JNET_DOWNLOAD)
+ CHECK_ID("Status", DISP_JNET_STATUS)
+ CHECK_ID("Buffer", DISP_JNET_BUFFER)
+ CHECK_ID("BufferRaw", DISP_JNET_BUFFER_RAW)
+ CHECK_ID("Size", DISP_JNET_SIZE)
+ CHECK_ID("Post", DISP_JNET_POST)
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JnetCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JnetCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT JnetCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case DISP_JNET_INIT:
+ {
+ if (!active && NULL == callback)
+ {
+ finalSize = 0;
+ isBlocking = true;
+ if ( pdispparams->cArgs == 2 )
+ {
+ isBlocking = false;
+ htmldiv = SysAllocString(pdispparams->rgvarg[0].bstrVal); // HTML DIV
+ callback = pdispparams->rgvarg[1].pdispVal; // callback object;
+ if (NULL != callback)
+ callback->AddRef();
+ }
+ }
+ else
+ {
+ if (pvarResult)
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_I4;
+ V_I4(pvarResult) = -1;
+ }
+ }
+ return S_OK;
+ }
+ break;
+ case DISP_JNET_DOWNLOAD:
+ if (pdispparams->cArgs == 1 || pdispparams->cArgs == 2)
+ {
+ int result = -1;
+ if ( !active )
+ {
+ time_t ret = 0;
+ active = 1;
+
+ DownloadURL(pdispparams);
+ result = 1;
+ }
+ else result = -1;
+
+ if (pvarResult)
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_I4;
+ V_I4(pvarResult) = result;
+ }
+
+ return S_OK;
+ }
+ break;
+ case DISP_JNET_POST:
+ if (pdispparams->cArgs == 2)
+ {
+ int result = -1;
+ if ( !active )
+ {
+ time_t ret = 0;
+ active = 1;
+
+ PostURL(pdispparams);
+ result = 1;
+ }
+ else result = -1;
+
+ if (pvarResult)
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_I4;
+ V_I4(pvarResult) = result;
+ }
+
+ return S_OK;
+ }
+ break;
+ case DISP_JNET_STATUS:
+ {
+ if (pvarResult && errorstr && active )
+ {
+ AutoWide result(errorstr);
+ BSTR tag = SysAllocString(result);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = tag;
+ }
+ return S_OK;
+ }
+ break;
+ case DISP_JNET_SIZE:
+ {
+ if (pvarResult && active )
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_I4;
+ V_I4(pvarResult) = (INT)finalSize;
+ }
+ return S_OK;
+ }
+ break;
+ case DISP_JNET_BUFFER_RAW:
+ {
+ if (pvarResult && jnetbuf->get() && active)
+ {
+ char dummy[1]={0};
+ size_t len = jnetbuf->getlen();
+ char *source = (char *)jnetbuf->get();
+ if (!len || !source)
+ {
+ source=dummy;
+ len=1;
+ }
+ else
+ {
+ while (*(source+len-1) == 0 && len)
+ len--;
+ }
+ SAFEARRAY *bufferArray=SafeArrayCreateVector(VT_UI1, 0, (ULONG)len);
+ void *data;
+ SafeArrayAccessData(bufferArray, &data);
+ memcpy(data, source, len);
+ SafeArrayUnaccessData(bufferArray);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_ARRAY|VT_UI1;
+ V_ARRAY(pvarResult) = bufferArray;
+ }
+ return S_OK;
+ }
+ case DISP_JNET_BUFFER:
+ {
+ if (pvarResult && jnetbuf->get() && active )
+ {
+ AutoWide result((char *)jnetbuf->get());
+ BSTR tag = SysAllocString(result);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = tag;
+ }
+ return S_OK;
+ }
+ break;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JnetCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JnetCOM::AddRef(void)
+{
+ return ++refCount;
+}
+
+ULONG JnetCOM::Release(void)
+{
+ if (--refCount)
+ return refCount;
+
+ threadexit = 1; // exit the thread if active
+ if (!isBlocking)
+ { // Only if a callback was associated would the thread be running
+ while (getter) // If there no getter, the thread is gone
+ Sleep(10);
+ }
+
+ if (!finalSize)
+ {
+ BufferMap::iterator buffer_it = buffer_map.find(url);
+ if (buffer_it != buffer_map.end())
+ {
+ jnetbuf = buffer_it->second;
+ buffer_map.erase(buffer_it->first);
+ delete jnetbuf;
+ jnetbuf = NULL;
+ }
+ }
+ Plugin_FreeString(url);
+ url=0;
+ if (postData)
+ {
+ free(postData);
+ postData=0;
+ }
+
+ if (NULL != callback)
+ {
+ callback->Release();
+ callback = NULL;
+ }
+
+ delete this;
+ return 0;
+}
+
+static VOID CALLBACK CallbackAPC(ULONG_PTR param)
+{
+ VARIANT arguments[2];
+ DISPPARAMS params;
+ unsigned int ret;
+
+ JnetCOM *jnet = (JnetCOM *)param;
+ if (NULL == jnet || NULL == jnet->callback)
+ return;
+
+ VariantInit(&arguments[0]);
+ VariantInit(&arguments[1]);
+
+ V_VT(&arguments[0]) = VT_DISPATCH;
+ V_DISPATCH(&arguments[0]) = jnet;
+
+ V_VT(&arguments[1]) = VT_BSTR;
+ V_BSTR(&arguments[1]) = jnet->htmldiv;
+
+ params.cArgs = ARRAYSIZE(arguments);
+ params.cNamedArgs = 0;
+ params.rgdispidNamedArgs = NULL;
+ params.rgvarg = arguments;
+
+ jnet->callback->Invoke(0, GUID_NULL, 0, DISPATCH_METHOD, &params, NULL, NULL, &ret);
+
+
+ V_DISPATCH(&arguments[0]) = NULL;
+ V_BSTR(&arguments[1]) = NULL;
+
+ VariantClear(&arguments[0]);
+ VariantClear(&arguments[1]);
+
+ jnet->Release();
+
+
+}
+
+void JnetCOM::callBack()
+{
+ AddRef();
+ if (GetCurrentThreadId() == callingThreadId)
+ CallbackAPC((ULONG_PTR)this);
+ else
+ {
+ if (NULL == callingThreadHandle ||
+ 0 == QueueUserAPC(CallbackAPC, callingThreadHandle, (ULONG_PTR)this))
+ {
+ Release();
+ }
+ }
+}
+
+#define USER_AGENT_SIZE (10 /*User-Agent*/ + 2 /*: */ + 6 /*Winamp*/ + 1 /*/*/ + 1 /*5*/ + 3/*.21*/ + 1 /*Null*/)
+void SetUserAgent(api_httpreceiver *http)
+{
+ char user_agent[USER_AGENT_SIZE] = {0};
+ int bigVer = ((winampVersion & 0x0000FF00) >> 12);
+ int smallVer = ((winampVersion & 0x000000FF));
+ StringCchPrintfA(user_agent, USER_AGENT_SIZE, "User-Agent: Winamp/%01x.%02x", bigVer, smallVer);
+ http->addheader(user_agent);
+}
+
+int JnetCOM::JthreadProc()
+{
+ int ret;
+ char temp[WORKSIZE] = {0};
+
+ char *proxy = mediaLibrary.GetProxy();
+
+ waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(httpreceiverGUID);
+ if (sf) getter = (api_httpreceiver *)sf->getInterface();
+
+ // we're keying off of 'getter' to know if the thread is running or not, so we can release now
+ Release();
+
+ if (!getter)
+ return -1;
+
+ getter->AllowCompression();
+ getter->open(API_DNS_AUTODNS, WORKSIZE, proxy);
+
+ SetUserAgent(getter);
+
+ getter->connect(AutoChar(url));
+
+ BOOL bDone = FALSE;
+ while (!bDone && !threadexit)
+ {
+ Sleep(10);
+ ret = getter->run();
+ if (ret == -1 || ret == 1)
+ bDone = TRUE;
+
+ int bytesReceived = getter->get_bytes(temp, WORKSIZE);
+ if (bytesReceived)
+ jnetbuf->add(temp, bytesReceived);
+ }
+
+ if (!threadexit)
+ {
+
+ if (ret == -1) StringCchCopyA(errorstr, 2048, getter->geterrorstr());
+ else
+ {
+ int bytesReceived;
+ do // flush out the socket
+ {
+ bytesReceived = getter->get_bytes(temp, WORKSIZE);
+ if (bytesReceived)
+ jnetbuf->add(temp, bytesReceived);
+ }
+ while (bytesReceived);
+
+ temp[0] = 0;
+ jnetbuf->add(temp, 1);
+ }
+
+ finalSize = jnetbuf->getlen();
+
+ callBack();
+ }
+
+ sf->releaseInterface(getter);
+ threadexit = 0;
+ if (callingThreadHandle)
+ CloseHandle(callingThreadHandle);
+ getter = NULL;
+ return ret;
+}
+
+int JnetCOM::PostProcedure()
+{
+ int ret;
+ char temp[WORKSIZE] = {0};
+
+ char *proxy = mediaLibrary.GetProxy();
+
+ waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(httpreceiverGUID);
+ if (sf) getter = (api_httpreceiver *)sf->getInterface();
+
+ // we're keying off of 'getter' to know if the thread is running or not, so we can release now
+ Release();
+ if (!getter)
+ return -1;
+
+ getter->AllowCompression();
+ getter->open(API_DNS_AUTODNS, WORKSIZE, proxy);
+
+ SetUserAgent(getter);
+ getter->addheader("Content-Type: application/x-www-form-urlencoded");
+
+ size_t contentLength = strlen(postData);
+ char contentLengthHeader[256] = {0};
+ StringCchPrintfA(contentLengthHeader, 256, "Content-Length: %u", contentLength);
+ getter->addheader(contentLengthHeader);
+
+ char *dataIndex = postData;
+ bool done=false;
+
+ getter->connect(AutoChar(url), 0, "POST");
+
+ // time to post data!
+ api_connection *connection = getter->GetConnection();
+
+ while (contentLength && !threadexit)
+ {
+ Sleep(1);
+ getter->run();
+
+ size_t lengthToSend = min(contentLength, connection->GetSendBytesAvailable());
+ if (lengthToSend)
+ {
+ connection->send(dataIndex, (int)lengthToSend);
+ dataIndex+=lengthToSend;
+ contentLength-=lengthToSend;
+ }
+ }
+
+ while (!threadexit && !done)
+ {
+ Sleep(10);
+ ret = getter->run();
+ if (ret == -1)
+ break;
+
+ // ---- check our reply code ----
+ int replycode = getter->getreplycode();
+ switch (replycode)
+ {
+ case 0:
+ break;
+ case 100:
+ break;
+ case 200:
+ {
+ int bytesReceived = getter->get_bytes(temp, WORKSIZE);
+ if (bytesReceived)
+ jnetbuf->add(temp, bytesReceived);
+ do
+ {
+ Sleep(10);
+ ret = getter->run();
+ bytesReceived = getter->get_bytes(temp, WORKSIZE);
+ if (bytesReceived)
+ jnetbuf->add(temp, bytesReceived);
+ }
+ while (ret == HTTPRECEIVER_RUN_OK);
+ done=true; // just in case
+ }
+ break;
+ default:
+ done=true;
+ break;
+ }
+ if (ret != HTTPRECEIVER_RUN_OK)
+ break;
+ }
+ if (!threadexit)
+ {
+ if (ret == -1)
+ StringCchCopyA(errorstr, 2048, getter->geterrorstr());
+ else
+ {
+ int bytesReceived;
+ do // flush out the socket
+ {
+ bytesReceived = getter->get_bytes(temp, WORKSIZE);
+ if (bytesReceived)
+ jnetbuf->add(temp, bytesReceived);
+ }
+ while (bytesReceived && !threadexit);
+
+ temp[0] = 0;
+ jnetbuf->add(temp, 1);
+ }
+
+ if ( !threadexit )
+ {
+ finalSize = jnetbuf->getlen();
+ callBack();
+ }
+
+ }
+ sf->releaseInterface(getter);
+
+ threadexit = 0;
+ if (callingThreadHandle)
+ CloseHandle(callingThreadHandle);
+ getter = NULL;
+ return ret;
+}
+
+void JnetCOM::DownloadURL(DISPPARAMS FAR *pdispparams)
+{
+ Plugin_FreeString(url);
+ url = Plugin_CopyString(pdispparams->rgvarg[pdispparams->cArgs - 1].bstrVal);
+
+ callingThreadId = GetCurrentThreadId();
+ callingThreadHandle = DuplicateCurrentThread();
+
+ BufferMap::iterator buffer_it = buffer_map.find(url);
+ if (buffer_it != buffer_map.end())
+ {
+ time_t check = time(NULL);
+ jnetbuf = buffer_it->second;
+ if ( check >= jnetbuf->expire_time)
+ {
+ buffer_map.erase(buffer_it->first);
+ delete jnetbuf;
+ jnetbuf = NULL;
+ }
+ else
+ {
+ finalSize = jnetbuf->getlen();
+ callBack();
+ }
+
+ }
+ if (!jnetbuf)
+ {
+ time_t now = 0;
+
+ jnetbuf = buffer_map[url] = new Buffer_GrowBuf;
+
+ if ( pdispparams->cArgs == 2 ) // passed in a time from Javascript, or 0 to not cache
+ {
+ if ( pdispparams->rgvarg[0].iVal )
+ {
+ now = time(NULL);
+ jnetbuf->expire_time = now + pdispparams->rgvarg[0].iVal;
+ }
+ }
+ else // Use winamp config cache time
+ {
+ int when = 0, x = 0;
+
+ x = g_config->ReadInt("radio_upd_freq", 0);
+ switch ( x )
+ {
+ case 0:
+ {
+ when = 3600; // One Hour
+ }
+ break;
+ case 1:
+ {
+ when = 3600 * 24; // One Day aka 24 hours
+ }
+ break;
+ case 2:
+ {
+ when = 3600 * 24 * 7; // One week (weak)
+ }
+ break;
+ default:
+ break;
+ }
+ if (when)
+ now = time(NULL);
+
+ jnetbuf->expire_time = now + when;
+
+ }
+ if (isBlocking)
+ {
+ AddRef(); // need to call this because JthreadProc does a Release
+ JthreadProc(); // Call it directly, block until done.
+ }
+ else
+ {
+ // Launch the thread
+ DWORD threadId;
+ AddRef(); // make sure jnetcom object doesn't die while the thread is launching.
+ jnetThread = CreateThread(NULL, NULL, JThreadProc, (void *)this, NULL, &threadId);
+ }
+
+ }
+}
+
+void JnetCOM::PostURL(DISPPARAMS FAR *pdispparams)
+{
+ postData = AutoCharDup(pdispparams->rgvarg[0].bstrVal);
+ Plugin_FreeString(url);
+ url = Plugin_CopyString(pdispparams->rgvarg[1].bstrVal);
+
+ callingThreadId = GetCurrentThreadId();
+ callingThreadHandle = DuplicateCurrentThread();
+
+ if (!jnetbuf)
+ {
+ time_t now = 0;
+
+ jnetbuf = buffer_map[url] = new Buffer_GrowBuf;
+
+ if (isBlocking)
+ {
+ AddRef(); // need to do this beacuse PostProcedure calls Release
+ PostProcedure(); // Call it directly, block until done.
+ }
+ else
+ {
+ // Launch the thread
+ DWORD threadId;
+ AddRef(); // make sure the jnetcom doesn't get deleted before the thread launches
+ jnetThread = CreateThread(NULL, NULL, jnetPostProcedure, (void *)this, NULL, &threadId);
+ }
+
+ }
+}
+
diff --git a/Src/Plugins/Library/ml_online/JnetCOM.h b/Src/Plugins/Library/ml_online/JnetCOM.h
new file mode 100644
index 00000000..c3a5e023
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/JnetCOM.h
@@ -0,0 +1,91 @@
+#ifndef NULLSOFT_JNETCOMH
+#define NULLSOFT_JNETCOMH
+
+#include <process.h>
+#include <ocidl.h>
+
+#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
+#include <objbase.h>
+#include "BufferCache.h"
+#include <map>
+#include <string>
+
+
+typedef std::map<std::wstring, Buffer_GrowBuf *> BufferMap;
+extern BufferMap buffer_map;
+#define WORKSIZE 32768
+
+class JnetCOM : public IDispatch
+{
+public:
+ JnetCOM() :
+ refCount(1),
+ threadexit(0),
+ active(0),
+ jnetThread(0),
+ jnetbuf(NULL),
+ isBlocking(0),
+ finalSize(0),
+ getter(NULL),
+ callback(NULL),
+ postData(0),
+ url(0),
+ callingThreadId(0),
+ callingThreadHandle(0)
+ {
+ memset(errorstr, 0, sizeof(errorstr));
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ int refCount;
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ void DownloadURL(DISPPARAMS FAR *pdispparams );
+ void PostURL(DISPPARAMS FAR *pdispparams);
+ void callBack();
+
+ int threadexit;
+ int isBlocking;
+
+ api_httpreceiver *getter;
+ Buffer_GrowBuf *jnetbuf;
+ IDispatch *callback;
+ BSTR htmldiv;
+ DWORD callingThreadId;
+ HANDLE callingThreadHandle;
+
+ static DWORD WINAPI JThreadProc(void *param)
+ {
+ CoInitialize(NULL);
+ JnetCOM *th = static_cast<JnetCOM*>(param);
+ int ret = th->JthreadProc();
+ _endthread();
+ return ret;
+ }
+
+ static DWORD WINAPI jnetPostProcedure(void *param)
+ {
+ CoInitialize(NULL);
+ JnetCOM *th = static_cast<JnetCOM*>(param);
+ int ret = th->PostProcedure();
+ _endthread();
+ return ret;
+ }
+
+ size_t finalSize;
+ wchar_t *url;
+ char *postData;
+ int active;
+ char errorstr[2048];
+ HANDLE jnetThread;
+private:
+ int JthreadProc();
+ int PostProcedure();
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Main.cpp b/Src/Plugins/Library/ml_online/Main.cpp
new file mode 100644
index 00000000..4f4f3c86
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Main.cpp
@@ -0,0 +1,599 @@
+#include "main.h"
+#include "./api__ml_online.h"
+#include "./config.h"
+#include "./navigation.h"
+#include "./resource.h"
+#include "./preferences.h"
+#include "./serviceHelper.h"
+
+#include "../../General/gen_ml/ml.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/ns_wc.h"
+#include "../nu/AutoWide.h"
+#include <vector>
+#include "../nu/nonewthrow.c"
+#include "../nu/ConfigCOM.h"
+
+#include "BufferCache.h"
+#include "OMCOM.h"
+#include "JNetCom.h" // for buffer_map
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+static int Plugin_Init();
+static void Plugin_Quit();
+static INT_PTR Plugin_MessageProc(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+static Navigation *navigation = NULL;
+static std::vector<PLUGINUNLOADCALLBACK> *unloadCallbacks = NULL;
+
+C_Config *g_config=NULL;
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_online.dll)",
+ Plugin_Init,
+ Plugin_Quit,
+ Plugin_MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+
+HINSTANCE Plugin_GetInstance(void)
+{
+ return plugin.hDllInstance;
+}
+
+HWND Plugin_GetWinamp(void)
+{
+ return plugin.hwndWinampParent;
+}
+
+HWND Plugin_GetLibrary(void)
+{
+ return plugin.hwndLibraryParent;
+}
+
+HRESULT Plugin_GetNavigation(Navigation **instance)
+{
+ if(NULL == instance) return E_POINTER;
+
+ if (NULL == navigation)
+ {
+ *instance = NULL;
+ return E_UNEXPECTED;
+ }
+
+ *instance = navigation;
+ navigation->AddRef();
+
+ return S_OK;
+}
+
+typedef struct __PLUGINTIMERREC
+{
+ UINT_PTR id;
+ PLUGINTIMERPROC callback;
+ ULONG_PTR data;
+} PLUGINTIMERREC;
+
+typedef std::vector<PLUGINTIMERREC> PluginTimerList;
+
+static void CALLBACK Plugin_TimerProcDispath(HWND hwnd, UINT uMsg, UINT_PTR eventId, DWORD elapsedMs)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL != hLibrary && FALSE != IsWindow(hLibrary))
+ {
+ PluginTimerList *list = (PluginTimerList*)GetProp(hLibrary, L"OnlineMediaTimerData");
+ if (NULL != list)
+ {
+ size_t index = list->size();
+ while(index--)
+ {
+ PLUGINTIMERREC *rec = &list->at(index);
+ if (rec->id == eventId)
+ {
+ rec->callback(eventId, elapsedMs, rec->data);
+ return;
+ }
+ }
+ }
+ }
+
+ KillTimer(hwnd, eventId);
+}
+
+UINT_PTR Plugin_SetTimer(UINT elapseMs, PLUGINTIMERPROC callback, ULONG_PTR data)
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary || FALSE == IsWindow(hLibrary))
+ return 0;
+
+ if (GetCurrentThreadId() != GetWindowThreadProcessId(hLibrary, NULL))
+ return 0;
+
+ if (NULL == callback)
+ return 0;
+
+ PluginTimerList *list = (PluginTimerList*)GetProp(hLibrary, L"OnlineMediaTimerData");
+ if (NULL == list)
+ {
+ list = new PluginTimerList();
+ if (NULL == list) return 0;
+ if (0 == SetProp(hLibrary, L"OnlineMediaTimerData", list))
+ {
+ delete(list);
+ return 0;
+ }
+ }
+
+ PLUGINTIMERREC rec;
+ rec.data = data;
+ rec.callback = callback;
+ rec.id = SetTimer(NULL, NULL, elapseMs, Plugin_TimerProcDispath);
+ if (0 == rec.id)
+ {
+ if (0 == list->size())
+ {
+ RemoveProp(hLibrary, L"OnlineMediaTimerData");
+ delete(list);
+ }
+ return 0;
+ }
+
+ list->push_back(rec);
+ return rec.id;
+}
+
+void Plugin_KillTimer(UINT_PTR eventId)
+{
+ KillTimer(NULL, eventId);
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary || FALSE == IsWindow(hLibrary))
+ return;
+
+ PluginTimerList *list = (PluginTimerList*)GetProp(hLibrary, L"OnlineMediaTimerData");
+ if (NULL == list) return;
+
+ size_t index = list->size();
+ while(index--)
+ {
+ if (list->at(index).id == eventId)
+ {
+ list->erase(list->begin() + index);
+ break;
+ }
+ }
+
+ if (0 == list->size())
+ {
+ RemoveProp(hLibrary, L"OnlineMediaTimerData");
+ delete(list);
+ }
+}
+
+static void Plugin_UninitializeTimer()
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary || FALSE == IsWindow(hLibrary))
+ return;
+
+ PluginTimerList *list = (PluginTimerList*)GetProp(hLibrary, L"OnlineMediaTimerData");
+ RemoveProp(hLibrary, L"OnlineMediaTimerData");
+ if (NULL == list) return;
+
+ size_t index = list->size();
+ while(index--)
+ {
+ KillTimer(NULL, list->at(index).id);
+ }
+
+ delete(list);
+}
+
+
+wchar_t g_w_cachedir[2048] = {0};
+int winampVersion=0;
+
+OMCOM omCOM;
+
+URLMap urlMap;
+MetadataMap metadataMap;
+Nullsoft::Utility::LockGuard urlMapGuard;
+
+void LoadCacheItem( wchar_t *path )
+{
+ FILECACHETYPE cachefile = {0};
+ unsigned long size = 0;
+ HANDLE hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile == INVALID_HANDLE_VALUE) return;
+ ReadFile(hFile, &cachefile, sizeof(FILECACHETYPE),&size, NULL);
+ if ( size == sizeof(FILECACHETYPE ))
+ {
+ size = 0;
+ time_t now = time(NULL);
+ //read the header, validate
+ if ( cachefile.version == FILECACHEVERSION )
+ {
+ if ( now < cachefile.expires )
+ {
+ char *url = (char *)calloc((size_t)cachefile.urllen, sizeof(char));
+ if (url)
+ {
+ size = 0;
+ ReadFile(hFile, url, (DWORD)cachefile.urllen, &size, NULL);
+ if ( cachefile.urllen == size ) // we read it ok!
+ {
+ char tempbuf[16384] = {0};
+ Buffer_GrowBuf *newbuffer = new Buffer_GrowBuf;
+ INT64 readin=0;
+ newbuffer->expire_time = (time_t)cachefile.expires;
+ while ( readin != cachefile.datalen )
+ {
+ DWORD toread=(DWORD)cachefile.datalen - (DWORD)readin;
+ if ( toread > 16384 ) toread=16384;
+ size = 0;
+ int success = ReadFile(hFile, &tempbuf, toread , &size, NULL);
+ if ( success )
+ {
+ if ( size )
+ {
+ newbuffer->add(tempbuf,(int)size);
+ readin += size;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ if ( readin != cachefile.datalen )
+ {
+ delete newbuffer;
+ }
+ else
+ {
+ buffer_map[(wchar_t *)AutoWide(url)]=newbuffer;
+ }
+ }
+ else
+ {
+ free(url);
+ }
+ }
+ }
+ }
+ }
+ CloseHandle(hFile);
+ DeleteFile(path);
+}
+
+void LoadCache()
+{
+ WIN32_FIND_DATA FindFileData = {0};
+ HANDLE hFind;
+ wchar_t searchs[2048] = {0};
+
+ buffer_map.clear();
+
+ StringCchPrintf(searchs, 2048, L"%s\\*.w5x",g_w_cachedir);
+ hFind = FindFirstFile(searchs, &FindFileData);
+ if ( hFind != INVALID_HANDLE_VALUE )
+ {
+ do
+ {
+ wchar_t activefile[2048] = {0};
+ StringCchPrintf(activefile, 2048, L"%s\\%s",g_w_cachedir,FindFileData.cFileName);
+ LoadCacheItem(activefile);
+ } while ( FindNextFile(hFind, &FindFileData) );
+ FindClose(hFind);
+ }
+}
+
+void SaveCache()
+{
+ BufferMap::iterator buffer_it;
+ DWORD start=0xABBACAFE;
+ for(buffer_it = buffer_map.begin();buffer_it != buffer_map.end(); buffer_it++)
+ {
+ time_t now = time(NULL);
+ if ( buffer_it->second->expire_time > now )
+ {
+ wchar_t filename[2048] = {0};
+ FILECACHETYPE cachefile;
+ HANDLE hFile;
+ INT64 size=0;
+ memset((void *)&cachefile,0,sizeof(FILECACHETYPE));
+ cachefile.version = FILECACHEVERSION;
+ cachefile.expires = buffer_it->second->expire_time;
+ AutoChar charUrl(buffer_it->first.c_str());
+ cachefile.urllen = strlen(charUrl)+1;
+ cachefile.datalen = buffer_it->second->getlen()+1;
+
+ StringCchPrintf(filename, 2048, L"%s\\%08X.w5x",g_w_cachedir,start++);
+ hFile = CreateFile(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ WriteFile(hFile, &cachefile, sizeof(FILECACHETYPE),(LPDWORD)&size,NULL);
+ if ( size == sizeof(FILECACHETYPE) )
+ {
+ char blank[2]="\0";
+ size = 0; WriteFile(hFile, (char *)charUrl ,(DWORD)cachefile.urllen, (LPDWORD)&size, NULL);
+ size = 0; WriteFile(hFile, buffer_it->second->get() , (DWORD)buffer_it->second->getlen(), (LPDWORD)&size, NULL);
+ size = 0; WriteFile(hFile, blank , 1, (LPDWORD)&size, NULL);
+ }
+ else
+ {
+ CloseHandle(hFile);
+ hFile=NULL;
+ DeleteFile(filename);
+ }
+ }
+ if (hFile)
+ {
+ CloseHandle(hFile);
+ hFile=NULL;
+ }
+ }
+ }
+}
+
+void initConfigCache()
+{
+ wchar_t iniFileName[2048] = {0};
+ mediaLibrary.BuildPath(L"Plugins\\ml", iniFileName, 2048);
+ CreateDirectory(iniFileName, NULL);
+ mediaLibrary.BuildPath(L"Plugins\\ml\\cache", g_w_cachedir, 2048);
+ CreateDirectory(g_w_cachedir, NULL);
+ mediaLibrary.BuildPath(L"Plugins\\ml\\ml_online.ini", iniFileName, 2048);
+ AutoChar charFn(iniFileName);
+ g_config = new C_Config(AutoChar(iniFileName));
+
+ int x = g_config->ReadInt("maxbandwidth", MAXBANDWIDTH );
+ g_config->WriteInt("maxbandwidth",x);
+
+ x = g_config->ReadInt("minbandwidth",1);
+ g_config->WriteInt("minbandwidth",x);
+
+ LoadCache();
+}
+
+static void Plugin_ExecuteOpenOnce()
+{
+ CHAR szBuffer[128] = {0};
+ INT cchLen = Config_ReadStr("Navigation", "openOnce", NULL, szBuffer, ARRAYSIZE(szBuffer));
+ if (0 != cchLen)
+ {
+ UINT serviceId;
+ if (FALSE != StrToIntExA(szBuffer, STIF_SUPPORT_HEX, (INT*)&serviceId))
+ {
+
+ cchLen = Config_ReadStr("Navigation", "openOnceMode", NULL, szBuffer, ARRAYSIZE(szBuffer));
+ UINT showMode;
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, "popup", -1, szBuffer, cchLen))
+ showMode = SHOWMODE_POPUP;
+ else if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, "ensureVisible", -1, szBuffer, cchLen))
+ showMode = SHOWMODE_ENSUREVISIBLE;
+ else
+ showMode = SHOWMODE_NORMAL;
+
+ ServiceHelper_ShowService(serviceId, showMode);
+ }
+
+ Config_WriteStr("Navigation", "openOnce", NULL);
+ Config_WriteStr("Navigation", "openOnceMode", NULL);
+ }
+}
+
+static int Plugin_Init()
+{
+ if (FAILED(WasabiApi_Initialize(plugin.hDllInstance, plugin.service)))
+ return 1;
+
+ if (FAILED(WasabiApi_LoadDefaults()) ||
+ NULL == OMBROWSERMNGR ||
+ NULL == OMSERVICEMNGR ||
+ NULL == OMUTILITY)
+ {
+ WasabiApi_Release();
+ return 2;
+ }
+
+ ServiceHelper_Initialize();
+
+ if (NULL != WASABI_API_LNG)
+ {
+ 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;
+ }
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ winampVersion = mediaLibrary.GetWinampVersion();
+
+ omCOM.Publish();
+
+ Preferences_Register();
+
+ if (NULL == navigation)
+ {
+ if (FAILED(Navigation::CreateInstance(&navigation)))
+ {
+ navigation = NULL;
+
+ if (NULL != unloadCallbacks)
+ {
+ size_t index = unloadCallbacks->size();
+ while(index--)
+ unloadCallbacks->at(index)();
+ delete(unloadCallbacks);
+ }
+
+ Preferences_Unregister();
+ WasabiApi_Release();
+ return 3;
+ }
+ }
+
+ initConfigCache();
+
+ Plugin_ExecuteOpenOnce();
+ return ML_INIT_SUCCESS;
+}
+
+static void Plugin_Quit()
+{
+ SaveCache();
+ buffer_map.clear();
+
+ Plugin_UninitializeTimer();
+
+ if (NULL != navigation)
+ {
+ navigation->Finish();
+ navigation->Release();
+ navigation = NULL;
+ }
+
+ if (NULL != unloadCallbacks)
+ {
+ size_t index = unloadCallbacks->size();
+ while(index--)
+ unloadCallbacks->at(index)();
+ delete(unloadCallbacks);
+ unloadCallbacks = NULL;
+ }
+
+ Preferences_Unregister();
+
+ WasabiApi_Release();
+}
+
+static INT_PTR TitleHook(waHookTitleStructW *hookTitle)
+{
+ if (NULL == hookTitle ||
+ NULL == hookTitle->filename)
+ {
+ return 0;
+ }
+
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+ // this is kinda slow but AOL Videos is so underused anyway that this map won't fill up much
+ URLMap::iterator itr;
+ for (itr=urlMap.begin();itr!=urlMap.end();itr++)
+ {
+ if (!_wcsnicmp(hookTitle->filename, itr->url.c_str(), itr->url_wcslen))
+ {
+ if (NULL != hookTitle->title)
+ StringCchCopy(hookTitle->title, 2048, itr->title.c_str());
+
+ hookTitle->length = itr->length;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static INT_PTR MetadataHook(extendedFileInfoStructW *hookMetadata)
+{
+ if (NULL == hookMetadata ||
+ NULL == hookMetadata->filename ||
+ NULL == hookMetadata->metadata)
+ {
+ return 0;
+ }
+
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+ // this is kinda slow but AOL Videos is so underused anyway that this map won't fill up much
+ MetadataMap::iterator itr;
+
+ for (itr=metadataMap.begin();itr!=metadataMap.end();itr++)
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, hookMetadata->filename, -1, itr->url.c_str(), - 1) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, hookMetadata->metadata, -1, itr->tag.c_str(), - 1))
+ {
+ StringCchCopy(hookMetadata->ret, hookMetadata->retlen, itr->metadata.c_str());
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static INT_PTR Plugin_MessageProc(int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ INT_PTR result = 0;
+ if (NULL != navigation &&
+ FALSE != navigation->ProcessMessage(msg, param1, param2, param3, &result))
+ {
+ return result;
+ }
+
+ switch (msg)
+ {
+ case ML_IPC_HOOKTITLEW: return TitleHook((waHookTitleStructW *)param1);
+ case ML_IPC_HOOKEXTINFOW: return MetadataHook((extendedFileInfoStructW *)param1);
+ case ML_MSG_CONFIG: Preferences_Show(); return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+void Plugin_RegisterUnloadCallback(PLUGINUNLOADCALLBACK callback)
+{
+ if (NULL == unloadCallbacks)
+ {
+ unloadCallbacks = new std::vector<PLUGINUNLOADCALLBACK>();
+ if (NULL == unloadCallbacks)
+ return;
+ }
+ unloadCallbacks->push_back(callback);
+}
+
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+}
+
+#if 0
+extern "C" __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+
+ // prompt to remove our settings with default as no (just incase)
+ /*if(MessageBoxA(hwndDlg,"Do you also want to remove the saved settings for this plugin?",
+ plugin.description,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ WritePrivateProfileString("ml_rg", 0, 0, iniFile);
+ }*/
+
+ // also attempt to remove the ReplayGainAnalysis.dll so everything is kept cleaner
+ /*char path[MAX_PATH] = {0};
+ GetModuleFileName(hDllInst, path, MAX_PATH);
+ PathRemoveFileSpec(path);
+ PathAppend(path, "ReplayGainAnalysis.dll");
+ // if we get a handle then try to lower the handle count so we can delete
+ HINSTANCE rgLib = GetModuleHandle(path);
+ if(rgLib)
+ FreeLibrary(rgLib);
+ DeleteFile(path);*/
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Main.h b/Src/Plugins/Library/ml_online/Main.h
new file mode 100644
index 00000000..5b6c887d
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Main.h
@@ -0,0 +1,79 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../General/gen_ml/ml.h"
+#include "./common.h"
+
+#include <string>
+#include <vector>
+#include "../nu/AutoLock.h"
+
+#define PLUGIN_VERSION_MAJOR 2
+#define PLUGIN_VERSION_MINOR 03
+
+HINSTANCE Plugin_GetInstance(void);
+HWND Plugin_GetWinamp(void);
+HWND Plugin_GetLibrary(void);
+
+class Navigation;
+HRESULT Plugin_GetNavigation(Navigation **instance);
+
+typedef void (CALLBACK *PLUGINUNLOADCALLBACK)(void);
+void Plugin_RegisterUnloadCallback(PLUGINUNLOADCALLBACK callback);
+
+
+extern int winampVersion;
+
+#define MUTEX_T CRITICAL_SECTION
+#define MUTEX_ENTER(n) EnterCriticalSection(&(n))
+#define MUTEX_LEAVE(n) LeaveCriticalSection(&(n))
+#define MUTEX_INIT(n) InitializeCriticalSection(&(n))
+#define MUTEX_DEL(n) DeleteCriticalSection(&(n))
+
+#define FILECACHEVERSION 0x00000001
+typedef struct FileCacheType {
+ INT64 version;
+ INT64 expires;
+ INT64 urllen;
+ INT64 datalen;
+ INT64 resv1; // Future use, older versions MUST ignore
+ INT64 resv2; // Future use, older versions MUST ignore
+ INT64 resv3; // Future use, older versions MUST ignore
+ INT64 resv4; // Future use, older versions MUST ignore
+} FileCacheType;
+#define FILECACHETYPE FileCacheType
+
+struct url_info
+{
+ std::wstring url;
+ size_t url_wcslen;
+ std::wstring title;
+ int length;
+} ;
+
+struct metadata_info
+{
+ std::wstring url;
+ std::wstring tag;
+ std::wstring metadata;
+} ;
+
+typedef std::vector<url_info> URLMap; // just to save some typing & template code ugliness
+typedef std::vector<metadata_info> MetadataMap;
+
+
+extern URLMap urlMap;
+extern MetadataMap metadataMap;
+
+extern Nullsoft::Utility::LockGuard urlMapGuard;
+
+typedef void (CALLBACK *PLUGINTIMERPROC)(UINT_PTR /*eventId*/, DWORD /*elapsedMs*/, ULONG_PTR /*data*/);
+UINT_PTR Plugin_SetTimer(UINT elapseMs, PLUGINTIMERPROC callback, ULONG_PTR data);
+void Plugin_KillTimer(UINT_PTR eventId);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/OMCOM.cpp b/Src/Plugins/Library/ml_online/OMCOM.cpp
new file mode 100644
index 00000000..8a00b456
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/OMCOM.cpp
@@ -0,0 +1,821 @@
+#include "main.h"
+#include "./omcom.h"
+#include "./resource.h"
+#include "./api__ml_online.h"
+
+#include "JnetCOM.h"
+
+#include "./navigation.h"
+#include "./preferences.h"
+#include "./serviceHelper.h"
+#include "./serviceHost.h"
+#include "./config.h"
+
+#include "../nu/ConfigCOM.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/AutoUrl.h"
+
+
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+#include <ifc_omfilestorage.h>
+
+
+#include "../Winamp/JSAPI.h" // IDispatch helper macros
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+extern MediaLibraryInterface mediaLibrary;
+extern C_Config *g_config;
+
+#define CONFIG_SERIALNUMBER "serialNumber"
+#define SERIALNUMBER_INVALID ((INT)-1)
+#define SERIALNUMBER_DEFAULT ((INT)0)
+
+enum
+{
+ DISPATCH_ISSUBSCRIBED = 12312,
+ DISPATCH_ADDSUBSCRIBED,
+ DISPATCH_CLEARSUBSCRIBED,
+ DISPATCH_SERIALNUMBER,
+ DISPATCH_JNETCREATE,
+ DISPATCH_GETWID,
+ DISPATCH_GETSID,
+ DISPATCH_PLAY,
+ DISPATCH_CONFIG,
+ DISPATCH_ENQUEUE,
+ DISPATCH_PREF,
+ DISPATCH_SETSIZE,
+ DISPATCH_GETX,
+ DISPATCH_GETY,
+ DISPATCH_NAVDISPLAY,
+ DISPATCH_FOCUSURL,
+ DISPATCH_SETCURRENTGUID,
+ DISPATCH_ADDTITLEHOOK,
+ DISPATCH_REMOVETITLEHOOK,
+ DISPATCH_ADDMETADATAHOOK,
+ DISPATCH_REMOVEMETADATAHOOK,
+ DISPATCH_SUBSCRIBE,
+};
+
+
+
+OMCOM::OMCOM()
+ : config(NULL), serialNumber(SERIALNUMBER_INVALID), publishCookie(0)
+{
+
+}
+
+OMCOM::~OMCOM()
+{
+ if (NULL != config)
+ config->Release();
+
+ if (0 != publishCookie)
+ {
+ SENDWAIPC(Plugin_GetWinamp(), IPC_REMOVE_DISPATCH_OBJECT, publishCookie);
+ publishCookie = NULL;
+ }
+}
+
+HRESULT OMCOM::Publish()
+{
+ if (0 != publishCookie)
+ return E_FAIL;
+
+ DispatchInfo dispatchInfo;
+ ZeroMemory(&dispatchInfo, sizeof(dispatchInfo));
+
+ dispatchInfo.name = L"OnMedia";
+ dispatchInfo.dispatch = this;
+
+ if (0 != SENDWAIPC(Plugin_GetWinamp(), IPC_ADD_DISPATCH_OBJECT, (WPARAM)&dispatchInfo))
+ return E_FAIL;
+
+ publishCookie = dispatchInfo.id;
+ return S_OK;
+}
+
+HRESULT OMCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ if (wcscmp(rgszNames[i], L"IsOmSubscribed") == 0)
+ rgdispid[i] = DISPATCH_ISSUBSCRIBED;
+ else if (wcscmp(rgszNames[i], L"AddOmSubscribed") == 0)
+ rgdispid[i] = DISPATCH_ADDSUBSCRIBED;
+ else if (wcscmp(rgszNames[i], L"ClearOmSubscribed") == 0)
+ rgdispid[i] = DISPATCH_CLEARSUBSCRIBED;
+ else if (wcscmp(rgszNames[i], L"OmSerialNumber") == 0)
+ rgdispid[i] = DISPATCH_SERIALNUMBER;
+ else if (wcscmp(rgszNames[i], L"JnetCreate") == 0)
+ rgdispid[i] = DISPATCH_JNETCREATE;
+ else if (wcscmp(rgszNames[i], L"GetUniqueID") == 0)
+ rgdispid[i] = DISPATCH_GETWID;
+ else if (wcscmp(rgszNames[i], L"GetSessionID") == 0)
+ rgdispid[i] = DISPATCH_GETSID;
+ else if (wcscmp(rgszNames[i], L"PlayUrl") == 0)
+ rgdispid[i] = DISPATCH_PLAY;
+ else if (wcscmp(rgszNames[i], L"Config") == 0)
+ rgdispid[i] = DISPATCH_CONFIG;
+ else if (wcscmp(rgszNames[i], L"EnqueueUrl") == 0)
+ rgdispid[i] = DISPATCH_ENQUEUE;
+ else if (wcscmp(rgszNames[i], L"ShowPreferences") == 0)
+ rgdispid[i] = DISPATCH_PREF;
+ else if (wcscmp(rgszNames[i], L"SetSize") == 0)
+ rgdispid[i] = DISPATCH_SETSIZE;
+ else if (wcscmp(rgszNames[i], L"GetX") == 0)
+ rgdispid[i] = DISPATCH_GETX;
+ else if (wcscmp(rgszNames[i], L"GetY") == 0)
+ rgdispid[i] = DISPATCH_GETY;
+ else if (wcscmp(rgszNames[i], L"DisplayNav") == 0)
+ rgdispid[i] = DISPATCH_NAVDISPLAY;
+ else if (wcscmp(rgszNames[i], L"FocusUrl") == 0)
+ rgdispid[i] = DISPATCH_FOCUSURL;
+ else if (wcscmp(rgszNames[i], L"SetCurrentGUID") == 0)
+ rgdispid[i] = DISPATCH_SETCURRENTGUID;
+ else if (wcscmp(rgszNames[i], L"AddTitleHook") == 0)
+ rgdispid[i] = DISPATCH_ADDTITLEHOOK;
+ else if (wcscmp(rgszNames[i], L"RemoveTitleHook") == 0)
+ rgdispid[i] = DISPATCH_REMOVETITLEHOOK;
+ else if (wcscmp(rgszNames[i], L"AddMetadataHook") == 0)
+ rgdispid[i] = DISPATCH_ADDMETADATAHOOK;
+ else if (wcscmp(rgszNames[i], L"RemoveMetadataHook") == 0)
+ rgdispid[i] = DISPATCH_REMOVEMETADATAHOOK;
+ else if (wcscmp(rgszNames[i], L"Subscribe") == 0)
+ rgdispid[i] = DISPATCH_SUBSCRIBE;
+ else
+ {
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+ }
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+HRESULT OMCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT OMCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+#define VIDEO_GENFF_SIZEREQUEST (WM_USER+2048)
+static void RemoveTitleHook(const wchar_t *url)
+{
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+DISPATCH_REMOVETITLEHOOK_again:
+ URLMap::iterator itr;
+ for (itr=urlMap.begin();itr!=urlMap.end();itr++)
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, url, -1, itr->url.c_str(), - 1))
+ {
+ urlMap.erase(itr);
+ goto DISPATCH_REMOVETITLEHOOK_again;
+ }
+ }
+}
+
+static void RemoveMetadataHook(const wchar_t *url)
+{
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+DISPATCH_REMOVEMETADATAHOOK_again:
+ MetadataMap::iterator itr;
+ for (itr=metadataMap.begin();itr!=metadataMap.end();itr++)
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, url, -1, itr->url.c_str(), - 1))
+ {
+ metadataMap.erase(itr);
+ goto DISPATCH_REMOVEMETADATAHOOK_again;
+ }
+ }
+}
+
+static void RemoveMetadataHook(const wchar_t *url, const wchar_t *tag)
+{
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+DISPATCH_REMOVEMETADATAHOOK_again2:
+ MetadataMap::iterator itr;
+ for (itr=metadataMap.begin();itr!=metadataMap.end();itr++)
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, url, -1, itr->url.c_str(), - 1) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, tag, -1, itr->tag.c_str(), - 1))
+ {
+ metadataMap.erase(itr);
+ goto DISPATCH_REMOVEMETADATAHOOK_again2;
+ }
+ }
+}
+
+HRESULT OMCOM::FindService(VARIANTARG *pArg, ifc_omservice **service)
+{
+ if (NULL == service)
+ return E_POINTER;
+
+ *service = NULL;
+ if (NULL == pArg)
+ return E_INVALIDARG;
+
+ HRESULT hr = E_INVALIDARG;
+ UINT serviceId;
+
+ if (VT_BSTR == pArg->vt)
+ {
+ if (FALSE != StrToIntEx(pArg->bstrVal, STIF_SUPPORT_HEX, (INT*)&serviceId))
+ hr = S_OK;
+ }
+ else if (VT_I4 == pArg->vt)
+ {
+ serviceId = pArg->lVal;
+ hr = S_OK;
+ }
+
+ if (SUCCEEDED(hr))
+ hr = ServiceHelper_Find(serviceId, service);
+
+ return hr;
+}
+static HRESULT OmCom_AddServiceToNavigation(ifc_omservice *service)
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != navigation->CreateItem(service))
+ {
+ hr = S_OK;
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ navigation->Release();
+ }
+ return hr;
+}
+
+static HRESULT OmCom_SubscribeToService(UINT serviceId, LPCWSTR pszName, LPCWSTR pszUrl, INT iconId)
+{
+ ifc_omservice *service = NULL;
+ HRESULT hr = ServiceHelper_Find(serviceId, &service);
+ if (FAILED(hr)) return hr;
+
+ if (S_FALSE == hr)
+ {
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ if (NULL != OMSERVICEMNGR)
+ {
+ hr = OMSERVICEMNGR->CreateService(serviceId, serviceHost, &service);
+ }
+ else
+ hr = E_FAIL;
+ }
+ else if (S_OK == ServiceHelper_IsSubscribed(service))
+ {
+ hr = S_FALSE;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (S_OK == hr)
+ {
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != pszName && L'\0' != *pszName)
+ editor->SetName(pszName, FALSE);
+
+ if (NULL != pszUrl && L'\0' != *pszUrl)
+ editor->SetUrl(pszUrl, FALSE);
+
+ WCHAR szIcon[256] = {0};
+ if (SUCCEEDED(StringCchPrintf(szIcon, ARRAYSIZE(szIcon), L"%u", iconId)))
+ {
+ editor->SetIcon(szIcon, FALSE);
+ }
+
+ hr = editor->SetFlags(SVCF_SUBSCRIBED, SVCF_SUBSCRIBED);
+ if (SUCCEEDED(hr))
+ ServiceHelper_Save(service);
+ editor->Release();
+ }
+
+ if (SUCCEEDED(hr))
+ OmCom_AddServiceToNavigation(service);
+ }
+ service->Release();
+ }
+
+ return hr;
+}
+
+HRESULT OMCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ if (dispid == DISPATCH_ADDTITLEHOOK)
+ {
+ if (pdispparams->cArgs == 3)
+ {
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+ RemoveTitleHook(pdispparams->rgvarg[2].bstrVal); // ensure no duplicates
+ url_info info;
+ info.url = pdispparams->rgvarg[2].bstrVal;
+ info.url_wcslen = wcslen(info.url.c_str());
+ info.title=pdispparams->rgvarg[1].bstrVal;
+ info.length= pdispparams->rgvarg[0].lVal;
+ urlMap.push_back(info);
+ return S_OK;
+ }
+ }
+ if (dispid == DISPATCH_REMOVETITLEHOOK)
+ {
+ if (pdispparams->cArgs == 1)
+ {
+ RemoveTitleHook(pdispparams->rgvarg[0].bstrVal);
+ return S_OK;
+ }
+ else
+ return DISP_E_BADPARAMCOUNT;
+ }
+
+ if (dispid == DISPATCH_REMOVEMETADATAHOOK)
+ {
+ if (pdispparams->cArgs == 1)
+ {
+ RemoveMetadataHook(pdispparams->rgvarg[0].bstrVal);
+ return S_OK;
+ }
+ else if (pdispparams->cArgs == 2)
+ {
+ RemoveMetadataHook(pdispparams->rgvarg[1].bstrVal, pdispparams->rgvarg[0].bstrVal);
+ return S_OK;
+ }
+ else
+ return DISP_E_BADPARAMCOUNT;
+ }
+
+ if (dispid == DISPATCH_ADDMETADATAHOOK)
+ {
+ if (pdispparams->cArgs == 3)
+ {
+ Nullsoft::Utility::AutoLock lock(urlMapGuard);
+ RemoveMetadataHook(pdispparams->rgvarg[2].bstrVal, pdispparams->rgvarg[1].bstrVal); // ensure no duplicates
+ metadata_info info;
+ info.url = pdispparams->rgvarg[2].bstrVal;
+ info.tag = pdispparams->rgvarg[1].bstrVal;
+ info.metadata= pdispparams->rgvarg[0].bstrVal;
+ metadataMap.push_back(info);
+ return S_OK;
+ }
+ }
+
+ if (dispid == DISPATCH_SERIALNUMBER)
+ {
+ int serial = GetSerialNumber(FALSE);
+ if (pdispparams->cArgs == 1)
+ {
+ SetSerialNumber(pdispparams->rgvarg[0].lVal);
+ serial = GetSerialNumber(FALSE);
+ }
+
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, serial);
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_ADDSUBSCRIBED)
+ {
+ return AddOmSubscribed(wFlags, pdispparams, pvarResult, puArgErr);
+ }
+
+ if (dispid == DISPATCH_SUBSCRIBE)
+ {
+ return Subscribe(wFlags, pdispparams, pvarResult, puArgErr);
+ }
+
+ if (dispid == DISPATCH_CLEARSUBSCRIBED && pdispparams->cArgs == 1)
+ {
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+
+ BOOL result = FALSE;
+ ifc_omservice *service;
+ if (S_OK == FindService(&JSAPI_PARAM(pdispparams, 1), &service))
+ {
+ if (SUCCEEDED(ServiceHelper_Subscribe(service, FALSE, SHF_SAVE /* | SHF_NOTIFY*/)))
+ result = TRUE;
+
+ service->Release();
+ }
+
+ JSAPI_SET_RESULT(pvarResult, lVal, result);
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_ISSUBSCRIBED)
+ {
+ return IsOmSubscribed(wFlags, pdispparams, pvarResult, puArgErr);
+ }
+
+ if (dispid == DISPATCH_JNETCREATE)
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_DISPATCH;
+ V_DISPATCH(pvarResult) = new JnetCOM();
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_GETWID)
+ {
+ WCHAR szBuffer[512] = {0};
+ if (NULL == OMBROWSERMNGR ||
+ FAILED(OMBROWSERMNGR->GetClientId(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ szBuffer[0] = L'\0';
+ }
+
+ BSTR tag = SysAllocString(szBuffer);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = tag;
+
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_GETSID)
+ {
+ WCHAR szBuffer[512] = {0};
+ if (NULL == OMBROWSERMNGR ||
+ FAILED(OMBROWSERMNGR->GetSessionId(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ szBuffer[0] = L'\0';
+ }
+
+ BSTR tag = SysAllocString(szBuffer);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = tag;
+
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_PLAY && pdispparams->cArgs == 1)
+ {
+ if (pdispparams->rgvarg[0].bstrVal)
+ mediaLibrary.PlayStream(pdispparams->rgvarg[0].bstrVal);
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_PREF)
+ {
+ Preferences_Show();
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_CONFIG)
+ {
+ VariantInit(pvarResult);
+ if(NULL == config && FAILED(ConfigCOM::CreateInstanceA("ml_online_config", g_config->GetPath(), &config)))
+ {
+ V_VT(pvarResult) = VT_NULL;
+ }
+ else
+ {
+ V_VT(pvarResult) = VT_DISPATCH;
+ V_DISPATCH(pvarResult) = config;
+ config->AddRef();
+ }
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_ENQUEUE && pdispparams->cArgs == 1)
+ {
+ if (pdispparams->rgvarg[0].bstrVal)
+ mediaLibrary.EnqueueStream(pdispparams->rgvarg[0].bstrVal);
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_SETSIZE && pdispparams->cArgs == 2)
+ {
+ HWND hView = NULL;
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ hView = navigation->GetActiveView(NULL);
+ navigation->Release();
+ }
+
+ if (NULL != hView && mediaLibrary.library &&
+ GetParent(mediaLibrary.library) &&
+ g_config->ReadInt("AutoSize",1))
+ {
+ HWND hWnd;
+ bool GenFF = false;
+ if (GetParent(GetParent(mediaLibrary.library)))
+ {
+ hWnd = GetParent(GetParent(mediaLibrary.library));
+ GenFF = true;
+ }
+ else
+ hWnd = GetParent(mediaLibrary.library);
+
+ int width = pdispparams->rgvarg[1].lVal;
+ int height = pdispparams->rgvarg[0].lVal;
+
+ RECT rc;
+ GetWindowRect(hView, &rc); // Our Html page
+ int WWidth = rc.right - rc.left;
+ int WHeight = rc.bottom - rc.top;
+
+ GetWindowRect(hWnd, &rc); // Gen ML Size
+ int PWidth = rc.right - rc.left;
+ int PHeight = rc.bottom - rc.top;
+
+ // Subtract the original window size from the parent(base) size
+ PWidth -= WWidth;
+ PHeight -= WHeight;
+
+ // Add the target size to the parent(base) size
+ PWidth += width;
+ PHeight += height;
+
+ if (GenFF)
+ {
+ SendMessage(hWnd, VIDEO_GENFF_SIZEREQUEST, PWidth, PHeight);
+ }
+ else
+ {
+ SetWindowPos(hWnd, 0, 0, 0, PWidth, PHeight, SWP_NOMOVE|SWP_ASYNCWINDOWPOS);
+ // weird? sometimes height isnt set if called once...
+ SetWindowPos(hWnd, 0, 0, 0, PWidth, PHeight, SWP_NOMOVE|SWP_ASYNCWINDOWPOS);
+ }
+ }
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_GETX)
+ {
+ RECT rc;
+ HWND hView = NULL;
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ hView = navigation->GetActiveView(NULL);
+ navigation->Release();
+ }
+
+ if (NULL != hView)
+ {
+ GetWindowRect(hView, &rc); // Our Html page
+ int WWidth = rc.right - rc.left;
+
+ if (pvarResult)
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_I4;
+ V_I4(pvarResult) = WWidth;
+ }
+ }
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_GETY)
+ {
+ RECT rc;
+ HWND hView = NULL;
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ hView = navigation->GetActiveView(NULL);
+ navigation->Release();
+ }
+
+ if (NULL != hView)
+ {
+ GetWindowRect(hView, &rc); // Our Html page
+ int WHeight = rc.bottom - rc.top;
+ if (pvarResult)
+ {
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_I4;
+ V_I4(pvarResult) = WHeight;
+ }
+ }
+ return S_OK;
+ }
+
+ if (dispid == DISPATCH_NAVDISPLAY && pdispparams->cArgs == 1)
+ {
+ //int visible = pdispparams->rgvarg[0].lVal;
+ return E_NOTIMPL;
+ }
+
+ if (dispid == DISPATCH_FOCUSURL && pdispparams->cArgs == 2)
+ {
+ ifc_omservice *service;
+ if (S_OK == FindService(&pdispparams->rgvarg[1], &service))
+ {
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ navigation->ShowService(service->GetId(), pdispparams->rgvarg[0].bstrVal);
+ navigation->Release();
+ }
+
+ service->Release();
+ }
+ return S_OK;
+ }
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP OMCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG OMCOM::AddRef(void)
+{
+ return 0;
+}
+
+ULONG OMCOM::Release(void)
+{
+ return 0;
+}
+
+HRESULT OMCOM::IsOmSubscribed(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ // 0 No knowledge , 1 Knowledge and Disabled, 2 Knowledge and Enabled
+
+ ifc_omservice *service;
+ if (S_OK == FindService(&JSAPI_PARAM(pdispparams, 1), &service))
+ {
+ JSAPI_SET_RESULT(pvarResult, lVal, (S_OK == ServiceHelper_IsSubscribed(service)) ? 2 : 1);
+ service->Release();
+ }
+ else
+ {
+ JSAPI_SET_RESULT(pvarResult, lVal, 0);
+ }
+
+ return S_OK;
+}
+
+HRESULT OMCOM::AddOmSubscribed(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 3, 4);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+
+ LPCWSTR pszUrl, pszName;
+ UINT baseArg, serviceId, iconId;
+
+ baseArg = 0;
+
+ if (pdispparams->cArgs >= 4)
+ {
+ JSAPI_GETUNSIGNED_AS_NUMBER(iconId, pdispparams, (baseArg + 1), puArgErr);
+ baseArg++;
+ }
+ else
+ iconId = 0;
+
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, (baseArg + 1), puArgErr);
+ JSAPI_GETSTRING(pszName, pdispparams, (baseArg + 2), puArgErr);
+ JSAPI_GETSTRING(pszUrl, pdispparams, (baseArg + 3), puArgErr);
+
+
+
+
+ INT result = OmCom_SubscribeToService(serviceId, pszName, pszUrl, iconId);
+ JSAPI_SET_RESULT(pvarResult, lVal, result);
+
+ return S_OK;
+}
+
+typedef int (*HTTPRETRIEVEFILEW)(HWND hwnd, char *url, wchar_t *file, wchar_t *dlgtitle);
+HRESULT OMCOM::Subscribe(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ // window.external.OnMedia.Subscribe(String name, String url, String id, String icon, String version);
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 5);
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+
+ WCHAR szBuffer[4096] = {0};
+ LPCWSTR pszName, pszUrl, pszIcon;
+ UINT serviceId, version;
+
+ JSAPI_GETSTRING(pszName, pdispparams, 1, puArgErr);
+ JSAPI_GETSTRING(pszUrl, pdispparams, 2, puArgErr);
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 3, puArgErr);
+ JSAPI_GETNUMBER_AS_STRING(pszIcon, szBuffer, pdispparams, 4, puArgErr);
+ JSAPI_GETUNSIGNED_AS_NUMBER(version, pdispparams, 5, puArgErr);
+
+ HRESULT hr;
+ ifc_omservice *service;
+ hr = ServiceHelper_Find(serviceId, &service);
+ if (S_OK != hr)
+ {
+ hr = ServiceHelper_Create(serviceId, pszName, pszIcon, pszUrl, SVCF_SUBSCRIBED | SVCF_PREAUTHORIZED, 2, TRUE, &service);
+ if (SUCCEEDED(hr))
+ {
+ OmCom_AddServiceToNavigation(service);
+ service->Release();
+ }
+ }
+ else
+ {
+ hr = ServiceHelper_Subscribe(service, TRUE, SHF_SAVE /*| SHF_NOTIFY*/); // do not call SHF_NOTIFY - or it will adjust stats
+ if (S_OK == hr)
+ OmCom_AddServiceToNavigation(service);
+
+ service->Release();
+ }
+
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+ return S_OK;
+}
+
+HRESULT OMCOM::SetSerialNumber(INT sn)
+{
+ if (SERIALNUMBER_INVALID == sn)
+ return E_INVALIDARG;
+
+ if (serialNumber == sn)
+ return S_FALSE;
+
+ serialNumber = sn;
+
+ CHAR szBuffer[64] = {0};
+ HRESULT hr = StringCchPrintfA(szBuffer, ARRAYSIZE(szBuffer), "%d", serialNumber);
+ if (SUCCEEDED(hr))
+ {
+ hr = Config_WriteStr(NULL, CONFIG_SERIALNUMBER, szBuffer);
+ }
+
+ return hr;
+}
+
+INT OMCOM::GetSerialNumber(BOOL fForceRead)
+{
+ if (FALSE != fForceRead || SERIALNUMBER_INVALID == serialNumber)
+ {
+ serialNumber = Config_ReadInt(NULL, CONFIG_SERIALNUMBER, SERIALNUMBER_DEFAULT);
+ }
+ return serialNumber;
+}
+
+/*
+HRESULT OMCOM::Login(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ // window.external.OnMedia.Login(String url);
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_FALSE);
+ const wchar_t *url = JSAPI_PARAM(pdispparams, 1).bstrVal;
+ HWND active_view = OmView_GetActive();
+ if (active_view)
+ {
+ OmService *service = OmView_GetService(active_view);
+ if (service)
+ {
+ OMNAVIGATION->SelectService(service, url);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ }
+ }
+ return S_OK;
+}
+*/ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/OMCOM.h b/Src/Plugins/Library/ml_online/OMCOM.h
new file mode 100644
index 00000000..07fbd5f3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/OMCOM.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_OMCOMH
+#define NULLSOFT_OMCOMH
+
+#include <ocidl.h>
+
+class ConfigCOM;
+class ifc_omservice;
+
+class OMCOM : public IDispatch
+{
+public:
+ OMCOM();
+ ~OMCOM();
+
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+private:
+ STDMETHOD (IsOmSubscribed)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ STDMETHOD (AddOmSubscribed)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ STDMETHOD (Subscribe)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+
+ HRESULT FindService(VARIANTARG *pArg, ifc_omservice **service);
+
+public:
+ INT GetSerialNumber(BOOL fForceRead);
+ HRESULT SetSerialNumber(INT sn);
+ HRESULT Publish();
+
+protected:
+ ConfigCOM *config;
+ INT serialNumber;
+ UINT publishCookie;
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Preferences.cpp b/Src/Plugins/Library/ml_online/Preferences.cpp
new file mode 100644
index 00000000..5bac3daf
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Preferences.cpp
@@ -0,0 +1,110 @@
+#include "main.h"
+#include "./preferences.h"
+
+#include "../winamp/wa_ipc.h"
+#include "./resource.h"
+#include "./api__ml_online.h"
+#include "./config.h"
+
+#include <windows.h>
+#include <shlobj.h>
+
+static prefsDlgRecW preferences;
+extern C_Config *g_config;
+
+static INT_PTR CALLBACK Preferences_DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+BOOL Preferences_Register()
+{
+ WCHAR szBuffer[256] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_ONLINE_SERVICES, szBuffer, ARRAYSIZE(szBuffer));
+
+ preferences.hInst = WASABI_API_LNG_HINST;
+ preferences.dlgID = IDD_OMPREF;
+ preferences.proc = (void *)Preferences_DialogProc;
+ preferences.name = Plugin_CopyString(szBuffer);
+ preferences.where = 6; // Media Library
+
+ return (BOOL)SENDWAIPC(Plugin_GetWinamp(), IPC_ADD_PREFS_DLGW, &preferences);
+
+}
+
+void Preferences_Unregister()
+{
+ SENDWAIPC(Plugin_GetWinamp(), IPC_REMOVE_PREFS_DLG, &preferences);
+
+}
+
+BOOL Preferences_Show()
+{
+ return (BOOL)SENDWAIPC(Plugin_GetWinamp(), IPC_OPENPREFSTOPAGE, &preferences);
+}
+
+static INT_PTR CALLBACK Preferences_DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ CheckDlgButton(hwndDlg,IDC_AUTOSIZE,g_config->ReadInt("AutoSize",1));
+
+ char tmp[64] = {0};
+ wsprintfA(tmp,"%i",g_config->ReadInt("maxbandwidth", MAXBANDWIDTH ));
+ SetDlgItemTextA(hwndDlg,IDC_RADIO_MAXBW,tmp);
+ wsprintfA(tmp,"%i",g_config->ReadInt("minbandwidth",1));
+ SetDlgItemTextA(hwndDlg,IDC_RADIO_MINBW,tmp);
+ int radiofreq=g_config->ReadInt("radio_upd_freq",0);
+ CheckDlgButton(hwndDlg,radiofreq==0?IDC_RADIO_HOURLY:radiofreq==1?IDC_RADIO_DAILY:radiofreq==2?IDC_RADIO_WEEKLY:IDC_RADIO_NEVER,BST_CHECKED);
+ SetDlgItemTextA(hwndDlg, IDC_NOWPLAYINGURL, g_config->ReadString("nowplayingurl",""));
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_AUTOSIZE:
+ g_config->WriteInt("AutoSize",IsDlgButtonChecked(hwndDlg,IDC_AUTOSIZE));
+ break;
+
+ case IDC_RADIO_NEVER:
+ case IDC_RADIO_DAILY:
+ case IDC_RADIO_WEEKLY:
+ case IDC_RADIO_HOURLY:
+ {
+ int radiofreq=0;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_NEVER)) radiofreq=3;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_DAILY)) radiofreq=1;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_WEEKLY)) radiofreq=2;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_HOURLY)) radiofreq=0;
+ g_config->WriteInt("radio_upd_freq",radiofreq);
+ }
+ break;
+
+ case IDC_NOWPLAYINGURL:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ char nowplayingurl[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_NOWPLAYINGURL, nowplayingurl, ARRAYSIZE(nowplayingurl));
+ g_config->WriteString("nowplayingurl",nowplayingurl);
+ }
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ char tmp[64]={0,};
+ GetDlgItemTextA(hwndDlg,IDC_RADIO_MAXBW,tmp,sizeof(tmp)-1);
+ int x = atoi(tmp);
+ if ( x < 2 ) x = 2;
+ g_config->WriteInt("maxbandwidth",x);
+ GetDlgItemTextA(hwndDlg,IDC_RADIO_MINBW,tmp,sizeof(tmp)-1);
+ int y = atoi(tmp);
+ if ( y < 1 ) y = 1;
+ if ( y > x ) y = x-1;
+ g_config->WriteInt("minbandwidth",y);
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Preferences.h b/Src/Plugins/Library/ml_online/Preferences.h
new file mode 100644
index 00000000..0fee936b
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Preferences.h
@@ -0,0 +1,16 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_PREFERENCES_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_PREFERENCES_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#define MAXBANDWIDTH 3500
+
+BOOL Preferences_Register();
+void Preferences_Unregister();
+BOOL Preferences_Show();
+
+#endif // NULLOSFT_ONLINEMEDIA_PLUGIN_PREFERENCES_HEADER
diff --git a/Src/Plugins/Library/ml_online/Setup/SetupGroupFilter.h b/Src/Plugins/Library/ml_online/Setup/SetupGroupFilter.h
new file mode 100644
index 00000000..feb4be24
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/SetupGroupFilter.h
@@ -0,0 +1,91 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUPFILTER_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUPFILTER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <vector>
+
+class ifc_omservice;
+
+class __declspec(novtable) SetupGroupFilter
+{
+public:
+ typedef enum
+ {
+ serviceIgnore = 0x00000000,
+ serviceInclude = 0x00000001,
+ serviceForceSubscribe = 0x00000002,
+ serviceForceUnsubscribe = 0x00000004,
+ } FilterResult;
+protected:
+ SetupGroupFilter(const GUID *filterId);
+ virtual ~SetupGroupFilter();
+
+public:
+ static HRESULT CreateInstance(const GUID *filterId, SetupGroupFilter **instance);
+
+public:
+ virtual ULONG AddRef();
+ virtual ULONG Release();
+
+ virtual HRESULT GetId(GUID *filterId);
+
+ virtual HRESULT Initialize() = 0;
+ virtual HRESULT ProcessService(ifc_omservice *service, UINT *filterResult) = 0;
+
+protected:
+ typedef std::vector<UINT> ServiceIdList;
+ static BOOL CALLBACK AppendServiceIdCallback(UINT serviceId, void *data);
+
+protected:
+ ULONG ref;
+ GUID id;
+};
+
+
+// {2F45FBDF-4372-4def-B20C-C6F1BAE5AE85}
+static const GUID FUID_SetupFeaturedGroupFilter =
+{ 0x2f45fbdf, 0x4372, 0x4def, { 0xb2, 0xc, 0xc6, 0xf1, 0xba, 0xe5, 0xae, 0x85 } };
+
+
+class SetupFeaturedGroupFilter : public SetupGroupFilter
+{
+protected:
+ SetupFeaturedGroupFilter();
+ ~SetupFeaturedGroupFilter();
+public:
+ static HRESULT CreateInstance(SetupFeaturedGroupFilter **instance);
+
+public:
+ HRESULT Initialize();
+ HRESULT ProcessService(ifc_omservice *service, UINT *filterResult);
+
+protected:
+ ServiceIdList filterList;
+};
+
+// {7CA8722D-8B11-43a0-8F55-533C9DE3D73E}
+static const GUID FUID_SetupKnownGroupFilter =
+{ 0x7ca8722d, 0x8b11, 0x43a0, { 0x8f, 0x55, 0x53, 0x3c, 0x9d, 0xe3, 0xd7, 0x3e } };
+
+
+class SetupKnownGroupFilter : public SetupGroupFilter
+{
+protected:
+ SetupKnownGroupFilter();
+ ~SetupKnownGroupFilter();
+public:
+ static HRESULT CreateInstance(SetupKnownGroupFilter **instance);
+
+public:
+ HRESULT Initialize();
+ HRESULT ProcessService(ifc_omservice *service, UINT *filterResult);
+
+protected:
+ ServiceIdList filterList;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUPFILTER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setup.cpp b/Src/Plugins/Library/ml_online/Setup/setup.cpp
new file mode 100644
index 00000000..e8497306
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setup.cpp
@@ -0,0 +1,71 @@
+#define GUID_DEFINE
+#include "../../winamp/setup/svc_setup.h"
+#undef GUID_DEFINE
+
+#include "./setupPage.h"
+
+#include "../api__ml_online.h"
+
+
+static HRESULT Setup_RegisterPage()
+{
+ HRESULT hr;
+ svc_setup *setupSvc;
+ SetupPage *page;
+
+
+ if (FAILED(WasabiApi_LoadDefaults()) ||
+ NULL == OMBROWSERMNGR ||
+ NULL == OMSERVICEMNGR ||
+ NULL == OMUTILITY)
+ {
+ return E_UNEXPECTED;
+ }
+
+ setupSvc = QueryWasabiInterface(svc_setup, UID_SVC_SETUP);
+ if (NULL == setupSvc)
+ return E_POINTER;
+
+ page = SetupPage::CreateInstance();
+ if (NULL == page)
+ hr = E_OUTOFMEMORY;
+ else
+ {
+ // try to insert before 'feedback' (if present)
+ // otherwise dump at the end of the pages list.
+ int index = 0xFFFFF;
+ if (FAILED(setupSvc->GetPageCount(&index)))
+ index = 0xFFFFF;
+ else if (index > 0 && index == 3)
+ index--;
+
+ hr = setupSvc->InsertPage(page, &index);
+ if (SUCCEEDED(hr))
+ setupSvc->AddJob((ifc_setupjob*)page);
+
+ page->Release();
+ }
+
+ ReleaseWasabiInterface(UID_SVC_SETUP, setupSvc);
+
+ return hr;
+}
+
+EXTERN_C _declspec(dllexport) BOOL RegisterSetup(HINSTANCE hInstance, api_service *waServices)
+{
+ // check the current date and if past November 30th 2013
+ // then we will prevent the online page from being shown
+ time_t now = time(0);
+ struct tm *tn = localtime(&now);
+ tn->tm_sec = tn->tm_min = tn->tm_hour = 0;
+
+ if (mktime(tn) >= 1387497600)
+ return FALSE;
+
+ if (FAILED(WasabiApi_Initialize(hInstance, waServices)))
+ return FALSE;
+
+ BOOL result = SUCCEEDED(Setup_RegisterPage());
+ WasabiApi_Release();
+ return result;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupDetails.cpp b/Src/Plugins/Library/ml_online/Setup/setupDetails.cpp
new file mode 100644
index 00000000..5e1b7be2
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupDetails.cpp
@@ -0,0 +1,80 @@
+#include "./common.h"
+#include "./setupDetails.h"
+#include "./setupServicePanel.h"
+
+EXTERN_C ATOM DETAILS_PROP = 0;
+HMODULE hEditModule = NULL;
+
+
+BOOL SetupDetails_Initialize()
+{
+ if (0 == DETAILS_PROP)
+ {
+ DETAILS_PROP = GlobalAddAtom(L"omSetupDetailsProp");
+ if (0 == DETAILS_PROP) return FALSE;
+ }
+
+ if (NULL == (hEditModule = LoadLibrary(L"riched20.dll")))
+ return FALSE;
+
+ return TRUE;
+}
+
+void SetupDetails_Uninitialize()
+{
+ if (NULL != hEditModule)
+ {
+ FreeLibrary(hEditModule);
+ hEditModule = NULL;
+ }
+
+ if (0 != DETAILS_PROP)
+ {
+ GlobalDeleteAtom(DETAILS_PROP);
+ DETAILS_PROP = 0;
+ }
+}
+
+void SetupDetails_SetDescription(HWND hEdit, LPCWSTR pszText)
+{
+ SetWindowText(hEdit, pszText);
+
+ DWORD originalStyle = GetWindowStyle(hEdit);
+ DWORD windowStyle = originalStyle & ~WS_VSCROLL;
+
+ INT lineCount = (INT)SendMessage(hEdit, EM_GETLINECOUNT, 0, 0L);
+ if (lineCount > 0)
+ {
+ INT charIndex = (INT)SendMessage(hEdit, EM_LINEINDEX, (WPARAM)(lineCount - 1), 0L);
+ if (-1 != charIndex)
+ {
+ LRESULT result = SendMessage(hEdit, EM_POSFROMCHAR, charIndex, 0L);
+ POINTS pts = MAKEPOINTS(result);
+ RECT clientRect;
+ if (GetClientRect(hEdit, &clientRect) && pts.y > (clientRect.bottom - 14))
+ {
+ windowStyle |= WS_VSCROLL;
+ }
+ }
+ }
+
+ if (windowStyle != originalStyle)
+ {
+ SetWindowLongPtr(hEdit, GWL_STYLE, windowStyle);
+ SetWindowPos(hEdit, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED);
+ }
+
+ ShowWindow(hEdit, SW_HIDE);
+ if (0 != ShowWindow(hEdit, (L'\0' != *pszText) ? SW_SHOWNA : SW_HIDE))
+ InvalidateRect(hEdit, NULL, TRUE);
+}
+
+HWND SetupDetails_CreateServiceView(HWND hParent, LPCWSTR pszName, ifc_omservice *service)
+{
+ return ServicePanel::CreateInstance(hParent, pszName, service, NULL);
+}
+
+BOOL SetupDetails_GetUniqueName(HWND hwnd, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ return (BOOL)SendMessage(hwnd, NSDM_GETUNIQUENAME, (WPARAM)cchBufferMax, (LPARAM)pszBuffer);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupDetails.h b/Src/Plugins/Library/ml_online/Setup/setupDetails.h
new file mode 100644
index 00000000..2afd1b48
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupDetails.h
@@ -0,0 +1,30 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPDETAILS_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPDETAILS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ifc_omservice;
+class SetupGroup;
+class SetupListboxItem;
+
+#define NSDM_FIRST (WM_APP + 7)
+#define NSDM_GETUNIQUENAME (NSDM_FIRST + 0)
+
+EXTERN_C ATOM DETAILS_PROP;
+
+BOOL SetupDetails_Initialize();
+void SetupDetails_Uninitialize();
+
+
+HWND SetupDetails_CreateServiceView(HWND hParent, LPCWSTR pszName, ifc_omservice *service);
+HWND SetupDetails_CreateGroupView(HWND hParent, LPCWSTR pszName, SetupGroup *group);
+BOOL SetupDetails_GetUniqueName(HWND hwnd, LPWSTR pszBuffer, UINT cchBufferMax);
+
+// internal
+void SetupDetails_SetDescription(HWND hEdit, LPCWSTR pszText);
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPDETAILS_HEADER
diff --git a/Src/Plugins/Library/ml_online/Setup/setupDetailsGroup.cpp b/Src/Plugins/Library/ml_online/Setup/setupDetailsGroup.cpp
new file mode 100644
index 00000000..38ca9943
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupDetailsGroup.cpp
@@ -0,0 +1,284 @@
+#include "./setupDetails.h"
+#include "../common.h"
+#include "../resource.h"
+#include "../api__ml_online.h"
+
+#include "../../winamp/commandLink.h"
+#include "./setupGroup.h"
+
+#include <commctrl.h>
+#include <strsafe.h>
+
+#ifndef IDC_HELPLINK
+#define IDC_HELPLINK 10000
+#endif
+
+struct GROUPDETAILSCREATEPARAM
+{
+ GROUPDETAILSCREATEPARAM() : group(NULL), name(NULL) {}
+
+ SetupGroup *group;
+ LPCWSTR name;
+};
+
+struct GROUPDETAILS
+{
+ GROUPDETAILS() : group(NULL), name(NULL), fontTitle(NULL) {}
+
+ SetupGroup *group;
+ LPWSTR name;
+ HFONT fontTitle;
+};
+
+#define GetDetails(__hwnd) ((GROUPDETAILS*)GetPropW((__hwnd), MAKEINTATOM(DETAILS_PROP)))
+
+static INT_PTR WINAPI GroupDetails_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+HWND SetupDetails_CreateGroupView(HWND hParent, LPCWSTR pszName, SetupGroup *group)
+{
+ GROUPDETAILSCREATEPARAM param;
+ param.group = group;
+ param.name = pszName;
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_SETUP_GROUPDETAILS, hParent, GroupDetails_DialogProc, (LPARAM)&param);
+}
+
+static void GroupDetails_SetTitle(HWND hwnd, SetupGroup *group)
+{
+ HWND hTitle = GetDlgItem(hwnd, IDC_TITLE);
+ if (NULL == hTitle) return;
+
+ WCHAR szBuffer[128] = {0};
+ if (NULL == group ||
+ FAILED(group->GetLongName(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ szBuffer[0] = L'\0';
+ }
+
+ GROUPDETAILS *details = GetDetails(hwnd);
+ if (NULL != details && NULL == details->fontTitle)
+ {
+ HFONT dialogFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+ LOGFONT lf;
+ if (0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Arial Bold");
+ lf.lfWidth = 0;
+ lf.lfWeight = FW_DONTCARE;
+ lf.lfHeight += (lf.lfHeight < 0) ? -2 : +2;
+ lf.lfQuality = 5/*ANTIALIASED_QUALITY*/;
+ details->fontTitle = CreateFontIndirect(&lf);
+ }
+
+ if (NULL != details->fontTitle)
+ {
+ SendMessage(hTitle, WM_SETFONT, (WPARAM)details->fontTitle, 0L);
+ }
+ }
+
+ SetWindowText(hTitle, szBuffer);
+ InvalidateRect(hTitle, NULL, TRUE);
+}
+
+static void GroupDetails_SetDescription(HWND hwnd, SetupGroup *group)
+{
+ HWND hDescription = GetDlgItem(hwnd, IDC_DESCRIPTION);
+ if (NULL == hDescription) return;
+
+ WCHAR szBuffer[4096] = {0};
+ if (NULL == group ||
+ FAILED(group->GetDescription(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ szBuffer[0] = L'\0';
+ }
+
+ SetupDetails_SetDescription(hDescription, szBuffer);
+}
+
+static BOOL GroupDetails_ShowHelp(HWND hwnd)
+{
+ INT result = (INT)(INT_PTR)ShellExecuteW(hwnd, L"open",
+ L"https://help.winamp.com/hc/articles/8112753225364-Online-Services-Security",
+ NULL, NULL, SW_SHOWNORMAL);
+ return (result > 32);
+}
+
+static void GroupDetails_UpdateLayout(HWND hwnd, BOOL fRedraw)
+{
+ RECT clientRect, rect;
+ if(FALSE == GetClientRect(hwnd, &clientRect))
+ return;
+
+
+ UINT commonFlags = SWP_NOACTIVATE | SWP_NOZORDER;
+ if (FALSE != fRedraw) commonFlags |= SWP_NOREDRAW;
+
+ LONG bottomLine = clientRect.bottom;
+
+ HWND hControl;
+ SIZE linkSize;
+ RECT linkMargins;
+
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_HELPLINK)) &&
+ CommandLink_GetIdealSize(hControl, &linkSize))
+ {
+ if (!CommandLink_GetMargins(hControl, &linkMargins))
+ SetRectEmpty(&linkMargins);
+
+ if (linkSize.cy > 0)
+ bottomLine -= linkSize.cy;
+
+ SetWindowPos(hControl, NULL, clientRect.left + 4, bottomLine, linkSize.cx, linkSize.cy, commonFlags);
+ }
+ else
+ {
+ ZeroMemory(&linkSize, sizeof(linkSize));
+ SetRectEmpty(&linkMargins);
+ }
+
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_HELPTEXT)))
+ {
+ LONG x = clientRect.left + 4 + linkSize.cx/* - linkMargins.right*/;
+ LONG y = 0;
+
+ HDC hdc = GetDCEx(hControl, NULL, DCX_CACHE | DCX_WINDOW | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ HFONT font = (HFONT)SendMessage(hControl, WM_GETFONT, 0, 0L);
+ HFONT originalFont = (HFONT)SelectObject(hdc, font);
+
+ TEXTMETRICW tm;
+ if (GetTextMetricsW(hdc, &tm))
+ y = tm.tmHeight;
+
+ SelectObject(hdc, originalFont);
+ ReleaseDC(hControl, hdc);
+ }
+
+ SetWindowPos(hControl, NULL, x, clientRect.bottom - linkMargins.bottom - y, clientRect.right - x, y, commonFlags);
+ }
+
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_DESCRIPTION)) &&
+ FALSE != GetWindowRect(hControl, &rect))
+ {
+ MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rect, 2);
+ SetWindowPos(hControl, NULL, rect.left, rect.right, rect.right - rect.left, bottomLine - rect.top,
+ commonFlags | SWP_NOMOVE);
+ }
+}
+
+static INT_PTR GroupDetails_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
+{
+ GROUPDETAILSCREATEPARAM *param = (GROUPDETAILSCREATEPARAM*)lParam;
+
+ GROUPDETAILS *details = (GROUPDETAILS*)calloc(1, sizeof(GROUPDETAILS));
+ if (NULL == details) return FALSE;
+
+ if (!SetProp(hwnd, MAKEINTATOM(DETAILS_PROP), details))
+ return FALSE;
+
+ if (NULL != param)
+ {
+ if (NULL != param->group)
+ {
+ details->group = param->group;
+ details->group->AddRef();
+ }
+
+ details->name = Plugin_CopyString(param->name);
+ }
+
+ HINSTANCE winampInstance = (NULL != WASABI_API_APP) ? WASABI_API_APP->main_gethInstance() : NULL;
+ if (NULL != winampInstance)
+ {
+ WCHAR szBuffer[256] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_CLICKHERE, szBuffer, ARRAYSIZE(szBuffer));
+ HWND hLink = CreateWindowExW(WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT, NWC_COMMANDLINKW, szBuffer,
+ WS_VISIBLE | WS_CHILD | WS_TABSTOP | CLS_ALWAYSUNDERLINE | CLS_DEFAULTCOLORS /* | CLS_HOTTRACK */,
+ 0, 0, 0, 0, hwnd, (HMENU)IDC_HELPLINK, winampInstance, NULL);
+
+ if (NULL != hLink)
+ {
+ SendMessageW(hLink, WM_SETFONT, (WPARAM)SendMessageW(hwnd, WM_GETFONT, 0, 0L), 0L);
+ }
+ }
+
+ GroupDetails_UpdateLayout(hwnd, FALSE);
+
+ if (NULL != param)
+ {
+ GroupDetails_SetTitle(hwnd, param->group);
+ GroupDetails_SetDescription(hwnd, param->group);
+ }
+
+ return FALSE;
+}
+
+static void GroupDetails_OnDestroy(HWND hwnd)
+{
+ GROUPDETAILS *details = GetDetails(hwnd);
+ RemoveProp(hwnd, MAKEINTATOM(DETAILS_PROP));
+
+ if (NULL != details)
+ {
+ if (NULL != details->group)
+ details->group->Release();
+ if (NULL != details->fontTitle)
+ DeleteObject(details->fontTitle);
+
+ Plugin_FreeString(details->name);
+ }
+}
+
+
+static INT_PTR GroupDetails_OnDialogColor(HWND hwnd, HDC hdc, HWND hControl)
+{
+ HWND hParent = GetAncestor(hwnd, GA_PARENT);
+ if (NULL != hParent && hParent != hwnd)
+ return (INT_PTR)SendMessage(hParent, WM_CTLCOLORDLG, (WPARAM)hdc, (LPARAM)hControl);
+ return 0;
+}
+
+
+static INT_PTR GroupDetails_OnStaticColor(HWND hwnd, HDC hdc, HWND hControl)
+{
+ HWND hParent = GetAncestor(hwnd, GA_PARENT);
+ if (NULL != hParent && hParent != hwnd)
+ return (INT_PTR)SendMessage(hParent, WM_CTLCOLORSTATIC, (WPARAM)hdc, (LPARAM)hControl);
+ return 0;
+}
+
+static LRESULT GroupDetails_OnNotify(HWND hwnd, INT controlId, NMHDR *pnmh)
+{
+ switch(controlId)
+ {
+ case IDC_HELPLINK:
+ if (NM_CLICK == pnmh->code)
+ GroupDetails_ShowHelp(hwnd);
+ return TRUE;
+ }
+ return 0;
+}
+
+static BOOL GroupDetails_OnGetUniqueName(HWND hwnd, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return FALSE;
+
+ GROUPDETAILS *details = GetDetails(hwnd);
+ return SUCCEEDED(StringCchCopy(pszBuffer, cchBufferMax,
+ (NULL != details && NULL != details->name) ? details->name : L""));
+}
+static INT_PTR WINAPI GroupDetails_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return GroupDetails_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: GroupDetails_OnDestroy(hwnd); break;
+ case WM_CTLCOLORDLG: return GroupDetails_OnDialogColor(hwnd, (HDC)wParam, (HWND)lParam);
+ case WM_CTLCOLORSTATIC: return GroupDetails_OnStaticColor(hwnd, (HDC)wParam, (HWND)lParam);
+ case WM_NOTIFY: MSGRESULT(hwnd, GroupDetails_OnNotify(hwnd, (INT)wParam, (NMHDR*)lParam));
+
+ case NSDM_GETUNIQUENAME: MSGRESULT(hwnd, GroupDetails_OnGetUniqueName(hwnd, (LPWSTR)lParam, (UINT)wParam));
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupDetailsService.cpp b/Src/Plugins/Library/ml_online/Setup/setupDetailsService.cpp
new file mode 100644
index 00000000..3e3907fa
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupDetailsService.cpp
@@ -0,0 +1,596 @@
+#include "./setupDetails.h"
+#include "../common.h"
+#include "../resource.h"
+#include "../wasabi.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omservicedetails.h>
+#include <ifc_omcachemanager.h>
+#include <ifc_omserviceevent.h>
+#include <ifc_omcachegroup.h>
+#include <ifc_omcacherecord.h>
+#include <ifc_imageloader.h>
+#include <ifc_omgraphics.h>
+#include <ifc_omserviceeventmngr.h>
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+
+#define GetPanel(__hwnd) ((ServicePanel*)GetPropW((__hwnd), MAKEINTATOM(DETAILS_PROP)))
+
+#define GET_IDETAILS(__service, __details)\
+ (NULL != (service) && SUCCEEDED((service)->QueryInterface(IFC_OmServiceDetails, (void**)&(__details))))
+
+static INT_PTR WINAPI ServicePanel_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+
+HWND OmSetupDetails_CreateServiceView(HWND hParent, ifc_omservice *service)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_SETUP_SERVICEDETAILS, hParent, ServicePanel_DialogProc, (LPARAM)service);
+}
+
+static HFONT ServicePanel_PickTitleFont(HWND hwnd, LPCWSTR pszTitle, INT cchTitle, INT maxWidth)
+{
+ HFONT dialogFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+
+ LOGFONT lf;
+ if (0 == GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ return NULL;
+
+ HFONT titleFont = NULL;
+ if (cchTitle > 0)
+ {
+ LOGFONT lf;
+ if (0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Arial Bold");
+ lf.lfWidth = 0;
+ lf.lfWeight = FW_DONTCARE;
+ lf.lfQuality = 5/*ANTIALIASED_QUALITY*/;
+
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ HFONT origFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
+ SIZE textSize;
+
+ INT heightLimit = (lf.lfHeight < 0) ? 1 : -1;
+ lf.lfHeight += (lf.lfHeight < 0) ? -2 : +2;
+ do
+ {
+ textSize.cx = 0;
+ if (NULL != titleFont) DeleteObject(titleFont);
+ titleFont = CreateFontIndirect(&lf);
+ if (NULL != titleFont)
+ {
+ SelectObject(hdc, titleFont);
+ GetTextExtentPoint32(hdc, pszTitle, cchTitle, &textSize);
+ }
+ lf.lfHeight += (lf.lfHeight < 0) ? 1 : -1;
+
+ } while(textSize.cx > maxWidth && lf.lfHeight != heightLimit);
+
+ if (0 == textSize.cx)
+ {
+ DeleteObject(titleFont);
+ titleFont = NULL;
+ }
+
+ SelectObject(hdc, origFont);
+ ReleaseDC(hwnd, hdc);
+ }
+ }
+ }
+
+ if (NULL == titleFont &&
+ 0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ titleFont = CreateFontIndirect(&lf);
+ }
+ return titleFont;
+}
+
+static void ServicePanel_SetServiceName(HWND hwnd, ifc_omservice *service)
+{
+ HWND hTitle = GetDlgItem(hwnd, IDC_TITLE);
+ if (NULL == hTitle) return;
+
+ WCHAR szBuffer[128];
+ if (NULL == service ||
+ FAILED(service->GetName(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ szBuffer[0] = L'\0';
+ }
+
+
+
+ SERVICEDETAILS *details = GetDetails(hwnd);
+ if (NULL != details)
+ {
+ INT cchBuffer = lstrlen(szBuffer);
+ RECT rc;
+ GetClientRect(hTitle, &rc);
+ HFONT titleFont = ServicePanel_PickTitleFont(hwnd, szBuffer, cchBuffer, rc.right - rc.left);
+ if (NULL != titleFont)
+ {
+ if (NULL != details->fontTitle) DeleteObject(details->fontTitle);
+ details->fontTitle = titleFont;
+ SendMessage(hTitle, WM_SETFONT, (WPARAM)details->fontTitle, 0L);
+ }
+ }
+
+ SetWindowText(hTitle, szBuffer);
+ InvalidateRect(hTitle, NULL, TRUE);
+}
+
+
+static void ServicePanel_SetServiceDescription(HWND hwnd, ifc_omservice *service)
+{
+ HWND hDescription = GetDlgItem(hwnd, IDC_DESCRIPTION);
+ if (NULL == hDescription) return;
+
+ WCHAR szBuffer[4096] = {0};
+
+ ifc_omservicedetails *details = 0;
+ if (GET_IDETAILS(service, details))
+ {
+ details->GetDescription(szBuffer, ARRAYSIZE(szBuffer));
+ details->Release();
+ }
+
+ OmSetupDetails_SetDescription(hDescription, szBuffer);
+}
+
+static LPCWSTR ServicePanel_FormatDate(LPCWSTR pszDate, LPWSTR pszBuffer, INT cchBufferMax)
+{
+ SYSTEMTIME st;
+ ZeroMemory(&st, sizeof(SYSTEMTIME));
+ LPCWSTR cursor;
+
+ cursor = pszDate;
+ INT index = 0;
+
+ for(;;)
+ {
+
+ INT iVal;
+
+ if (FALSE == StrToIntEx(cursor, STIF_DEFAULT, &iVal) || iVal < 1)
+ {
+ index = 0;
+ break;
+ }
+
+ if (0 == index)
+ {
+ if (iVal < 2000 || iVal > 2100)
+ break;
+ st.wYear = iVal;
+ index++;
+ }
+ else if (1 == index)
+ {
+ if (iVal < 1 || iVal > 12)
+ break;
+ st.wMonth = iVal;
+ index++;
+ }
+ else if (2 == index)
+ {
+ if (iVal < 1 || iVal > 31)
+ break;
+ st.wDay = iVal;
+ index++;
+ }
+ else
+ {
+ index = 0;
+ break;
+ }
+
+ while(L'\0' != *cursor && L'-' != *cursor) cursor++;
+ if (L'-' == *cursor) cursor++;
+ if (L'\0' == *cursor)
+ break;
+
+ }
+
+ if (3 == index &&
+ 0 != GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, pszBuffer, cchBufferMax))
+ {
+ return pszBuffer;
+ }
+
+ return pszDate;
+}
+
+
+
+static HRESULT ServicePanel_GetFullName(ifc_omservicedetails *details, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ *pszBuffer = L'\0';
+
+ HRESULT hr = S_OK;
+ if (NULL != details)
+ {
+ hr = details->GetAuthorFirst(pszBuffer, cchBufferMax);
+ if (SUCCEEDED(hr))
+ {
+ UINT cchBuffer = lstrlen(pszBuffer);
+ LPWSTR cursor = pszBuffer + cchBuffer;
+ size_t remaining = cchBufferMax - cchBuffer;
+
+ if (cursor != pszBuffer)
+ {
+ hr = StringCchCopyEx(cursor, remaining, L" ", &cursor, &remaining, 0);
+ if (SUCCEEDED(hr))
+ {
+ hr = details->GetAuthorLast(cursor, (UINT)remaining);
+ if (FAILED(hr) || L'\0' == *cursor)
+ {
+ pszBuffer[cchBuffer] = L'\0';
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+static void ServicePanel_SetServiceMeta(HWND hwnd, ifc_omservice *service)
+{
+ HWND hMeta = GetDlgItem(hwnd, IDC_SERVICEMETA);
+ if (NULL == hMeta) return;
+
+ WCHAR szBuffer[512] = {0};
+
+ ifc_omservicedetails *svcdetails = 0;
+ if (GET_IDETAILS(service, svcdetails))
+ {
+ HRESULT hr = S_OK;
+ LPWSTR cursor = szBuffer;
+ WCHAR szValue[256] = {0}, szPrefix[64] = {0};
+ size_t remaining = ARRAYSIZE(szBuffer);
+
+ if (SUCCEEDED(ServicePanel_GetFullName(svcdetails, szValue, ARRAYSIZE(szValue))) && L'\0' != szValue[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SERVICE_BYAUTHOR, szPrefix, ARRAYSIZE(szPrefix));
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"%s%s", szPrefix, szValue);
+ }
+
+ if (SUCCEEDED(svcdetails->GetUpdated(szValue, ARRAYSIZE(szValue))) && L'\0' != szValue[0])
+ {
+ if (cursor != szBuffer)
+ hr = StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ if (SUCCEEDED(hr))
+ {
+ WCHAR szDate[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SERVICE_LASTUPDATED, szPrefix, ARRAYSIZE(szPrefix));
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"%s%s", szPrefix, ServicePanel_FormatDate(szValue, szDate, ARRAYSIZE(szDate)));
+ }
+ }
+
+ svcdetails->Release();
+
+ }
+
+ SERVICEDETAILS *details = GetDetails(hwnd);
+ if (NULL != details && NULL == details->fontMeta)
+ {
+ HFONT dialogFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+ LOGFONT lf;
+ if (0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Tahoma");
+ lf.lfWidth = 0;
+ lf.lfHeight += (lf.lfHeight < 0) ? 1 : -1;
+ lf.lfQuality = ANTIALIASED_QUALITY;
+ details->fontMeta = CreateFontIndirect(&lf);
+ }
+
+ if (NULL != details->fontMeta)
+ {
+ SendMessage(hMeta, WM_SETFONT, (WPARAM)details->fontMeta, 0L);
+ }
+ }
+
+ SetWindowText(hMeta, szBuffer);
+ if (0 != ShowWindow(hMeta, (L'\0' != szBuffer[0]) ? SW_SHOWNA : SW_HIDE))
+ InvalidateRect(hMeta, NULL, TRUE);
+}
+
+static void ServicePanel_SetThumbnail(HWND hwnd, ifc_omservice *service)
+{
+ HWND hThumbnail = GetDlgItem(hwnd, IDC_THUMBNAIL);
+ if (NULL == hThumbnail) return;
+
+ SendMessage(hThumbnail, WM_SETREDRAW, FALSE, 0L);
+
+ HBITMAP hBitmap;
+
+ BITMAPINFOHEADER header;
+ void *pixelData;
+
+ WCHAR szPath[2048];
+
+ ifc_omservicedetails *details = 0;
+ if (GET_IDETAILS(service, details))
+ {
+ if (SUCCEEDED(details->GetThumbnail(szPath, ARRAYSIZE(szPath))))
+ {
+ ifc_omcachemanager *cacheManager;
+ if (SUCCEEDED(OMUTILITY->GetCacheManager(&cacheManager)))
+ {
+ ifc_omcachegroup *cacheGroup;
+ if (SUCCEEDED(cacheManager->Find(L"thumbnail", TRUE, &cacheGroup, NULL)))
+ {
+ ifc_omcacherecord *cacheRecord;
+ if (SUCCEEDED(cacheGroup->Find(szPath, TRUE, &cacheRecord, FALSE)))
+ {
+ cacheRecord->Release();
+ }
+ cacheGroup->Release();
+ }
+ cacheManager->Release();
+ }
+ }
+ details->Release();
+ }
+
+ ifc_omimageloader *imageLoader;
+ if (SUCCEEDED(OMUTILITY->QueryImageLoader(NULL, szPath, FALSE, &imageLoader)))
+ {
+ if (FAILED(imageLoader->LoadBitmapEx(&hBitmap, &header, &pixelData)))
+ hBitmap = NULL;
+ imageLoader->Release();
+ }
+
+ if (NULL == hBitmap &&
+ SUCCEEDED(OMUTILITY->QueryImageLoader(WASABI_API_ORIG_HINST, MAKEINTRESOURCE(IDR_SERVICE64X64_IMAGE), FALSE, &imageLoader)))
+ {
+ if (FAILED(imageLoader->LoadBitmapEx(&hBitmap, &header, &pixelData)))
+ hBitmap = NULL;
+ imageLoader->Release();
+ }
+
+ HBITMAP hTest = (HBITMAP)SendMessage(hThumbnail, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBitmap);
+ if (NULL != hTest)
+ DeleteObject(hTest);
+
+ if (NULL != hBitmap)
+ {
+ hTest = (HBITMAP)SendMessage(hThumbnail, STM_GETIMAGE, IMAGE_BITMAP, 0L);
+ if (hTest != hBitmap)
+ { // this is XP and up image copy was created and alpha channel will be handled properly
+ DeleteObject(hBitmap);
+ }
+ else
+ { // fix alpha channel
+ if (32 == header.biBitCount)
+ {
+ HDC hdcFixed = CreateCompatibleDC(NULL);
+ if (NULL != hdcFixed)
+ {
+ BITMAPINFOHEADER headerFixed;
+ CopyMemory(&headerFixed, &header, sizeof(BITMAPINFOHEADER));
+ BYTE *pixelsFixed;
+ INT cx = header.biWidth;
+ INT cy = abs(header.biHeight);
+ HBITMAP bitmapFixed = CreateDIBSection(NULL, (LPBITMAPINFO)&headerFixed, DIB_RGB_COLORS, (void**)&pixelsFixed, NULL, 0);
+
+ if (NULL != bitmapFixed)
+ {
+ HBITMAP bitmapOrig = (HBITMAP)SelectObject(hdcFixed, bitmapFixed);
+ HBRUSH hb = (HBRUSH)SendMessage(hwnd, WM_CTLCOLORDLG, (WPARAM)hdcFixed, (LPARAM)hwnd);
+ if (NULL == hb)
+ hb = GetSysColorBrush(COLOR_3DFACE);
+ RECT rect;
+ SetRect(&rect, 0, 0, cx, cy);
+ FillRect(hdcFixed, &rect, hb);
+
+ ifc_omgraphics *graphics;
+ if (SUCCEEDED(OMUTILITY->GetGraphics(&graphics)))
+ {
+ HDC hdcSrc = CreateCompatibleDC(NULL);
+ if (NULL != hdcSrc)
+ {
+ HBITMAP bitmapSrcOrig = (HBITMAP)SelectObject(hdcSrc, hBitmap);
+ BLENDFUNCTION bf;
+ bf.BlendOp = AC_SRC_OVER;
+ bf.BlendFlags = 0;
+ bf.SourceConstantAlpha = 0xFF;
+ bf.AlphaFormat = AC_SRC_ALPHA;
+
+ RECT blendRect;
+ SetRect(&blendRect, 0, 0, cx, cy);
+
+ graphics->Premultiply((BYTE*)pixelData, cx, cy);
+ graphics->AlphaBlend(hdcFixed, &blendRect, hdcSrc, &blendRect, bf);
+
+ SelectObject(hdcSrc, bitmapSrcOrig);
+ DeleteDC(hdcSrc);
+ }
+ graphics->Release();
+ }
+
+ SelectObject(hdcFixed, bitmapOrig);
+ SendMessage(hThumbnail, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bitmapFixed);
+ DeleteObject(hBitmap);
+
+ }
+ DeleteDC(hdcFixed);
+ }
+ }
+ }
+ }
+
+ RECT clientRect;
+ if (GetClientRect(hThumbnail, &clientRect))
+ {
+ INT cx = clientRect.right - clientRect.left;
+ INT cy = clientRect.bottom - clientRect.top;
+ if (64 != cx || 64 != cy)
+ {
+ SetWindowPos(hThumbnail, NULL, 0, 0, 64, 64, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+ }
+
+ SendMessage(hThumbnail, WM_SETREDRAW, TRUE, 0L);
+
+ if (0 != ShowWindow(hThumbnail, (NULL != hBitmap) ? SW_SHOWNA : SW_HIDE))
+ InvalidateRect(hThumbnail, NULL, TRUE);
+}
+
+
+static void CALLBACK ServicePanel_OnServiceNotify(UINT serviceUid, UINT callbackType, UINT callbackParam, ULONG_PTR user)
+{
+ HWND hwnd = (HWND)user;
+ if (NULL == hwnd) return;
+
+ SERVICEDETAILS *details = GetDetails(hwnd);
+
+ if (NULL == details ||
+ NULL == details->service ||
+ serviceUid != details->service->GetId())
+ {
+ return;
+ }
+
+ switch(callbackType)
+ {
+ case OmService::eventServiceModified:
+ if (0 != (OmService::modifiedName & callbackParam))
+ ServicePanel_SetServiceName(hwnd, details->service);
+ if (0 != (OmService::modifiedDescription & callbackParam))
+ ServicePanel_SetServiceDescription(hwnd, details->service);
+ if (0 != ((OmService::modifiedAuthor | OmService::modifiedDate) & callbackParam))
+ ServicePanel_SetServiceMeta(hwnd, details->service);
+ if (0 != (OmService::modifiedThumbnail & callbackParam))
+ ServicePanel_SetThumbnail(hwnd, details->service);
+ break;
+ }
+}
+
+static INT_PTR ServicePanel_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
+{
+ ifc_omservice *service = (ifc_omservice*)lParam;
+
+ SERVICEDETAILS *details = (SERVICEDETAILS*)malloc(sizeof(SERVICEDETAILS));
+ if (NULL == details) return FALSE;
+ ZeroMemory(details, sizeof(SERVICEDETAILS));
+
+ if (!SetProp(hwnd, MAKEINTATOM(DETAILS_PROP), details))
+ return FALSE;
+
+ details->service = service;
+ details->service->AddRef();
+
+ ServicePanel_SetServiceName(hwnd, service);
+ ServicePanel_SetServiceDescription(hwnd, service);
+ ServicePanel_SetThumbnail(hwnd, service);
+ ServicePanel_SetServiceMeta(hwnd, service);
+
+ if (NULL != service)
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->GetEventManager(&eventManager)))
+ {
+ if (SUCCEEDED(eventManager->RegisterHandler(eventHander)))
+ {
+ details->eventHandler = eventHandler;
+ }
+ else
+ {
+ eventHandler->Release();
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static void ServicePanel_OnDestroy(HWND hwnd)
+{
+ OMSERVICEMNGR->UnregisterCallback(ServicePanel_OnServiceNotify, (ULONG_PTR)hwnd);
+
+ SERVICEDETAILS *details = GetDetails(hwnd);
+ RemoveProp(hwnd, MAKEINTATOM(DETAILS_PROP));
+
+ if (NULL != details)
+ {
+ if (NULL != details->service)
+ details->service->Release();
+ if (NULL != details->fontTitle)
+ DeleteObject(details->fontTitle);
+ if (NULL != details->fontMeta)
+ DeleteObject(details->fontMeta);
+ }
+
+ HWND hThumbnail = GetDlgItem(hwnd, IDC_THUMBNAIL);
+ if (NULL != hThumbnail)
+ {
+ HBITMAP hBitmap = (HBITMAP)SendMessage(hThumbnail, STM_SETIMAGE, IMAGE_BITMAP, 0L);
+ if (NULL != hBitmap)
+ DeleteObject(hBitmap);
+ }
+}
+
+
+static INT_PTR ServicePanel_OnDialogColor(HWND hwnd, HDC hdc, HWND hControl)
+{
+ HWND hParent = GetAncestor(hwnd, GA_PARENT);
+ if (NULL != hParent && hParent != hwnd)
+ return (INT_PTR)SendMessage(hParent, WM_CTLCOLORDLG, (WPARAM)hdc, (LPARAM)hControl);
+ return 0;
+}
+
+
+static INT_PTR ServicePanel_OnStaticColor(HWND hwnd, HDC hdc, HWND hControl)
+{
+ INT_PTR result = 0;
+ HWND hParent = GetAncestor(hwnd, GA_PARENT);
+ if (NULL != hParent && hParent != hwnd)
+ result = (INT_PTR)SendMessage(hParent, WM_CTLCOLORSTATIC, (WPARAM)hdc, (LPARAM)hControl);
+
+ INT controlId = GetDlgCtrlID(hControl);
+ switch(controlId)
+ {
+ case IDC_SERVICEMETA:
+ {
+ COLORREF rgbBk = GetBkColor(hdc);
+ COLORREF rgbFg = GetTextColor(hdc);
+
+ ifc_omgraphics *graphics;
+ if (SUCCEEDED(OMUTILITY->GetGraphics(&graphics)))
+ {
+ graphics->BlendColor(rgbFg, rgbBk, 180, &rgbFg);
+ graphics->Release();
+ }
+
+ SetTextColor(hdc, rgbFg);
+ }
+ break;
+ }
+ return result;
+}
+
+static INT_PTR WINAPI ServicePanel_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return ServicePanel_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: ServicePanel_OnDestroy(hwnd); break;
+ case WM_CTLCOLORDLG: return ServicePanel_OnDialogColor(hwnd, (HDC)wParam, (HWND)lParam);
+ case WM_CTLCOLORSTATIC: return ServicePanel_OnStaticColor(hwnd, (HDC)wParam, (HWND)lParam);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupGroup.cpp b/Src/Plugins/Library/ml_online/Setup/setupGroup.cpp
new file mode 100644
index 00000000..bf1faf28
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupGroup.cpp
@@ -0,0 +1,929 @@
+#include "./setupGroup.h"
+#include "./setupGroupFilter.h"
+#include "./setupListboxLabel.h"
+#include "./setupDetails.h"
+#include "./setupPage.h"
+#include "../common.h"
+#include "../api__ml_online.h"
+#include "../resource.h"
+#include "../serviceHost.h"
+#include "../serviceHelper.h"
+
+#include "../../nu/menuHelpers.h"
+#include <vector>
+
+#include <ifc_omservice.h>
+#include <ifc_omstorage.h>
+#include <ifc_omstorageasync.h>
+#include <ifc_omserviceenum.h>
+#include <ifc_omfilestorage.h>
+
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <algorithm>
+
+typedef std::vector<ifc_omservice*> ServiceList;
+
+#define GROUP_MARGINCX 0
+#define GROUP_MARGINCY 1
+
+#define TEXT_OFFSET_LEFT 2
+#define TEXT_OFFSET_BOTTOM 2
+#define TEXT_ALIGN (TA_LEFT | TA_BOTTOM)
+
+SetupGroup::SetupGroup(INT groupId, LPCWSTR pszName, LPCWSTR pszAddress, const GUID *storageId, const GUID *filterId, UINT fStyle)
+ : ref(1), id(groupId), name(NULL), flags(0), emptyLabel(NULL), errorCode(S_OK), hPage(NULL),
+ longName(NULL), description(NULL), address(NULL), loadResult(NULL), style(fStyle), loadComplete(NULL)
+{
+ name = Plugin_DuplicateResString(pszName);
+ address = Plugin_DuplicateResString(pszAddress);
+ this->storageId = (NULL != storageId) ? *storageId : GUID_NULL;
+ this->filterId = (NULL != filterId) ? *filterId : GUID_NULL;
+
+ InitializeCriticalSection(&lock);
+}
+
+SetupGroup::~SetupGroup()
+{
+ Plugin_FreeResString(name);
+ Plugin_FreeResString(address);
+
+ SetLongName(NULL);
+ SetDescription(NULL);
+
+ EnterCriticalSection(&lock);
+
+ size_t index = list.size();
+ while(index--)
+ {
+ list[index]->Release();
+ }
+
+ if (NULL != emptyLabel)
+ emptyLabel->Release();
+
+ if (NULL != loadResult)
+ {
+ ifc_omstorage *storage;
+ HRESULT hr = OMSERVICEMNGR->QueryStorage(&storageId, &storage);
+ if (SUCCEEDED(hr))
+ {
+ storage->RequestAbort(loadResult, TRUE);
+ }
+
+ loadResult->Release();
+ loadResult = NULL;
+ }
+
+ if (NULL != loadComplete)
+ CloseHandle(loadComplete);
+
+ LeaveCriticalSection(&lock);
+
+ DeleteCriticalSection(&lock);
+
+}
+
+SetupGroup *SetupGroup::CreateInstance(INT groupId, LPCWSTR pszName, LPCWSTR pszAddress, const GUID *storageId, const GUID *filterId, UINT fStyle)
+{
+ return new SetupGroup(groupId, pszName, pszAddress, storageId, filterId, fStyle);
+}
+
+ULONG SetupGroup::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupGroup::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+ return r;
+}
+
+HRESULT SetupGroup::GetName(LPWSTR pszBuffer, INT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ HRESULT hr;
+ if (NULL != name)
+ {
+ if (IS_INTRESOURCE(name))
+ {
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)name, pszBuffer, cchBufferMax);
+ hr = (L'\0' != *pszBuffer) ? S_OK : E_FAIL;
+ }
+ else
+ {
+ hr = StringCchCopyW(pszBuffer, cchBufferMax, name);
+ }
+ }
+ else
+ {
+ hr = StringCchCopyW(pszBuffer, cchBufferMax, L"Unknown");
+ }
+ return hr;
+}
+
+HRESULT SetupGroup::GetLongName(LPWSTR pszBuffer, INT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ HRESULT hr;
+ if (NULL == longName)
+ return GetName(pszBuffer, cchBufferMax);
+
+ if (IS_INTRESOURCE(longName))
+ {
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)longName, pszBuffer, cchBufferMax);
+ hr = (L'\0' != *pszBuffer) ? S_OK : E_FAIL;
+ }
+ else
+ {
+ hr = StringCchCopyW(pszBuffer, cchBufferMax, longName);
+ }
+ return hr;
+}
+
+HRESULT SetupGroup::GetDescription(LPWSTR pszBuffer, INT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ HRESULT hr;
+
+ if (NULL != description && IS_INTRESOURCE(description))
+ {
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)description, pszBuffer, cchBufferMax);
+ hr = (L'\0' != *pszBuffer) ? S_OK : E_FAIL;
+ }
+ else
+ {
+ hr = StringCchCopyEx(pszBuffer, cchBufferMax, description, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ return hr;
+}
+size_t SetupGroup::GetRecordCount()
+{
+ return list.size();
+}
+
+size_t SetupGroup::GetListboxCount()
+{
+ if (0 != (flagCollapsed & flags)) return 0;
+ size_t listSize = list.size();
+
+ if (0 == listSize)
+ {
+ return (NULL != emptyLabel && FALSE == emptyLabel->IsNameNull()) ? 1 : 0;
+ }
+
+ return listSize;
+}
+
+SetupListboxItem *SetupGroup::GetListboxItem(size_t index)
+{
+ if (0 != (flagCollapsed & flags)) return NULL;
+ size_t listSize = list.size();
+
+ if (0 == listSize)
+ {
+ return (NULL != emptyLabel && FALSE == emptyLabel->IsNameNull()) ? emptyLabel : NULL;
+ }
+ return list[index];
+}
+
+BOOL SetupGroup::IsModified()
+{
+ size_t index = list.size();
+ while(index--)
+ {
+ if (list[index]->IsModified())
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL SetupGroup::IsExpanded()
+{
+ return (0 == (flagCollapsed & flags));
+}
+
+void SetupGroup::SetExpanded(BOOL fExpanded)
+{
+ if ((FALSE == fExpanded) == (FALSE == IsExpanded()))
+ return;
+
+ if (FALSE == fExpanded)
+ flags |= flagCollapsed;
+ else
+ flags &= ~flagCollapsed;
+}
+
+void SetupGroup::Clear(BOOL fInvalidate)
+{
+ size_t index = list.size();
+ if (0 == index) return;
+
+ EnterCriticalSection(&lock);
+
+ while(index--)
+ {
+ SetupRecord *record = list[index];
+ if (NULL != record)
+ {
+ record->Release();
+ }
+ }
+ list.clear();
+
+ LeaveCriticalSection(&lock);
+
+ SetEmptyText(MAKEINTRESOURCE(IDS_SETUP_EMPTYGROUP), FALSE);
+
+ if (FALSE != fInvalidate && NULL != hPage)
+ PostMessage(hPage, SPM_UPDATELIST, (WPARAM)id, NULL);
+}
+
+static void CALLBACK SetupGroup_LoadCallback(ifc_omstorageasync *result)
+{
+ if (NULL == result) return;
+ SetupGroup *group;
+ if (SUCCEEDED(result->GetData((void**)&group)) && NULL != group)
+ {
+ group->OnLoadCompleted();
+ }
+}
+
+
+__inline static int __cdecl SetupGroup_AlphabeticalSorter(const void *elem1, const void *elem2)
+{
+ SetupRecord *record1 = (SetupRecord*)elem1;
+ SetupRecord *record2 = (SetupRecord*)elem2;
+
+ if (NULL == record1 || NULL == record2)
+ return (INT)(INT_PTR)(record1 - record2);
+
+ ifc_omservice *svc1 = record1->GetService();
+ ifc_omservice *svc2 = record2->GetService();
+
+ if (NULL == svc1 || NULL == svc2)
+ return (INT)(INT_PTR)(svc1 - svc2);
+
+ WCHAR szBuffer1[256] = {0}, szBuffer2[256] = {0};
+ if (FAILED(svc1->GetName(szBuffer1, ARRAYSIZE(szBuffer1))))
+ szBuffer1[0] = L'\0';
+ if (FAILED(svc2->GetName(szBuffer2, ARRAYSIZE(szBuffer2))))
+ szBuffer2[0] = L'\0';
+
+ return CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szBuffer1, -1, szBuffer2, -1) - 2;
+}
+
+__inline static bool __cdecl SetupGroup_AlphabeticalSorter_V2(const void* elem1, const void* elem2)
+{
+ return SetupGroup_AlphabeticalSorter(elem1, elem2) < 0;
+}
+void SetupGroup::OnLoadCompleted()
+{
+ ifc_omstorage *storage;
+ HRESULT hr = OMSERVICEMNGR->QueryStorage(&storageId, &storage);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceenum *serviceEnum;
+ hr = storage->EndLoad(loadResult, &serviceEnum);
+ if (SUCCEEDED(hr))
+ {
+ SetupGroupFilter *filter;
+ if (FAILED(SetupGroupFilter::CreateInstance(&filterId, &filter)))
+ {
+ filter = NULL;
+ }
+ else if (FAILED(filter->Initialize()))
+ {
+ filter->Release();
+ filter = NULL;
+ }
+
+ EnterCriticalSection(&lock);
+
+ ifc_omservice *service;
+ UINT filterResult, defaultFilter;
+ defaultFilter = SetupGroupFilter::serviceInclude;
+ if (0 != (styleDefaultUnsubscribed & style))
+ defaultFilter |= SetupGroupFilter::serviceForceUnsubscribe;
+ else if (0 != (styleDefaultSubscribed & style))
+ defaultFilter |= SetupGroupFilter::serviceForceSubscribe;
+
+ while(S_OK == serviceEnum->Next(1, &service, NULL))
+ {
+
+ filterResult = defaultFilter;
+ if (NULL != filter && FAILED(filter->ProcessService(service, &filterResult)))
+ filterResult = defaultFilter;
+
+ if (0 == (SetupGroupFilter::serviceInclude & filterResult))
+ {
+ service->Release();
+ service = NULL;
+ continue;
+ }
+
+ if (0 != (SetupGroupFilter::serviceForceUnsubscribe & filterResult))
+ ServiceHelper_Subscribe(service, TRUE, 0);
+ else if (0 != (SetupGroupFilter::serviceForceSubscribe & filterResult))
+ ServiceHelper_Subscribe(service, FALSE, 0);
+
+ if (0 != (styleSaveAll & style))
+ ServiceHelper_MarkModified(service, (UINT)-1, (UINT)-1);
+
+ SetupRecord *record = SetupRecord::CreateInstance(service);
+ if (NULL != record)
+ {
+ if (0 != (SetupGroupFilter::serviceForceUnsubscribe & filterResult))
+ record->SetSelected(FALSE);
+ else if (0 != (SetupGroupFilter::serviceForceSubscribe & filterResult))
+ record->SetSelected(TRUE);
+ list.push_back(record);
+ }
+
+ service->Release();
+ }
+
+ if (0 != (styleSortAlphabetically & style))
+ {
+ //qsort(list.first(), list.size(), sizeof(SetupRecord*), SetupGroup_AlphabeticalSorter);
+ std::sort(list.begin(), list.end(), SetupGroup_AlphabeticalSorter_V2);
+ }
+
+ LeaveCriticalSection(&lock);
+
+ serviceEnum->Release();
+ if (NULL != filter)
+ filter->Release();
+ }
+
+ storage->Release();
+ }
+
+ EnterCriticalSection(&lock);
+
+ loadResult->Release();
+ loadResult = NULL;
+
+ if (NULL != loadComplete)
+ {
+ SetEvent(loadComplete);
+ CloseHandle(loadComplete);
+ loadComplete = NULL;
+ }
+
+ LeaveCriticalSection(&lock);
+
+ LPCWSTR pszText = MAKEINTRESOURCE(((FAILED(hr)) ? IDS_SETUP_GROUPLOADFAILED : IDS_SETUP_EMPTYGROUP));
+ SetEmptyText( pszText, TRUE);
+}
+
+HRESULT SetupGroup::RequestReload()
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ HRESULT hr;
+
+ EnterCriticalSection(&lock);
+
+ if (NULL != loadResult)
+ hr = E_PENDING;
+ else
+ {
+ if (NULL != loadComplete)
+ {
+ CloseHandle(loadComplete);
+ loadComplete = NULL;
+ }
+
+ Clear(FALSE);
+ SetEmptyText(MAKEINTRESOURCE(IDS_SETUP_LOADINGGROUP), TRUE);
+
+ ifc_omstorage *storage;
+ hr = OMSERVICEMNGR->QueryStorage(&storageId, &storage);
+ if (SUCCEEDED(hr))
+ {
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ hr = storage->BeginLoad(address, serviceHost, SetupGroup_LoadCallback, this, &loadResult);
+ storage->Release();
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+ }
+
+ if (FAILED(hr))
+ {
+ SetEmptyText(MAKEINTRESOURCE(IDS_SETUP_GROUPLOADFAILED), TRUE);
+ }
+ }
+
+ LeaveCriticalSection(&lock);
+
+ return hr;
+}
+
+void SetupGroup::SetPageWnd(HWND hPage)
+{
+ this->hPage = hPage;
+}
+
+HRESULT SetupGroup::SignalLoadCompleted(HANDLE event)
+{
+ HRESULT hr;
+ if (NULL == event) return E_INVALIDARG;
+
+ EnterCriticalSection(&lock);
+
+ if (NULL == loadResult)
+ {
+ SetEvent(event);
+ hr = S_OK;
+ }
+ else
+ {
+ if (NULL != loadComplete)
+ CloseHandle(loadComplete);
+
+ if (FALSE == DuplicateHandle(GetCurrentProcess(), event, GetCurrentProcess(), &loadComplete, 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ DWORD error = GetLastError();
+ hr = HRESULT_FROM_WIN32(error);
+ }
+
+ hr = S_OK;
+ }
+
+ LeaveCriticalSection(&lock);
+ return hr;
+}
+
+HRESULT SetupGroup::Save(SetupLog *log)
+{
+ HRESULT hr(S_OK);
+ size_t index = list.size();
+ while(index--)
+ {
+ if (FAILED(list[index]->Save(log)))
+ hr = E_FAIL;
+ }
+ return hr;
+}
+
+void SetupGroup::GetColors(HDC hdc, UINT state, COLORREF *rgbBkOut, COLORREF *rgbTextOut)
+{
+ COLORREF rgbBk, rgbText;
+
+
+ if (0 != (ODS_DISABLED & state))
+ {
+ rgbBk = GetBkColor(hdc);
+ rgbText = GetSysColor(COLOR_GRAYTEXT);
+ }
+ else
+ {
+ if (0 != (ODS_SELECTED & state))
+ {
+ if (0 == (ODS_INACTIVE & state))
+ {
+ rgbBk = GetSysColor(COLOR_HIGHLIGHT);
+ rgbText = GetSysColor(COLOR_HIGHLIGHTTEXT);
+ }
+ else
+ {
+ rgbBk = GetSysColor(COLOR_3DFACE);
+ rgbText = GetSysColor(COLOR_WINDOWTEXT);
+ }
+ }
+ else
+ {
+ rgbBk = GetSysColor(COLOR_WINDOW);
+ rgbText = GetSysColor(COLOR_WINDOWTEXT);
+ }
+ }
+
+ if (NULL != rgbBkOut) *rgbBkOut = rgbBk;
+ if (NULL != rgbTextOut) *rgbTextOut = rgbText;
+}
+
+HBRUSH SetupGroup::GetBrush(HDC hdc, UINT state)
+{
+ if (0 != (ODS_DISABLED & state))
+ {
+ return GetSysColorBrush(COLOR_WINDOW);
+ }
+ if (0 != (ODS_COMBOBOXEDIT & state))
+ {
+ return GetSysColorBrush(COLOR_WINDOWTEXT);
+ }
+ if (0 != (ODS_SELECTED & state))
+ {
+ return GetSysColorBrush( (0 == (ODS_INACTIVE & state)) ? COLOR_HIGHLIGHT : COLOR_3DFACE);
+ }
+
+ return GetSysColorBrush(COLOR_WINDOW);
+}
+
+BOOL SetupGroup::MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy)
+{
+ HDC hdc = GetDCEx(instance->GetHwnd(), NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == hdc) return FALSE;
+
+ HFONT originalFont = (HFONT)SelectObject(hdc, instance->GetFont());
+ SIZE imageSize;
+ if (!instance->GetExpandboxMetrics(hdc, IsExpanded(), &imageSize))
+ ZeroMemory(&imageSize, sizeof(SIZE));
+
+ if (NULL != cy)
+ {
+ *cy = 0;
+ TEXTMETRIC tm;
+ if (GetTextMetrics(hdc, &tm))
+ {
+ *cy = tm.tmHeight + tm.tmExternalLeading;
+ if (imageSize.cy > (INT)*cy) *cy = imageSize.cy;
+ *cy += GROUP_MARGINCY*2;
+ }
+ }
+
+ if (NULL != cx)
+ {
+ *cx = imageSize.cx;
+ WCHAR szBuffer[128] = {0};
+ if (SUCCEEDED(GetName(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ INT cchBuffer = lstrlenW(szBuffer);
+ SIZE textSize;
+ if (0 != cchBuffer && GetTextExtentPoint32(hdc, szBuffer, cchBuffer, &textSize))
+ {
+ *cx += textSize.cx;
+ }
+ }
+ if (0 != *cx) *cx += GROUP_MARGINCX*2;
+ }
+
+ SelectObject(hdc, originalFont);
+ ReleaseDC(instance->GetHwnd(), hdc);
+ return TRUE;
+}
+
+
+static void SetupGroup_DrawFrame(HDC hdc, const RECT *prc, INT width, COLORREF rgbFrame)
+{
+ if (width > 0)
+ {
+ COLORREF rgbOld = SetBkColor(hdc, rgbFrame);
+
+ RECT rcPart;
+ SetRect(&rcPart, prc->left, prc->top, prc->right, prc->top + width);
+ ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcPart, NULL, 0, NULL);
+ SetRect(&rcPart, prc->left, prc->bottom - width, prc->right, prc->bottom);
+ ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcPart, NULL, 0, NULL);
+ SetRect(&rcPart, prc->left, prc->top + width, prc->left + width, prc->bottom - width);
+ ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcPart, NULL, 0, NULL);
+ SetRect(&rcPart, prc->right - width, prc->top + width, prc->right, prc->bottom - width);
+ ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcPart, NULL, 0, NULL);
+
+ if (rgbOld != rgbFrame)
+ SetBkColor(hdc, rgbOld);
+ }
+}
+BOOL SetupGroup::DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state)
+{
+ LONG paintLeft = prc->left + GROUP_MARGINCX;
+ RECT partRect;
+
+ SetRectEmpty(&partRect);
+
+ COLORREF rgbBk, rgbText;
+ GetColors(hdc, state, &rgbBk, &rgbText);
+
+ COLORREF origBk = SetBkColor(hdc, rgbBk);
+ COLORREF origText = SetTextColor(hdc, rgbText);
+ UINT textAlign = SetTextAlign(hdc, TEXT_ALIGN);
+
+ HRGN backRgn, rgn;
+ backRgn = CreateRectRgnIndirect(prc);
+ rgn = CreateRectRgn(0,0,0,0);
+
+ SetRectEmpty(&partRect);
+ if (instance->GetExpandboxMetrics(hdc, IsExpanded(), (((SIZE*)&partRect) + 1)))
+ {
+ INT space = (prc->bottom - prc->top) - (partRect.bottom- partRect.top);
+ INT offsetY = space / 2 + space%2;
+ if (offsetY < 0) offsetY = 0;
+ OffsetRect(&partRect, paintLeft, prc->top + offsetY);
+ if (instance->DrawExpandbox(hdc, IsExpanded(), &partRect, rgbBk, rgbText))
+ {
+ paintLeft = partRect.right;
+ if (SetRectRgn(rgn, partRect.left, partRect.top, partRect.right, partRect.bottom))
+ CombineRgn(backRgn, backRgn, rgn, RGN_DIFF);
+ }
+ }
+
+ WCHAR szBuffer[128] = {0};
+ INT cchBuffer = 0;
+ if (SUCCEEDED(GetName(szBuffer, ARRAYSIZE(szBuffer))))
+ cchBuffer = lstrlenW(szBuffer);
+
+ SetRect(&partRect, paintLeft, prc->top, prc->right, prc->bottom);
+ if (ExtTextOut(hdc, partRect.left + TEXT_OFFSET_LEFT, partRect.bottom - TEXT_OFFSET_BOTTOM,
+ ETO_OPAQUE | ETO_CLIPPED, &partRect, szBuffer, cchBuffer, NULL))
+ {
+ if (SetRectRgn(rgn, partRect.left, partRect.top, partRect.right, partRect.bottom))
+ CombineRgn(backRgn, backRgn, rgn, RGN_DIFF);
+ }
+
+
+
+ COLORREF rgbLine = ColorAdjustLuma(rgbBk, -150, TRUE);
+ if (rgbLine != rgbBk)
+ {
+ RECT lineRect;
+ SetRect(&lineRect, prc->left, prc->bottom - 1, prc->right, prc->bottom);
+
+ SetBkColor(hdc, rgbLine);
+ if (ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &lineRect, NULL, 0, NULL))
+ {
+ if (SetRectRgn(rgn, lineRect.left, lineRect.top, lineRect.right, lineRect.bottom))
+ CombineRgn(backRgn, backRgn, rgn, RGN_DIFF);
+ }
+
+ SetBkColor(hdc, rgbBk);
+ }
+
+ if (0 != (flagMenuActive & flags))
+ {
+ COLORREF rgbFrame = rgbLine; //ColorAdjustLuma(GetSysColor(COLOR_HIGHLIGHT), 100, TRUE);
+ SetupGroup_DrawFrame(hdc, prc, 1, rgbFrame);
+ if (SetRectRgn(rgn, prc->left + 1, prc->top + 1, prc->right - 1, prc->bottom - 1))
+ CombineRgn(backRgn, backRgn, rgn, RGN_AND);
+ }
+
+
+ if (NULL != backRgn)
+ {
+ FillRgn(hdc, backRgn, GetBrush(hdc, state));
+ DeleteObject(backRgn);
+ }
+ if (NULL != rgn)
+ DeleteObject(rgn);
+
+
+
+ if (ODS_FOCUS == ((ODS_FOCUS | 0x0200/*ODS_NOFOCUSRECT*/) & state))
+ DrawFocusRect(hdc, prc);
+
+ if (TEXT_ALIGN != textAlign) SetTextAlign(hdc, textAlign);
+ if (origBk != rgbBk) SetBkColor(hdc, origBk);
+ if (origText != rgbText) SetTextColor(hdc, origText);
+ return TRUE;
+}
+
+INT_PTR SetupGroup::KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey)
+{
+ switch(vKey)
+ {
+ case VK_SPACE:
+ InvertExpanded(instance);
+ return -2;
+ }
+ return -1;
+}
+BOOL SetupGroup::MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupGroup::MouseLeave(SetupListbox *instance, const RECT *prcItem)
+{
+ return FALSE;
+}
+BOOL SetupGroup::LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupGroup::LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupGroup::LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ InvertExpanded(instance);
+ return TRUE;
+}
+
+BOOL SetupGroup::RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return TRUE;
+}
+
+BOOL SetupGroup::RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ HMENU hMenu = instance->GetContextMenu(SetupListbox::menuGroupContext);
+ if (NULL == hMenu)
+ return FALSE;
+
+ hMenu = MenuHelper_DuplcateMenu(hMenu);
+ if (NULL == hMenu) return FALSE;
+
+ MENUITEMINFO mi;
+ mi.cbSize = sizeof(MENUITEMINFO);
+ mi.fMask = MIIM_STATE;
+ GetMenuItemInfo(hMenu, ID_GROUP_TOGGLE, FALSE, &mi);
+
+ mi.fMask = 0;
+ if (0 == (MFS_DEFAULT & mi.fState))
+ {
+ mi.fMask |= MIIM_STATE;
+ mi.fState |= MFS_DEFAULT;
+ }
+
+ WCHAR szBuffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(((IsExpanded()) ? IDS_COLLAPSE : IDS_EXPAND), szBuffer, ARRAYSIZE(szBuffer));
+ mi.fMask |= MIIM_STRING;
+ mi.dwTypeData = szBuffer;
+
+ if (0 != mi.fMask)
+ SetMenuItemInfo(hMenu, ID_GROUP_TOGGLE, FALSE, &mi);
+
+ if (0 == list.size())
+ {
+ EnableMenuItem(hMenu, ID_GROUP_SELECTALL, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
+ EnableMenuItem(hMenu, ID_GROUP_UNSELECTALL, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
+ }
+
+ if (0 != (flagLoading & flags))
+ {
+ EnableMenuItem(hMenu, ID_GROUP_RELOAD, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
+ }
+
+ MapWindowPoints(instance->GetHwnd(), HWND_DESKTOP, &pt, 1);
+
+ flags |= flagMenuActive;
+ instance->InvalidateRect(prcItem, TRUE);
+
+ INT cmd = TrackPopupMenuEx(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD, pt.x, pt.y, instance->GetHwnd(), NULL);
+ if (0 != cmd)
+ {
+ Command(instance, cmd, 0);
+ }
+
+ DestroyMenu(hMenu);
+
+ flags &= ~flagMenuActive;
+ instance->InvalidateRect(prcItem, TRUE);
+
+ return TRUE;
+}
+
+void SetupGroup::CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured)
+{
+}
+
+void SetupGroup::InvertExpanded(SetupListbox *instance)
+{
+ if (FALSE != IsExpanded())
+ ValidateSelection(instance);
+
+ SetExpanded(!IsExpanded());
+ if (NULL != instance)
+ {
+ instance->UpdateCount();
+ UpdateWindow(instance->GetHwnd());
+ }
+}
+void SetupGroup::SelectAll(SetupListbox *instance, BOOL fSelect)
+{
+ size_t index = list.size();
+
+ INT baseIndex;
+ if (NULL == instance || FALSE == instance->GetIndex(this, &baseIndex))
+ baseIndex = -1;
+ else
+ baseIndex++;
+
+ while(index--)
+ {
+ SetupRecord *record = list[index];
+ if (NULL != record && !record->IsDisabled())
+ {
+ if ((FALSE == fSelect) != (FALSE == record->IsSelected()))
+ {
+ record->SetSelected(fSelect);
+ if (0 == (flagCollapsed & flags) && NULL != instance && -1 != baseIndex)
+ {
+ instance->InvalidateItem((INT)(baseIndex + index), TRUE);
+ }
+ }
+ }
+ }
+}
+
+
+void SetupGroup::SetEmptyText(LPCWSTR pszText, BOOL fInvalidate)
+{
+ if (NULL == emptyLabel)
+ emptyLabel = SetupListboxLabel::CreateInstance(pszText);
+ else
+ emptyLabel->SetName(pszText);
+
+ if (FALSE != fInvalidate && NULL != hPage)
+ PostMessage(hPage, SPM_UPDATELIST, (WPARAM)id, NULL);
+}
+
+HWND SetupGroup::CreateDetailsView(HWND hParent)
+{
+ WCHAR szName[64] = {0};
+ if (FALSE == GetUniqueName(szName, ARRAYSIZE(szName)))
+ szName[0] = L'\0';
+
+ return SetupDetails_CreateGroupView(hParent, szName, this);
+}
+
+BOOL SetupGroup::GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer ||
+ FAILED(StringCchPrintf(pszBuffer, cchBufferMax, L"grp_id_%d", id)))
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+void SetupGroup::SetLongName(LPCWSTR pszText)
+{
+ if (NULL != longName && !IS_INTRESOURCE(longName))
+ Plugin_FreeString(longName);
+ longName = NULL;
+
+ if (NULL != pszText)
+ longName = (IS_INTRESOURCE(pszText)) ? (LPWSTR)pszText : Plugin_CopyString(pszText);
+}
+
+void SetupGroup::SetDescription(LPCWSTR pszText)
+{
+ if (NULL != description && !IS_INTRESOURCE(description))
+ Plugin_FreeString(description);
+ description = NULL;
+ if (NULL != pszText)
+ description = (IS_INTRESOURCE(pszText)) ? (LPWSTR)pszText : Plugin_CopyString(pszText);
+}
+
+void SetupGroup::ValidateSelection(SetupListbox *instance)
+{
+ if (NULL == instance)
+ return;
+
+ SetupListboxItem *selection = instance->GetSelection();
+ if (NULL != selection)
+ {
+ EnterCriticalSection(&lock);
+ size_t index = list.size();
+ while(index--)
+ {
+ if (list[index] == selection)
+ {
+ instance->SetSelection(this);
+ break;
+ }
+ }
+
+ LeaveCriticalSection(&lock);
+ }
+}
+
+void SetupGroup::Command(SetupListbox *instance, INT commandId, INT eventId)
+{
+ switch(commandId)
+ {
+ case ID_GROUP_TOGGLE:
+ InvertExpanded(instance);
+ break;
+ case ID_GROUP_SELECTALL:
+ SelectAll(instance, TRUE);
+ break;
+ case ID_GROUP_UNSELECTALL:
+ SelectAll(instance, FALSE);
+ break;
+ case ID_GROUP_RELOAD:
+ ValidateSelection(instance);
+ RequestReload();
+ break;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupGroup.h b/Src/Plugins/Library/ml_online/Setup/setupGroup.h
new file mode 100644
index 00000000..ce3f3c5f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupGroup.h
@@ -0,0 +1,131 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUP_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUP_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "./setupRecord.h"
+#include "./setupListbox.h"
+#include <vector>
+
+class SetupListboxLabel;
+class SetupLog;
+class SetupPage;
+class ifc_omstorage;
+
+class SetupGroup : public SetupListboxItem
+{
+public:
+ typedef enum
+ {
+ styleDefaultUnsubscribed = 0x00000001,
+ styleDefaultSubscribed = 0x00000002,
+ styleSortAlphabetically = 0x00000008,
+ styleSaveAll = 0x00000010,
+ } GroupStyles;
+protected:
+ typedef enum
+ {
+ flagCollapsed = 0x0001,
+ flagMenuActive = 0x0002,
+ flagLoading = 0x0004,
+ } GroupFlags;
+
+protected:
+ SetupGroup(INT groupId, LPCWSTR pszName, LPCWSTR pszAddress, const GUID *storageId, const GUID *filterId, UINT fStyle);
+ ~SetupGroup();
+
+public:
+ static SetupGroup *CreateInstance(INT groupId, LPCWSTR pszName, LPCWSTR pszAddress, const GUID *storageId, const GUID *filterId, UINT fStyle);
+
+public:
+ ULONG AddRef();
+ ULONG Release();
+
+ INT GetId() { return id; }
+ HRESULT GetName(LPWSTR pszBuffer, INT cchBufferMax);
+ HRESULT GetLongName(LPWSTR pszBuffer, INT cchBufferMax);
+ HRESULT GetDescription(LPWSTR pszBuffer, INT cchBufferMax);
+
+ size_t GetRecordCount();
+ SetupRecord *GetRecord(size_t index) { return list[index]; }
+
+ size_t GetListboxCount();
+ SetupListboxItem *GetListboxItem(size_t index);
+
+ BOOL IsModified();
+
+ BOOL IsExpanded();
+ void SetExpanded(BOOL fExpanded);
+ void SelectAll(SetupListbox *instance, BOOL fSelect);
+
+ HRESULT RequestReload();
+ HRESULT Save(SetupLog *log);
+
+
+ void SetEmptyText(LPCWSTR pszText, BOOL fInvalidate);
+ void SetLongName(LPCWSTR pszText);
+ void SetDescription(LPCWSTR pszText);
+
+ void GetColors(HDC hdc, UINT state, COLORREF *rgbBkOut, COLORREF *rgbTextOut);
+ HBRUSH GetBrush(HDC hdc, UINT state);
+
+ HRESULT SignalLoadCompleted(HANDLE event);
+ void ValidateSelection(SetupListbox *instance);
+
+ /* SetupListboxItem */
+ BOOL MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy);
+ BOOL DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state);
+ INT_PTR KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey);
+ BOOL MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL MouseLeave(SetupListbox *instance, const RECT *prcItem);
+ BOOL LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ void CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured);
+ BOOL IsDisabled() { return FALSE; }
+ void Command(SetupListbox *instance, INT commandId, INT eventId);
+ HWND CreateDetailsView(HWND hParent);
+ BOOL GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax);
+
+ void SetError(HRESULT code) { errorCode = code; }
+ HRESULT GetError() { return errorCode; }
+
+ void Clear(BOOL fInvalidate);
+
+ void SetPageWnd(HWND hPage);
+
+protected:
+ void InvertExpanded(SetupListbox *instance);
+ void OnLoadCompleted();
+
+
+private:
+ friend static void CALLBACK SetupGroup_LoadCallback(ifc_omstorageasync *result);
+
+
+protected:
+ ULONG ref;
+ INT id;
+ LPWSTR name;
+ LPWSTR longName;
+ LPWSTR description;
+ UINT style;
+ UINT flags;
+ LPWSTR address;
+ GUID storageId;
+ GUID filterId;
+ HRESULT errorCode;
+ std::vector<SetupRecord*> list;
+ SetupListboxLabel *emptyLabel;
+ CRITICAL_SECTION lock;
+ ifc_omstorageasync *loadResult;
+ HWND hPage;
+ HANDLE loadComplete;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUP_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupGroupFilter.cpp b/Src/Plugins/Library/ml_online/Setup/setupGroupFilter.cpp
new file mode 100644
index 00000000..c4d4311f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupGroupFilter.cpp
@@ -0,0 +1,226 @@
+#include "./setupGroupFilter.h"
+#include "../api__ml_online.h"
+#include "../serviceHost.h"
+#include "../config.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omstorage.h>
+#include <ifc_omserviceenum.h>
+#include <ifc_omfilestorage.h>
+
+
+SetupGroupFilter::SetupGroupFilter(const GUID *filterId)
+ : ref(1)
+{
+ id = (NULL != filterId) ? *filterId : GUID_NULL;
+}
+
+SetupGroupFilter::~SetupGroupFilter()
+{
+}
+
+HRESULT SetupGroupFilter::CreateInstance(const GUID *filterId, SetupGroupFilter **instance)
+{
+ if (NULL == filterId)
+ {
+ *instance = NULL;
+ return E_INVALIDARG;
+ }
+
+ if (FALSE != IsEqualGUID(*filterId, FUID_SetupFeaturedGroupFilter))
+ return SetupFeaturedGroupFilter::CreateInstance((SetupFeaturedGroupFilter**)instance);
+ if (FALSE != IsEqualGUID(*filterId, FUID_SetupKnownGroupFilter))
+ return SetupKnownGroupFilter::CreateInstance((SetupKnownGroupFilter**)instance);
+
+ *instance = NULL;
+ return E_NOINTERFACE;
+}
+
+ULONG SetupGroupFilter::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupGroupFilter::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+ return r;
+}
+
+HRESULT SetupGroupFilter::GetId(GUID *filterId)
+{
+ if (NULL == filterId)
+ return E_POINTER;
+ *filterId = id;
+ return S_OK;
+}
+
+BOOL CALLBACK SetupGroupFilter::AppendServiceIdCallback(UINT serviceId, void *data)
+{
+ ServiceIdList *list = (ServiceIdList*)data;
+ if (NULL == list) return FALSE;
+ list->push_back(serviceId);
+ return TRUE;
+}
+
+SetupFeaturedGroupFilter::SetupFeaturedGroupFilter()
+ : SetupGroupFilter(&FUID_SetupFeaturedGroupFilter)
+{
+}
+
+SetupFeaturedGroupFilter::~SetupFeaturedGroupFilter()
+{
+}
+
+HRESULT SetupFeaturedGroupFilter::CreateInstance(SetupFeaturedGroupFilter **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new SetupFeaturedGroupFilter();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+HRESULT SetupFeaturedGroupFilter::Initialize()
+{
+ HRESULT hr;
+ ifc_omstorage *storage;
+
+ filterList.clear();
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ hr = OMSERVICEMNGR->QueryStorage(&SUID_OmStorageIni, &storage);
+ if(FAILED(hr))
+ return hr;
+
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ ifc_omserviceenum *serviceEnum;
+ hr = storage->Load(L"*.ini", serviceHost, &serviceEnum);
+ if(SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ while(S_OK == serviceEnum->Next(1, &service, NULL) && NULL != service)
+ {
+ filterList.push_back(service->GetId());
+ }
+ serviceEnum->Release();
+ }
+ storage->Release();
+
+ ServiceIdList promoList;
+ Config_ReadServiceIdList("Setup", "featuredExtra", ',', AppendServiceIdCallback, &promoList);
+
+ ServiceIdList historyList;
+ Config_ReadServiceIdList("Setup", "featuredHistory", ',', AppendServiceIdCallback, &historyList);
+
+ size_t index = historyList.size();
+ size_t filterIndex, filterSize = filterList.size();
+
+ while(index--)
+ {
+ size_t promoSize = promoList.size();
+ for(filterIndex = 0; filterIndex < promoSize; filterIndex++)
+ {
+ if (promoList[filterIndex] == historyList[index])
+ {
+ promoList.erase(promoList.begin() + filterIndex);
+ break;
+ }
+ }
+ if (filterIndex == promoSize)
+ {
+ for(filterIndex = 0; filterIndex < filterSize; filterIndex++)
+ {
+ if (filterList[filterIndex] == historyList[index])
+ break;
+ }
+
+ if (filterIndex == filterSize)
+ filterList.push_back(historyList[index]);
+ }
+ }
+
+ return hr;
+}
+
+HRESULT SetupFeaturedGroupFilter::ProcessService(ifc_omservice *service, UINT *filterResult)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == filterResult)
+ return E_POINTER;
+
+ size_t index = filterList.size();
+ while(index--)
+ {
+ if (filterList[index] == service->GetId())
+ {
+ filterList.erase(filterList.begin() + index);
+ *filterResult = serviceIgnore;
+ break;
+ }
+ }
+ return S_OK;
+}
+
+SetupKnownGroupFilter::SetupKnownGroupFilter()
+ : SetupGroupFilter(&FUID_SetupKnownGroupFilter)
+{
+}
+
+SetupKnownGroupFilter::~SetupKnownGroupFilter()
+{
+}
+
+HRESULT SetupKnownGroupFilter::CreateInstance(SetupKnownGroupFilter **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new SetupKnownGroupFilter();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+HRESULT SetupKnownGroupFilter::Initialize()
+{
+ Config_ReadServiceIdList("Setup", "featuredExtra", ',', AppendServiceIdCallback, &filterList);
+ return S_OK;
+}
+
+HRESULT SetupKnownGroupFilter::ProcessService(ifc_omservice *service, UINT *filterResult)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == filterResult)
+ return E_POINTER;
+
+ size_t index = filterList.size();
+ while(index--)
+ {
+ if (filterList[index] == service->GetId())
+ {
+ filterList.erase(filterList.begin() + index);
+ *filterResult &= ~serviceForceUnsubscribe;
+ *filterResult |= serviceForceSubscribe;
+ break;
+ }
+ }
+
+ return S_OK;
+}
diff --git a/Src/Plugins/Library/ml_online/Setup/setupGroupList.cpp b/Src/Plugins/Library/ml_online/Setup/setupGroupList.cpp
new file mode 100644
index 00000000..9ee1de1f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupGroupList.cpp
@@ -0,0 +1,190 @@
+#include "./setupGroupList.h"
+#include "../api__ml_online.h"
+#include <strsafe.h>
+
+SetupGroupList::SetupGroupList()
+ : ref(1)
+{
+}
+
+SetupGroupList::~SetupGroupList()
+{
+ size_t index = list.size();
+ while(index--)
+ {
+ list[index]->Release();
+ }
+}
+
+SetupGroupList *SetupGroupList::CreateInstance()
+{
+ return new SetupGroupList();
+}
+
+ULONG SetupGroupList::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupGroupList::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+ return r;
+}
+
+BOOL SetupGroupList::AddGroup(SetupGroup *group)
+{
+ if (NULL == group) return FALSE;
+ list.push_back(group);
+ group->AddRef();
+ return TRUE;
+}
+size_t SetupGroupList::GetGroupCount()
+{
+ return list.size();
+}
+
+BOOL SetupGroupList::IsModified()
+{
+ size_t index = list.size();
+ while(index--)
+ {
+ if (list[index]->IsModified())
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL SetupGroupList::FindGroupIndex(SetupGroup *group, size_t *groupIndex)
+{
+ if (NULL == group) return FALSE;
+
+ size_t index = list.size();
+ while(index--)
+ {
+ if (list[index] == group)
+ {
+ if (NULL != groupIndex)
+ *groupIndex = index;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+
+}
+
+HRESULT SetupGroupList::FindGroupById(UINT groupId, SetupGroup **group)
+{
+ if (NULL == group) return E_POINTER;
+
+ size_t index = list.size();
+ while(index--)
+ {
+ if (list[index]->GetId() == groupId)
+ {
+ *group = list[index];
+ (*group)->AddRef();
+ return S_OK;
+ }
+ }
+ return S_FALSE;
+}
+
+size_t SetupGroupList::GetListboxCount()
+{
+ size_t recordCount = list.size();
+ size_t index = recordCount;
+ while(index--)
+ {
+ recordCount += list[index]->GetListboxCount();
+ }
+ return recordCount;
+}
+
+HRESULT SetupGroupList::Save(SetupLog *log)
+{
+ HRESULT hr(S_OK);
+ size_t index = list.size();
+ while(index--)
+ {
+ if (FAILED(list[index]->Save(log)))
+ hr = E_FAIL;
+ }
+ return hr;
+}
+
+HRESULT SetupGroupList::FindListboxItem(size_t listboxId, SetupListboxItem **listboxItem)
+{
+ if (NULL == listboxItem) return E_POINTER;
+
+ size_t index = 0;
+ size_t groupCount = list.size();
+
+ SetupGroup *group;
+ for (size_t i = 0; i < groupCount; i++)
+ {
+ group = list[i];
+ if (index == listboxId)
+ {
+ *listboxItem = (SetupListboxItem*)group;
+ return S_OK;
+ }
+ index++;
+
+ size_t itemCount;
+ if (0 != (itemCount = group->GetListboxCount()))
+ {
+ if (listboxId < (index + itemCount))
+ {
+ size_t itemIndex = (listboxId - index);
+ *listboxItem = group->GetListboxItem(itemIndex);
+ return S_OK;
+ }
+ index += itemCount;
+ }
+ }
+ return E_NOTIMPL;
+}
+
+INT SetupGroupList::GetListboxItem(SetupListboxItem *item)
+{
+ if (NULL == item) return LB_ERR;
+ size_t index = 0;
+ size_t groupCount = list.size();
+ SetupGroup *group;
+ SetupListboxItem *groupItem;
+
+ for (size_t i = 0; i < groupCount; i++)
+ {
+ group = list[i];
+ if (item == group)
+ return (INT)index;
+
+ index++;
+ size_t itemCount = group->GetListboxCount();
+ for (size_t j = 0; j < itemCount; j++)
+ {
+ groupItem = group->GetListboxItem(j);
+ if (groupItem == item)
+ return (INT)index;
+ index++;
+ }
+ }
+ return LB_ERR;
+}
+
+void SetupGroupList::SetPageWnd(HWND hPage)
+{
+ size_t index = list.size();
+ while(index--)
+ {
+ list[index]->SetPageWnd(hPage);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupGroupList.h b/Src/Plugins/Library/ml_online/Setup/setupGroupList.h
new file mode 100644
index 00000000..0b4445cb
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupGroupList.h
@@ -0,0 +1,52 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUPLIST_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUPLIST_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "./setupGroup.h"
+#include <vector>
+
+class SetupListboxItem;
+class SetupLog;
+
+class SetupGroupList
+{
+
+protected:
+ SetupGroupList();
+ ~SetupGroupList();
+
+public:
+ static SetupGroupList *CreateInstance();
+
+public:
+ ULONG AddRef();
+ ULONG Release();
+
+ BOOL AddGroup(SetupGroup *group);
+ SetupGroup *GetGroup(size_t index) { return list[index]; }
+ size_t GetGroupCount();
+ BOOL FindGroupIndex(SetupGroup *group, size_t *groupIndex);
+ HRESULT FindGroupById(UINT groupId, SetupGroup **group);
+
+
+ BOOL IsModified();
+
+ HRESULT Save(SetupLog *log);
+
+ size_t GetListboxCount();
+ INT GetListboxItem(SetupListboxItem *item);
+ HRESULT FindListboxItem(size_t listboxId, SetupListboxItem **listboxItem);
+
+ void SetPageWnd(HWND hPage);
+
+
+protected:
+ ULONG ref;
+ std::vector<SetupGroup*> list;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPGROUPLIST_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupImage.cpp b/Src/Plugins/Library/ml_online/Setup/setupImage.cpp
new file mode 100644
index 00000000..46979043
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupImage.cpp
@@ -0,0 +1,282 @@
+#include "./setupImage.h"
+#include "../api__ml_online.h"
+
+#include <shlwapi.h>
+
+static BOOL SetupImage_CopyImage(HDC hdc, HBITMAP bitmapDst, INT x, INT y, INT cx, INT cy, HBITMAP bitmapSrc, INT srcX, INT srcY)
+{
+ BOOL resultOk = FALSE;
+ HDC hdcDst = CreateCompatibleDC(hdc);
+ HDC hdcSrc = CreateCompatibleDC(hdc);
+
+ if (NULL != hdcDst && NULL != hdcSrc)
+ {
+ HBITMAP bitmapDstOrig = (HBITMAP)SelectObject(hdcDst, bitmapDst);
+ HBITMAP bitmapSrcOrig = (HBITMAP)SelectObject(hdcSrc, bitmapSrc);
+
+ resultOk = BitBlt(hdcDst, x, y, cx, cy, hdcSrc, srcX, srcY, SRCCOPY);
+
+ SelectObject(hdcDst, bitmapDstOrig);
+ SelectObject(hdcSrc, bitmapSrcOrig);
+ }
+
+ if (NULL != hdcDst) DeleteDC(hdcDst);
+ if (NULL != hdcSrc) DeleteDC(hdcSrc);
+
+ return resultOk;
+}
+
+static BOOL SetupImage_ColorizeImage(BYTE *pPixels, LONG x, LONG y, LONG cx, LONG cy, WORD bpp, LONG dstX, LONG dstY, COLORREF rgbBk, COLORREF rgbFg, BOOL removeAlpha)
+{
+ LONG pitch;
+ INT step;
+ BYTE rFg, gFg, bFg;
+ LPBYTE srcCursor, srcLine;
+ LPBYTE dstLine;
+
+ if (bpp < 24) return FALSE;
+
+ step = (bpp>>3);
+ pitch = cx*step;
+ while (pitch%4) pitch++;
+
+ rFg = GetRValue(rgbFg); gFg = GetGValue(rgbFg); bFg = GetBValue(rgbFg);
+
+ INT bK = (bFg - GetBValue(rgbBk));
+ INT gK = (gFg - GetGValue(rgbBk));
+ INT rK = (rFg - GetRValue(rgbBk));
+
+ srcLine = pPixels + pitch * y + x*step;
+ dstLine = pPixels + pitch * dstY + dstX*step;
+
+ if (24 == bpp)
+ {
+ for (; cy-- != 0; srcLine += pitch, dstLine += pitch )
+ {
+ LONG i;
+ LPBYTE dstCursor;
+ for (i = cx, srcCursor = srcLine, dstCursor = dstLine ; i-- != 0; srcCursor += 3, dstCursor +=3)
+ {
+ dstCursor[0] = bFg - (bK*(255 - srcCursor[0])>>8);
+ dstCursor[1] = gFg - (gK*(255 - srcCursor[1])>>8);
+ dstCursor[2] = rFg - (rK*(255 - srcCursor[2])>>8);
+ }
+ }
+ }
+ else
+ {
+ // nothing for now
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+SetupImage::SetupImage(HDC hdc, HBITMAP bitmapSource, INT maxColors)
+ : ref(1), bitmap(NULL), pixels(NULL),
+ table(NULL), tableSize(0), tableCount(0), insertCursor(0), readCursor(0)
+{
+ ZeroMemory(&header, sizeof(BITMAPINFOHEADER));
+
+ BITMAP bm;
+ if (sizeof(BITMAP) != GetObject(bitmapSource, sizeof(BITMAP), &bm))
+ return;
+
+ if (bm.bmHeight < 0)
+ bm.bmHeight = -bm.bmHeight;
+
+ header.biSize = sizeof(BITMAPINFOHEADER);
+ header.biCompression = BI_RGB;
+ header.biBitCount = 24;
+ header.biPlanes = 1;
+ header.biWidth = bm.bmWidth;
+ header.biHeight = -(bm.bmHeight * (maxColors + 1));
+
+ bitmap = CreateDIBSection(hdc, (LPBITMAPINFO)&header, DIB_RGB_COLORS, (void**)&pixels, NULL, 0);
+ if (NULL == bitmap)
+ return;
+
+ if (FALSE == SetupImage_CopyImage(hdc, bitmap, 0, 0, bm.bmWidth, bm.bmHeight, bitmapSource, 0, 0))
+ {
+ DeleteObject(bitmap);
+ bitmap = NULL;
+ return;
+ }
+
+ tableSize = maxColors;
+ table = (IMAGEINDEX*)calloc(tableSize, sizeof(IMAGEINDEX));
+ if (NULL == table)
+ {
+ DeleteObject(bitmap);
+ bitmap = NULL;
+ return;
+ }
+}
+
+SetupImage::~SetupImage()
+{
+ if (NULL != bitmap)
+ {
+ DeleteObject(bitmap);
+ bitmap = NULL;
+ }
+
+ if (NULL != table)
+ {
+ free(table);
+ table = NULL;
+ }
+}
+
+SetupImage *SetupImage::CreateInstance(HDC hdc, HBITMAP bitmapSource, INT maxColors)
+{
+ if (NULL == bitmapSource || maxColors < 1 || maxColors > 120)
+ return NULL;
+
+ SetupImage *instance = new SetupImage(hdc, bitmapSource, maxColors);
+ if (NULL == instance) return NULL;
+ if (NULL == instance->bitmap || NULL == instance->table)
+ {
+ instance->Release();
+ instance = NULL;
+ }
+ return instance;
+}
+
+SetupImage *SetupImage::CreateFromPluginBitmap(HDC hdc, LPCWSTR pszModuleName, LPCWSTR resourceName, INT maxColors)
+{
+ SetupImage *instance = NULL;
+ WCHAR szPath[MAX_PATH] = {0};
+
+ if (0 != GetModuleFileName(WASABI_API_ORIG_HINST, szPath, ARRAYSIZE(szPath)))
+ {
+ PathRemoveFileSpec(szPath);
+ PathAppend(szPath, pszModuleName);
+ HMODULE hModule = LoadLibraryEx(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE | 0x00000020/*LOAD_LIBRARY_AS_IMAGE_RESOURCE*/);
+ if (NULL != hModule)
+ {
+ HBITMAP bitmapSource = (HBITMAP)LoadImage(hModule, resourceName, IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
+ if (NULL != bitmapSource)
+ instance = CreateInstance(hdc, bitmapSource, maxColors);
+ FreeLibrary(hModule);
+ }
+ }
+ return instance;
+}
+
+ULONG SetupImage::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupImage::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+BOOL SetupImage::GetSize(SIZE *pSize)
+{
+ if (NULL == pSize) return FALSE;
+
+ INT cy = header.biHeight;
+ if (cy < 0) cy = -cy;
+
+ pSize->cx = header.biWidth;
+ pSize->cy = cy / (tableSize + 1);
+ return TRUE;
+}
+
+BOOL SetupImage::DrawImage(HDC hdc, INT x, INT y, INT cx, INT cy, INT srcX, INT srcY, COLORREF rgbBk, COLORREF rgbFg)
+{
+ BYTE bitmapIndex = 0xFF;
+
+ SIZE imageSize;
+ if (!GetSize(&imageSize))
+ return FALSE;
+
+ for(BYTE i = readCursor; i < tableCount; i++)
+ {
+ if (table[i].rgbBk == rgbBk && table[i].rgbFg == rgbFg)
+ {
+ bitmapIndex = i;
+ break;
+ }
+ }
+
+ if (0xFF == bitmapIndex)
+ {
+ if (readCursor > tableCount)
+ readCursor = tableCount;
+ for(BYTE i = 0; i < readCursor; i++)
+ {
+ if (table[i].rgbBk == rgbBk && table[i].rgbFg == rgbFg)
+ {
+ bitmapIndex = i;
+ break;
+ }
+ }
+
+ if (0xFF == bitmapIndex)
+ {
+ if (tableCount < tableSize)
+ {
+ insertCursor = tableCount;
+ tableCount++;
+ }
+ else if (++insertCursor == tableCount)
+ insertCursor = 0;
+
+ INT targetY = (insertCursor + 1) * imageSize.cy;
+
+ if (!SetupImage_ColorizeImage(pixels, 0, 0, imageSize.cx, imageSize.cy,
+ header.biBitCount, 0, targetY, rgbBk, rgbFg, TRUE))
+ {
+ return FALSE;
+ }
+ table[insertCursor].rgbBk = rgbBk;
+ table[insertCursor].rgbFg = rgbFg;
+ bitmapIndex = insertCursor;
+
+ }
+ }
+
+ readCursor = bitmapIndex;
+ srcY += ((bitmapIndex + 1) * imageSize.cy);
+
+ INT dstY = y;
+ INT dstCY = cy;
+
+ INT imageHeight = header.biHeight;
+ if (imageHeight < 0)
+ {
+ header.biHeight = -imageHeight;
+ dstY += (cy - 1);
+ dstCY = -cy;
+ }
+
+ BOOL resultOk = StretchDIBits(hdc, x, dstY, cx, dstCY, srcX, srcY, cx, cy,
+ pixels, (BITMAPINFO*)&header, DIB_RGB_COLORS, SRCCOPY);
+
+ if (imageHeight < 0)
+ header.biHeight = imageHeight;
+
+ return resultOk;
+}
+
+BOOL SetupImage::ResetCache()
+{
+ if (NULL != table)
+ ZeroMemory(&table, sizeof(IMAGEINDEX) * tableSize);
+ tableCount = 0;
+ insertCursor = 0;
+ readCursor = 0;
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupImage.h b/Src/Plugins/Library/ml_online/Setup/setupImage.h
new file mode 100644
index 00000000..893e2c93
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupImage.h
@@ -0,0 +1,50 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPIMAGE_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPIMAGE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+class SetupImage
+{
+private:
+ typedef struct __IMAGEINDEX
+ {
+ COLORREF rgbBk;
+ COLORREF rgbFg;
+ } IMAGEINDEX;
+
+protected:
+ SetupImage(HDC hdc, HBITMAP bitmapSource, INT maxColors);
+ ~SetupImage();
+
+public:
+ static SetupImage *CreateInstance(HDC hdc, HBITMAP bitmapSource, INT maxColors);
+ static SetupImage *CreateFromPluginBitmap(HDC hdc, LPCWSTR pszModuleName, LPCWSTR resourceName, INT maxColors);
+
+public:
+ ULONG AddRef();
+ ULONG Release();
+
+ BOOL GetSize(SIZE *pSize);
+ BOOL DrawImage(HDC hdc, INT x, INT y, INT cx, INT cy, INT srcX, INT srcY, COLORREF rgbBk, COLORREF rgbFg);
+
+ BOOL ResetCache();
+
+private:
+ ULONG ref;
+ HBITMAP bitmap;
+ BYTE *pixels;
+ BITMAPINFOHEADER header;
+
+
+ IMAGEINDEX *table;
+ BYTE tableSize;
+ BYTE tableCount;
+ BYTE insertCursor;
+ BYTE readCursor;
+};
+
+#endif // NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPIMAGE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupListbox.cpp b/Src/Plugins/Library/ml_online/Setup/setupListbox.cpp
new file mode 100644
index 00000000..1035c699
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupListbox.cpp
@@ -0,0 +1,878 @@
+#include "./setupPage.h"
+#include "./setupListbox.h"
+#include "./setupGroupList.h"
+#include "./setupImage.h"
+#include "../common.h"
+#include "../resource.h"
+#include "../api__ml_online.h"
+
+#include "../../nu/windowsTheme.h"
+
+#include <vssym32.h>
+
+//#include <tmschema.h>
+
+static ATOM SERVICELIST_PROP = 0;
+
+#define SLF_UNICODE 0x0001
+#define SLF_DRAGMOVE 0x0002
+
+typedef BOOL (SetupListboxItem::*ITEMMOUSEPROC)(SetupListbox*, const RECT*, UINT, POINT);
+
+class Listbox : public SetupListbox
+{
+
+protected:
+ Listbox(HWND hListbox, SetupGroupList *groupList);
+ ~Listbox();
+
+public:
+ static HRESULT AttachToWindow(HWND hwndListbox, SetupGroupList *groupList, SetupListbox **pInstance);
+
+public:
+ HWND GetHwnd() { return hwnd; }
+ HFONT GetFont() { return (HFONT)::SendMessage(hwnd, WM_GETFONT, 0, 0L); }
+ LRESULT CallDefaultProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+ LRESULT CallPrevProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+ BOOL MeasureItem(INT itemId, UINT *cx, UINT *cy);
+ BOOL DrawItem(HDC hdc, const RECT *itemRect, INT itemId, UINT itemState, UINT itemAction);
+ INT_PTR KeyToItem(INT vKey, INT caretPos);
+ INT_PTR CharToItem(INT vKey, INT caretPos);
+
+ BOOL DrawCheckbox(HDC hdc, BOOL checked, UINT state, const RECT *pRect, const RECT *pClipRect);
+ BOOL GetCheckboxMetrics(HDC hdc, BOOL checked, UINT state, SIZE *pSize);
+
+ INT GetCheckboxThemeState(BOOL checked, UINT state);
+ INT HitTest(POINT pt, RECT *prcItem);
+
+ void SetCapture(SetupListboxItem *item);
+ SetupListboxItem *GetCapture();
+ void ReleaseCapture();
+ BOOL InvalidateRect(const RECT *prcInvalidate, BOOL fErase);
+ BOOL InvalidateItem(INT itemId, BOOL fErase);
+ void UpdateCount();
+
+ BOOL DrawExpandbox(HDC hdc, BOOL fExpanded, const RECT *pRect, COLORREF rgbBk, COLORREF rgbFg);
+ BOOL GetExpandboxMetrics(HDC hdc, BOOL fExpanded, SIZE *pSize);
+
+ INT GetPageCount();
+ INT GetNextEnabledItem(INT iItem, SetupListboxItem **itemOut);
+ INT GetPrevEnabledItem(INT iItem, SetupListboxItem **itemOut);
+
+ SetupListboxItem *GetSelection();
+ BOOL SetSelection(SetupListboxItem *item);
+ BOOL GetIndex(SetupListboxItem *item, INT *iItem);
+
+ BOOL DoDragAndDrop(UINT mouseEvent, UINT mouseFlags, POINT pt);
+ HMENU GetContextMenu(UINT menuId);
+
+protected:
+ friend static LRESULT WINAPI Listbox_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+ void OnDestroy();
+ void GetItemRect(INT itemId, RECT *prcItem);
+
+ SetupImage *GetExpandboxImage(HDC hdc, BOOL fExpanded);
+
+ void OnMouseEvent(UINT mouseEvent, UINT mouseFlags, POINTS pts, BOOL fDefaultHandler, ITEMMOUSEPROC proc);
+ void OnMouseLeave();
+ void OnEraseBkGround(HDC hdc);
+ void OnCaptureChanged(HWND hwndGained);
+ void NotifyReleaseCapture(INT itemId, SetupListboxItem *itemGain);
+ void OnCommand(INT commandId, INT eventId, HWND hControl);
+
+protected:
+ HWND hwnd;
+ UINT flags;
+ WNDPROC originalProc;
+ UXTHEME buttonTheme;
+ SetupGroupList *groups;
+ INT mouseoverId;
+ INT capturedId;
+ SetupImage *expandedImage;
+ SetupImage *collapsedImage;
+};
+
+#define GetList(__hwnd) ((Listbox*)GetPropW((__hwnd), MAKEINTATOM(SERVICELIST_PROP)))
+
+
+
+
+HRESULT SetupListbox::CreateInstance(HWND hListbox, SetupGroupList *groupList, SetupListbox **pInstance)
+{
+ return Listbox::AttachToWindow(hListbox, groupList, pInstance);
+}
+
+SetupListbox *SetupListbox::GetInstance(HWND hListbox)
+{
+ return GetList(hListbox);
+}
+
+Listbox::Listbox(HWND hListbox, SetupGroupList *groupList)
+ : hwnd(hListbox), flags(0), originalProc(NULL), buttonTheme(NULL), groups(groupList),
+ mouseoverId(LB_ERR), capturedId(LB_ERR), expandedImage(NULL), collapsedImage(NULL)
+{
+ if (IsWindowUnicode(hwnd))
+ flags |= SLF_UNICODE;
+
+ buttonTheme = (UxIsAppThemed()) ? UxOpenThemeData(hwnd, L"Button") : NULL;
+
+ groups->AddRef();
+ UpdateCount();
+
+}
+
+Listbox::~Listbox()
+{
+ if (NULL != hwnd)
+ {
+ RemoveProp(hwnd, MAKEINTATOM(SERVICELIST_PROP));
+ if (NULL != originalProc)
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)originalProc);
+ CallPrevProc(WM_DESTROY, 0, 0L);
+ }
+ }
+
+ if (NULL != groups)
+ groups->Release();
+
+ if (NULL != buttonTheme)
+ UxCloseThemeData(buttonTheme);
+
+ if (NULL != expandedImage)
+ expandedImage->Release();
+
+ if (NULL != collapsedImage)
+ collapsedImage->Release();
+}
+
+HRESULT Listbox::AttachToWindow(HWND hListbox, SetupGroupList *groupList, SetupListbox **pInstance)
+{
+ if (0 == SERVICELIST_PROP)
+ {
+ SERVICELIST_PROP = GlobalAddAtom(TEXT("omSetupListbox"));
+ if (0 == SERVICELIST_PROP) return E_UNEXPECTED;
+ }
+
+
+ if(NULL == hListbox || !IsWindow(hListbox) || NULL == groupList)
+ return E_INVALIDARG;
+
+ Listbox *list = new Listbox(hListbox, groupList);
+ if (NULL == list)
+ return E_OUTOFMEMORY;
+
+ list->originalProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(hListbox, GWLP_WNDPROC,
+ (LONGX86)(LONG_PTR)Listbox_WindowProc);
+
+ if (NULL == list->originalProc || !SetProp(hListbox, MAKEINTATOM(SERVICELIST_PROP), list))
+ {
+ if (NULL != list->originalProc)
+ SetWindowLongPtr(hListbox, GWLP_WNDPROC, (LONGX86)(LONG_PTR)list->originalProc);
+ delete(list);
+ return E_FAIL;
+ }
+
+
+ if (NULL != pInstance)
+ *pInstance = list;
+
+ return S_OK;
+}
+
+LRESULT Listbox::CallDefaultProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return (0 != (SLF_UNICODE & flags)) ?
+ DefWindowProcW(hwnd, uMsg, wParam, lParam) :
+ DefWindowProcA(hwnd, uMsg, wParam, lParam);
+}
+
+LRESULT Listbox::CallPrevProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return (0 != (SLF_UNICODE & flags)) ?
+ CallWindowProcW(originalProc, hwnd, uMsg, wParam, lParam) :
+ CallWindowProcA(originalProc, hwnd, uMsg, wParam, lParam);
+}
+
+
+void Listbox::UpdateCount()
+{
+ SendMessage(hwnd, WM_SETREDRAW, FALSE, 0L);
+
+ INT iSelected = (INT)SendMessage(hwnd, LB_GETCURSEL, 0, 0L);
+
+ size_t recordCount = (NULL != groups) ? groups->GetListboxCount() : 0;
+ SendMessage(hwnd, LB_SETCOUNT, (WPARAM)recordCount, 0L);
+
+ SetupListboxItem *item;
+ UINT cy, maxCY = 0;
+ for (size_t i = 0; i < recordCount; i++)
+ {
+ if (SUCCEEDED(groups->FindListboxItem(i, &item)) &&
+ item->MeasureItem(this, NULL, &cy) && cy > maxCY)
+ {
+ maxCY = cy;
+ }
+ }
+
+ SendMessage(hwnd, LB_SETITEMHEIGHT, (WPARAM)0, (LPARAM)maxCY);
+
+ if (recordCount > 0)
+ {
+ if (iSelected < 0)
+ iSelected = 0;
+
+ if ((size_t)iSelected >= recordCount)
+ iSelected = (INT)(recordCount - 1);
+
+ SendMessage(hwnd, LB_SETCARETINDEX, (WPARAM)iSelected, TRUE);
+ SendMessage(hwnd, LB_SETANCHORINDEX, (WPARAM)iSelected, 0L);
+ SendMessage(hwnd, LB_SETCURSEL, (WPARAM)iSelected, 0L);
+ }
+
+ SendMessage(hwnd, WM_SETREDRAW, TRUE, 0L);
+}
+
+BOOL Listbox::MeasureItem(INT itemId, UINT *cx, UINT *cy)
+{
+ SetupListboxItem *item;
+ HRESULT hr = groups->FindListboxItem(itemId, &item);
+ return (SUCCEEDED(hr)) ? item->MeasureItem(this, cx, cy) : FALSE;
+}
+
+BOOL Listbox::DrawItem(HDC hdc, const RECT *prcItem, INT itemId, UINT itemState, UINT itemAction)
+{
+ SetupListboxItem *item;
+ HRESULT hr = groups->FindListboxItem(itemId, &item);
+ if (FAILED(hr)) return FALSE;
+
+ if (0 != (ODS_SELECTED & itemState) && GetFocus() != hwnd)
+ itemState |= ODS_INACTIVE;
+
+ if (item->IsDisabled())
+ itemState |= ODS_DISABLED;
+
+ return item->DrawItem(this, hdc, prcItem, itemState);
+}
+
+INT Listbox::GetPageCount()
+{
+ RECT clientRect;
+ if (NULL == hwnd || !GetClientRect(hwnd, &clientRect))
+ return 0;
+
+ INT itemHeight = (INT)SendMessage(hwnd, LB_GETITEMHEIGHT, 0, 0L);
+ return (clientRect.bottom - clientRect.top) / itemHeight;
+}
+
+INT Listbox::GetNextEnabledItem(INT iItem, SetupListboxItem **itemOut)
+{
+ INT iLast = (INT)SendMessage(hwnd, LB_GETCOUNT, 0, 0L);
+ if (iLast >= 0) iLast--;
+
+ SetupListboxItem *testItem;
+ while(iItem++ < iLast)
+ {
+ if (SUCCEEDED(groups->FindListboxItem(iItem, &testItem)))
+ {
+ if (FALSE == testItem->IsDisabled()) break;
+ }
+ }
+ if (NULL != itemOut)
+ {
+ *itemOut = (iItem <= iLast) ? testItem : NULL;
+ }
+ return (iItem <= iLast) ? iItem : LB_ERR;
+}
+
+INT Listbox::GetPrevEnabledItem(INT iItem, SetupListboxItem **itemOut)
+{
+ SetupListboxItem *testItem;
+ while(iItem-- > 0)
+ {
+ if (SUCCEEDED(groups->FindListboxItem(iItem, &testItem)))
+ {
+ if (FALSE == testItem->IsDisabled()) break;
+ }
+ }
+
+ if (NULL != itemOut)
+ {
+ *itemOut = (iItem >= 0) ? testItem : NULL;
+ }
+ return (iItem >= 0) ? iItem : LB_ERR;
+}
+
+SetupListboxItem *Listbox::GetSelection()
+{
+ INT iSelected = (INT)SendMessage(hwnd, LB_GETCURSEL, 0, 0L);
+ if (LB_ERR == iSelected)
+ return NULL;
+
+ SetupListboxItem *item;
+ return (SUCCEEDED(groups->FindListboxItem(iSelected, &item))) ? item : NULL;
+}
+
+BOOL Listbox::SetSelection(SetupListboxItem *item)
+{
+ INT iItem = LB_ERR;
+ if (NULL != item)
+ {
+ iItem = groups->GetListboxItem(item);
+ if (LB_ERR == iItem)
+ return FALSE;
+ }
+
+ BOOL resultOk = (LB_ERR != SendMessage(hwnd, LB_SETCURSEL, (WPARAM)iItem, 0L));
+ if (LB_ERR == iItem) resultOk = TRUE;
+
+ if (LB_ERR != iItem)
+ {
+ HWND hParent = GetParent(hwnd);
+ if (NULL != hParent)
+ SendMessage(hParent, WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwnd), LBN_SELCHANGE), (LPARAM)hwnd);
+ }
+ return resultOk;
+}
+
+BOOL Listbox::GetIndex(SetupListboxItem *item, INT *iItem)
+{
+ if (NULL == iItem)
+ return FALSE;
+
+ *iItem = (NULL != item && NULL != groups) ? groups->GetListboxItem(item) : LB_ERR;
+ return (LB_ERR != *iItem);
+}
+
+INT_PTR Listbox::KeyToItem(INT vKey, INT iCaret)
+{
+ SetupListboxItem *item;
+ HRESULT hr = groups->FindListboxItem(iCaret, &item);
+ if (FAILED(hr)) return -1;
+
+ RECT itemRect;
+ GetItemRect(iCaret, &itemRect);
+ INT_PTR result = item->KeyToItem(this, &itemRect, vKey);
+ if (-1 != result) return result;
+
+ INT iTarget, iCount;
+
+ switch(vKey)
+ {
+ case VK_UP:
+ case VK_LEFT:
+ iTarget = GetPrevEnabledItem(iCaret, NULL);
+ if (LB_ERR != iTarget) return iTarget;
+
+ SendMessage(hwnd, LB_SETTOPINDEX, (WPARAM)0, 0L);
+ SendMessage(hwnd, LB_SETCARETINDEX, (WPARAM)iCaret, FALSE);
+ return -2;
+
+ case VK_DOWN:
+ case VK_RIGHT:
+ iTarget = GetNextEnabledItem(iCaret, NULL);
+ if (LB_ERR != iTarget) return iTarget;
+
+ SendMessage(hwnd, LB_SETTOPINDEX, (WPARAM)iCaret, 0L);
+ return -2;
+
+ case VK_HOME:
+ if (iCaret > 0)
+ {
+ SendMessage(hwnd, LB_SETTOPINDEX, (WPARAM)0, 0L);
+ iTarget = GetNextEnabledItem(-1, NULL);
+ if (iTarget >= iCaret) iTarget = LB_ERR;
+ if (LB_ERR != iTarget) return iTarget;
+ SendMessage(hwnd, LB_SETCARETINDEX, (WPARAM)iCaret, FALSE);
+ }
+ return -2;
+
+ case VK_PRIOR:
+ if (iCaret > 0)
+ {
+ INT iTop = (INT)SendMessage(hwnd, LB_GETTOPINDEX, 0, 0L);
+ if (iTop == iCaret)
+ {
+ INT iPage = iCaret - GetPageCount() + 1;
+ iTop = (iPage <= 0) ? 0 : (iPage - 1);
+ }
+
+ iTarget = GetPrevEnabledItem(iTop + 1, NULL);
+
+ if (LB_ERR == iTarget)
+ {
+ SendMessage(hwnd, LB_SETTOPINDEX, (WPARAM)0, 0L);
+ iTarget = GetNextEnabledItem(iTop, NULL);
+ if (iTarget > iCaret) iTarget = LB_ERR;
+ }
+
+ if (LB_ERR != iTarget) return iTarget;
+ SendMessage(hwnd, LB_SETCARETINDEX, (WPARAM)iCaret, FALSE);
+ }
+ return -2;
+
+ case VK_END:
+ iCount = (INT)SendMessage(hwnd, LB_GETCOUNT, 0, 0L);
+ if (iCount > 0 && iCaret != (iCount - 1))
+ {
+ SendMessage(hwnd, LB_SETTOPINDEX, (WPARAM)(iCount - 1), 0L);
+ iTarget = GetPrevEnabledItem(iCount, NULL);
+ if (iTarget <= iCaret) iTarget = LB_ERR;
+ if (LB_ERR != iTarget) return iTarget;
+ SendMessage(hwnd, LB_SETCARETINDEX, (WPARAM)iCaret, FALSE);
+ }
+ return -2;
+
+ case VK_NEXT:
+ iCount = (INT)SendMessage(hwnd, LB_GETCOUNT, 0, 0L);
+ if (iCount > 0 && iCaret != (iCount - 1))
+ {
+ INT iPage = GetPageCount();
+ INT iBottom = (INT)SendMessage(hwnd, LB_GETTOPINDEX, 0, 0L) + iPage - 1;
+ if (iBottom == iCaret)
+ {
+ iBottom += iPage;
+ if (iBottom >= iCount) iBottom = iCount -1;
+ }
+ iTarget = GetNextEnabledItem(iBottom - 1, NULL);
+ if (LB_ERR == iTarget)
+ {
+ SendMessage(hwnd, LB_SETTOPINDEX, (WPARAM)(iCount - 1), 0L);
+ iTarget = GetPrevEnabledItem(iBottom, NULL);
+ if (iTarget < iCaret) iTarget = LB_ERR;
+ }
+
+ if (LB_ERR != iTarget) return iTarget;
+ SendMessage(hwnd, LB_SETCARETINDEX, (WPARAM)iCaret, FALSE);
+ }
+ return -2;
+ }
+
+ return result;
+}
+
+INT_PTR Listbox::CharToItem(INT vKey, INT caretPos)
+{
+ return -2;
+ //SetupListboxItem *item;
+ //HRESULT hr = groups->FindListboxItem(caretPos, &item);
+ //return (SUCCEEDED(hr)) ? item->CharToItem(this, vKey) : -1;
+}
+
+INT Listbox::GetCheckboxThemeState(BOOL checked, UINT state)
+{
+ if (FALSE != checked)
+ {
+ if (0 != (ODS_DISABLED & state)) return CBS_CHECKEDDISABLED;
+ if (0 != (ODS_HOTLIGHT & state)) return CBS_CHECKEDHOT;
+ if (0 != (ODS_SELECTED & state)) return CBS_CHECKEDPRESSED;
+ return CBS_CHECKEDNORMAL;
+ }
+
+ if (0 != (ODS_DISABLED & state)) return CBS_UNCHECKEDDISABLED;
+ if (0 != (ODS_HOTLIGHT & state)) return CBS_UNCHECKEDHOT;
+ if (0 != (ODS_SELECTED & state)) return CBS_UNCHECKEDPRESSED;
+ return CBS_UNCHECKEDNORMAL;
+}
+
+BOOL Listbox::DrawCheckbox(HDC hdc, BOOL checked, UINT state, const RECT *pRect, const RECT *pClipRect)
+{
+ if (NULL != buttonTheme)
+ {
+ INT stateId = GetCheckboxThemeState(checked, state);
+ if (SUCCEEDED(UxDrawThemeBackground(buttonTheme, hdc, BP_CHECKBOX, stateId, pRect, pClipRect)))
+ return TRUE;
+ }
+
+ UINT stateId = DFCS_BUTTONCHECK;
+ if (FALSE != checked) stateId |= DFCS_CHECKED;
+ if (0 != (ODS_DISABLED & state)) stateId |= DFCS_INACTIVE;
+ if (0 != (ODS_HOTLIGHT & state)) stateId |= DFCS_HOT;
+ if (0 != (ODS_SELECTED & state)) stateId |= DFCS_PUSHED;
+
+ return DrawFrameControl(hdc, (LPRECT)pRect,DFC_BUTTON, stateId);
+}
+
+BOOL Listbox::GetCheckboxMetrics(HDC hdc, BOOL checked, UINT state, SIZE *pSize)
+{
+ if (NULL != buttonTheme)
+ {
+ HDC hdcMine = NULL;
+
+ if (NULL == hdc)
+ {
+ hdcMine = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ hdc = hdcMine;
+ }
+
+ INT stateId = GetCheckboxThemeState(checked, state);
+ HRESULT hr = UxGetThemePartSize(buttonTheme, hdc, BP_CHECKBOX,
+ stateId, NULL, TS_DRAW, pSize);
+
+ if (NULL != hdcMine)
+ ReleaseDC(hwnd, hdcMine);
+ if (SUCCEEDED(hr)) return TRUE;
+ }
+ pSize->cx = 13;
+ pSize->cy = 13;
+ return TRUE;
+}
+
+void Listbox::OnDestroy()
+{
+ delete(this);
+}
+
+
+INT Listbox::HitTest(POINT pt, RECT *prcItem)
+{
+ RECT itemRect;
+ INT itemId = (INT)SendMessage(hwnd, LB_ITEMFROMPOINT, 0, MAKELONG(pt.x, pt.y));
+
+ if (LB_ERR == itemId ||
+ LB_ERR == SendMessage(hwnd, LB_GETITEMRECT, (WPARAM)itemId, (LPARAM)&itemRect) ||
+ FALSE == PtInRect(&itemRect, pt))
+ {
+ return LB_ERR;
+ }
+
+ if (NULL != prcItem)
+ CopyRect(prcItem, &itemRect);
+
+ return itemId;
+}
+
+void Listbox::GetItemRect(INT itemId, RECT *prcItem)
+{
+ if (LB_ERR == ::SendMessage(hwnd, LB_GETITEMRECT, (WPARAM)itemId, (LPARAM)prcItem))
+ SetRectEmpty(prcItem);
+}
+
+BOOL Listbox::DoDragAndDrop(UINT mouseEvent, UINT mouseFlags, POINT pt)
+{
+ if (WM_MOUSEMOVE == mouseEvent &&
+ 0 != ((MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) & mouseFlags))
+ {
+ flags |= SLF_DRAGMOVE;
+ }
+ else
+ {
+ flags &= ~SLF_DRAGMOVE;
+ }
+ return (0 != (SLF_DRAGMOVE & flags));
+}
+
+void Listbox::OnMouseEvent(UINT mouseEvent, UINT mouseFlags, POINTS pts, BOOL fDefaultHandler, ITEMMOUSEPROC proc)
+{
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ SetupListboxItem *item;
+ RECT itemRect;
+
+ if (LB_ERR != capturedId)
+ {
+ if (SUCCEEDED(groups->FindListboxItem(capturedId, &item)))
+ {
+ GetItemRect(capturedId, &itemRect);
+ if (FALSE == ((item->*proc)(this, &itemRect, mouseFlags, pt)))
+ {
+ CallPrevProc(mouseEvent, (WPARAM)mouseFlags, *((LPARAM*)&pts));
+ }
+ return;
+ }
+ capturedId = LB_ERR;
+ }
+
+ if (DoDragAndDrop(mouseEvent, mouseFlags, pt))
+ return;
+
+ INT itemId = HitTest(pt, &itemRect);
+ if (mouseoverId != itemId)
+ {
+ if (SUCCEEDED(groups->FindListboxItem(mouseoverId, &item)))
+ {
+ RECT leaveRect;
+ GetItemRect(mouseoverId, &leaveRect);
+ item->MouseLeave(this, &leaveRect);
+ }
+ mouseoverId = itemId;
+
+ TRACKMOUSEEVENT tm;
+ tm.cbSize = sizeof(TRACKMOUSEEVENT);
+ tm.hwndTrack = hwnd;
+ tm.dwFlags = TME_LEAVE;
+ if (LB_ERR == mouseoverId)
+ tm.dwFlags |= TME_CANCEL;
+ TrackMouseEvent(&tm);
+ }
+
+ if (LB_ERR != mouseoverId)
+ {
+ if (SUCCEEDED(groups->FindListboxItem(mouseoverId, &item)))
+ {
+ BOOL callListbox = FALSE;
+ if (FALSE != item->IsDisabled())
+ {
+ switch(mouseEvent)
+ {
+ case WM_LBUTTONUP:
+ case WM_RBUTTONUP:
+ case WM_MBUTTONUP:
+ case 0x020C /*WM_XBUTTONUP*/:
+ callListbox = TRUE;
+ break;
+ }
+ }
+ else
+ {
+ if (FALSE == ((item->*proc)(this, &itemRect, mouseFlags, pt)))
+ {
+ callListbox = TRUE;
+ }
+ }
+
+ if (FALSE != callListbox)
+ {
+ CallPrevProc(mouseEvent, (WPARAM)mouseFlags, *((LPARAM*)&pts));
+ }
+ return;
+ }
+ }
+
+ if (FALSE != fDefaultHandler)
+ {
+ CallPrevProc(mouseEvent, (WPARAM)mouseFlags, *((LPARAM*)&pts));
+ }
+}
+
+void Listbox::OnMouseLeave()
+{
+ if (LB_ERR != mouseoverId)
+ {
+ SetupListboxItem *item;
+ INT itemId = mouseoverId;
+ mouseoverId = LB_ERR;
+ if (SUCCEEDED(groups->FindListboxItem(itemId, &item)))
+ {
+ RECT itemRect;
+ GetItemRect(itemId, &itemRect);
+ if (item->MouseLeave(this, &itemRect))
+ return;
+ }
+ }
+ CallPrevProc(WM_MOUSELEAVE, 0, 0L);
+}
+
+void Listbox::SetCapture(SetupListboxItem *item)
+{
+ INT prevCapturedId = capturedId;
+ capturedId = (NULL != item) ? groups->GetListboxItem(item) : LB_ERR;
+
+ NotifyReleaseCapture(prevCapturedId, item);
+
+ if (LB_ERR != capturedId && ::GetCapture() != hwnd)
+ ::SetCapture(hwnd);
+}
+
+SetupListboxItem *Listbox::GetCapture()
+{
+ SetupListboxItem *capturedItem;
+ if (LB_ERR == capturedId || FAILED(groups->FindListboxItem(capturedId, &capturedItem)))
+ capturedItem = NULL;
+
+ return capturedItem;
+}
+
+void Listbox::ReleaseCapture()
+{
+ if (LB_ERR != capturedId)
+ {
+ INT prevCapturedId = capturedId;
+ capturedId = LB_ERR;
+
+ NotifyReleaseCapture(prevCapturedId, NULL);
+ if (::GetCapture() == hwnd)
+ ::ReleaseCapture();
+ }
+}
+
+void Listbox::NotifyReleaseCapture(INT itemId, SetupListboxItem *itemGain)
+{
+ if (LB_ERR == itemId)
+ return;
+
+ SetupListboxItem *item;
+ if (SUCCEEDED(groups->FindListboxItem(itemId, &item)))
+ {
+ RECT itemRect;
+ GetItemRect(itemId, &itemRect);
+ item->CaptureChanged(this, &itemRect, itemGain);
+ }
+}
+
+void Listbox::OnCaptureChanged(HWND hwndGained)
+{
+ if (hwnd != hwndGained && LB_ERR != capturedId)
+ {
+ INT prevCapturedId = capturedId;
+ capturedId = LB_ERR;
+ NotifyReleaseCapture(prevCapturedId, NULL);
+ }
+}
+
+BOOL Listbox::InvalidateRect(const RECT *prcInvalidate, BOOL fErase)
+{
+ return ::InvalidateRect(hwnd, prcInvalidate, fErase);
+}
+
+BOOL Listbox::InvalidateItem(INT itemId, BOOL fErase)
+{
+ if (itemId < 0) return FALSE;
+
+ RECT itemRect;
+ if (LB_ERR == SendMessage(hwnd, LB_GETITEMRECT, (WPARAM)itemId, (LPARAM)&itemRect))
+ return FALSE;
+
+ return ::InvalidateRect(hwnd, &itemRect, fErase);
+}
+
+void Listbox::OnEraseBkGround(HDC hdc)
+{
+ INT iCount = (INT)SendMessage(hwnd, LB_GETCOUNT, 0, 0L);
+ RECT clientRect, itemRect;
+
+ GetClientRect(hwnd, &clientRect);
+ if (iCount > 0 &&
+ LB_ERR != SendMessage(hwnd, LB_GETITEMRECT, (WPARAM)(iCount - 1), (LPARAM)&itemRect))
+ {
+ clientRect.top = itemRect.top;
+ }
+
+ if (clientRect.top < clientRect.bottom)
+ {
+ HBRUSH hb = NULL;
+ HWND hParent = GetParent(hwnd);
+ if (NULL != hParent)
+ {
+ hb = (HBRUSH)SendMessage(hParent, WM_CTLCOLORLISTBOX, (WPARAM)hdc, (LPARAM)hwnd);
+ }
+ if (NULL == hb)
+ {
+ hb = GetSysColorBrush(COLOR_WINDOW);
+ }
+ FillRect(hdc, &clientRect, hb);
+ }
+}
+
+void Listbox::OnCommand(INT commandId, INT eventId, HWND hControl)
+{
+ if (NULL == hControl)
+ {
+ SetupListboxItem *item = GetSelection();
+ if (NULL != item)
+ {
+ item->Command(this, commandId, eventId);
+ }
+
+ }
+}
+SetupImage *Listbox::GetExpandboxImage(HDC hdc, BOOL fExpanded)
+{
+ if (fExpanded)
+ {
+ if (NULL == expandedImage)
+ expandedImage = SetupImage::CreateFromPluginBitmap(hdc, L"gen_ml.dll", MAKEINTRESOURCE(137), 3);
+ return expandedImage;
+ }
+
+ if (NULL == collapsedImage)
+ collapsedImage = SetupImage::CreateFromPluginBitmap(hdc, L"gen_ml.dll", MAKEINTRESOURCE(135), 3);
+ return collapsedImage;
+}
+
+BOOL Listbox::DrawExpandbox(HDC hdc, BOOL fExpanded, const RECT *pRect, COLORREF rgbBk, COLORREF rgbFg)
+{
+ SetupImage *image = GetExpandboxImage(hdc, fExpanded);
+ return (NULL != image) ?
+ image->DrawImage(hdc, pRect->left, pRect->top,
+ pRect->right - pRect->left, pRect->bottom- pRect->top,
+ 0, 0, rgbBk, rgbFg)
+ : FALSE;
+}
+
+BOOL Listbox::GetExpandboxMetrics(HDC hdc, BOOL fExpanded, SIZE *pSize)
+{
+ SetupImage *image = GetExpandboxImage(hdc, fExpanded);
+ return (NULL != image) ? image->GetSize(pSize) : FALSE;
+}
+
+HMENU Listbox::GetContextMenu(UINT menuId)
+{
+ HMENU baseMenu = WASABI_API_LOADMENUW(IDR_SETUPMENU);
+ if (NULL == baseMenu)
+ return NULL;
+
+ switch(menuId)
+ {
+ case menuGroupContext:
+ return GetSubMenu(baseMenu, 0);
+ }
+ return NULL;
+}
+
+LRESULT Listbox::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_DESTROY:
+ OnDestroy();
+ return 0;
+ case WM_MOUSEMOVE:
+ OnMouseEvent(uMsg, (UINT)wParam, MAKEPOINTS(lParam), TRUE, &SetupListboxItem::MouseMove);
+ return 0;
+ case WM_LBUTTONDOWN:
+ OnMouseEvent(uMsg, (UINT)wParam, MAKEPOINTS(lParam), FALSE, &SetupListboxItem::LButtonDown);
+ return 0;
+ case WM_LBUTTONUP:
+ OnMouseEvent(uMsg, (UINT)wParam, MAKEPOINTS(lParam), TRUE, &SetupListboxItem::LButtonUp);
+ return 0;
+ case WM_LBUTTONDBLCLK:
+ OnMouseEvent(uMsg, (UINT)wParam, MAKEPOINTS(lParam), FALSE, &SetupListboxItem::LButtonDblClk);
+ return 0;
+ case WM_RBUTTONDOWN:
+ OnMouseEvent(uMsg, (UINT)wParam, MAKEPOINTS(lParam), FALSE, &SetupListboxItem::RButtonDown);
+ return 0;
+ case WM_RBUTTONUP:
+ OnMouseEvent(uMsg, (UINT)wParam, MAKEPOINTS(lParam), TRUE, &SetupListboxItem::RButtonUp);
+ return 0;
+ case WM_MOUSELEAVE:
+ OnMouseLeave();
+ return 0;
+ case WM_CAPTURECHANGED:
+ OnCaptureChanged((HWND)lParam);
+ break;
+ case WM_ERASEBKGND:
+ OnEraseBkGround((HDC)wParam);
+ return TRUE;
+ case WM_COMMAND:
+ OnCommand(LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
+ return 0;
+ }
+
+ return CallPrevProc(uMsg, wParam, lParam);
+}
+
+static LRESULT WINAPI Listbox_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ Listbox *list = GetList(hwnd);
+ if (NULL != list)
+ return list->WindowProc(uMsg, wParam, lParam);
+
+ return (IsWindowUnicode(hwnd)) ?
+ DefWindowProcW(hwnd, uMsg, wParam, lParam) :
+ DefWindowProcA(hwnd, uMsg, wParam, lParam);
+
+}
diff --git a/Src/Plugins/Library/ml_online/Setup/setupListbox.h b/Src/Plugins/Library/ml_online/Setup/setupListbox.h
new file mode 100644
index 00000000..0fceffc3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupListbox.h
@@ -0,0 +1,84 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLISTBOX_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLISTBOX_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <windows.h>
+
+class SetupGroupList;
+class SetupListboxItem;
+
+class __declspec(novtable) SetupListbox
+{
+public:
+ typedef enum
+ {
+ menuGroupContext = 0,
+ } contextMenu;
+
+public:
+ static HRESULT CreateInstance(HWND hListbox, SetupGroupList *groupList, SetupListbox **pInstance);
+ static SetupListbox *GetInstance(HWND hListbox);
+
+public:
+ virtual HWND GetHwnd() = 0;
+ virtual HFONT GetFont() = 0;
+ virtual LRESULT CallDefaultProc(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
+ virtual LRESULT CallPrevProc(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
+
+ virtual BOOL MeasureItem(INT itemId, UINT *cx, UINT *cy) = 0;
+ virtual BOOL DrawItem(HDC hdc, const RECT *prcItem, INT itemId, UINT itemState, UINT itemAction) = 0;
+ virtual INT_PTR KeyToItem(INT vKey, INT caretPos) = 0;
+ virtual INT_PTR CharToItem(INT vKey, INT caretPos) = 0;
+
+ virtual BOOL DrawCheckbox(HDC hdc, BOOL checked, UINT state, const RECT *pRect, const RECT *pClipRect) = 0;
+ virtual BOOL GetCheckboxMetrics(HDC hdc, BOOL checked, UINT state, SIZE *pSize) = 0;
+ virtual void SetCapture(SetupListboxItem *item) = 0;
+ virtual SetupListboxItem *GetCapture() = 0;
+ virtual void ReleaseCapture() = 0;
+
+ virtual BOOL InvalidateRect(const RECT *prcInvalidate, BOOL fErase) = 0;
+ virtual BOOL InvalidateItem(INT iItem, BOOL fErase) = 0;
+ virtual void UpdateCount() = 0;
+
+
+ virtual BOOL DrawExpandbox(HDC hdc, BOOL fExpanded, const RECT *pRect, COLORREF rgbBk, COLORREF rgbFg) = 0;
+ virtual BOOL GetExpandboxMetrics(HDC hdc, BOOL fExpanded, SIZE *pSize) = 0;
+
+ virtual INT GetPageCount() = 0;
+ virtual INT GetNextEnabledItem(INT iItem, SetupListboxItem **itemOut) = 0;
+ virtual INT GetPrevEnabledItem(INT iItem, SetupListboxItem **itemOut) = 0;
+ virtual SetupListboxItem *GetSelection() = 0;
+ virtual BOOL SetSelection(SetupListboxItem *item) = 0;
+ virtual BOOL GetIndex(SetupListboxItem *item, INT *iItem) = 0;
+
+ virtual HMENU GetContextMenu(UINT menuId) = 0;
+
+
+};
+
+class __declspec(novtable) SetupListboxItem
+{
+
+public:
+ virtual BOOL MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy) = 0;
+ virtual BOOL DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state) = 0;
+ virtual INT_PTR KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey) = 0;
+ virtual BOOL MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt) = 0;
+ virtual BOOL MouseLeave(SetupListbox *instance, const RECT *prcItem) = 0;
+ virtual BOOL LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt) = 0;
+ virtual BOOL LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt) = 0;
+ virtual BOOL LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt) = 0;
+ virtual BOOL RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt) = 0;
+ virtual BOOL RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt) = 0;
+ virtual void CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured) = 0;
+ virtual BOOL IsDisabled() = 0;
+ virtual void Command(SetupListbox *instance, INT commandId, INT eventId) = 0;
+
+ virtual HWND CreateDetailsView(HWND hParent) = 0;
+ virtual BOOL GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax) = 0;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLISTBOX_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupListboxLabel.cpp b/Src/Plugins/Library/ml_online/Setup/setupListboxLabel.cpp
new file mode 100644
index 00000000..0a24b61e
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupListboxLabel.cpp
@@ -0,0 +1,228 @@
+#include "./setupListboxLabel.h"
+#include "../api__ml_online.h"
+#include "../common.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define LABEL_MARGINCX 0
+#define LABEL_MARGINCY 1
+
+#define TEXT_OFFSET_LEFT 2
+#define TEXT_OFFSET_BOTTOM 2
+#define TEXT_ALIGN (TA_CENTER | TA_BOTTOM)
+
+SetupListboxLabel::SetupListboxLabel(LPCWSTR pszName)
+ : ref(1), name(NULL)
+{
+ SetName(pszName);
+}
+
+SetupListboxLabel::~SetupListboxLabel()
+{
+ SetName(NULL);
+}
+
+SetupListboxLabel *SetupListboxLabel::CreateInstance(LPCWSTR pszName)
+{
+ return new SetupListboxLabel(pszName);
+}
+
+ULONG SetupListboxLabel::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupListboxLabel::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+ return r;
+}
+
+HRESULT SetupListboxLabel::GetName(LPWSTR pszBuffer, INT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ if (NULL != name && IS_INTRESOURCE(name))
+ {
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)name, pszBuffer, cchBufferMax);
+ return (L'\0' != *pszBuffer) ? S_OK : E_FAIL;
+ }
+ return StringCchCopyExW(pszBuffer, cchBufferMax, name, NULL, NULL, STRSAFE_IGNORE_NULLS);
+}
+HRESULT SetupListboxLabel::SetName(LPCWSTR pszName)
+{
+ if (NULL != name && !IS_INTRESOURCE(name))
+ Plugin_FreeString(name);
+
+ if (IS_INTRESOURCE(pszName))
+ {
+ name = (LPWSTR)pszName;
+ }
+ else
+ {
+ name = Plugin_CopyString(pszName);
+ }
+
+ return S_OK;
+}
+
+BOOL SetupListboxLabel::IsNameNull()
+{
+ return (NULL == name);
+}
+
+void SetupListboxLabel::GetColors(HDC hdc, UINT state, COLORREF *rgbBkOut, COLORREF *rgbTextOut)
+{
+ COLORREF rgbBk, rgbText;
+
+ rgbBk = GetSysColor(COLOR_WINDOW);
+ rgbText = GetSysColor( (0 == (ODS_DISABLED & state)) ? COLOR_GRAYTEXT : COLOR_GRAYTEXT);
+
+ if (NULL != rgbBkOut) *rgbBkOut = rgbBk;
+ if (NULL != rgbTextOut) *rgbTextOut = rgbText;
+}
+
+HBRUSH SetupListboxLabel::GetBrush(HDC hdc, UINT state)
+{
+ return GetSysColorBrush(COLOR_WINDOW);
+}
+
+BOOL SetupListboxLabel::MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy)
+{
+ HDC hdc = GetDCEx(instance->GetHwnd(), NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == hdc) return FALSE;
+
+ HFONT originalFont = (HFONT)SelectObject(hdc, instance->GetFont());
+
+ if (NULL != cy)
+ {
+ *cy = 0;
+ TEXTMETRIC tm;
+ if (GetTextMetrics(hdc, &tm))
+ *cy = tm.tmHeight + tm.tmExternalLeading + LABEL_MARGINCY*2;
+ }
+
+ if (NULL != cx)
+ {
+ *cx = 0;
+ WCHAR szBuffer[128] = {0};
+ if (SUCCEEDED(GetName(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ INT cchBuffer = lstrlenW(szBuffer);
+ SIZE textSize;
+ if (0 != cchBuffer && GetTextExtentPoint32(hdc, szBuffer, cchBuffer, &textSize))
+ {
+ *cx = textSize.cx + LABEL_MARGINCX*2;
+ }
+ }
+ }
+
+ SelectObject(hdc, originalFont);
+ ReleaseDC(instance->GetHwnd(), hdc);
+ return TRUE;
+}
+
+BOOL SetupListboxLabel::DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state)
+{
+ LONG paintLeft = prc->left + LABEL_MARGINCX;
+
+ COLORREF rgbBk, rgbText;
+ GetColors(hdc, state, &rgbBk, &rgbText);
+
+ COLORREF origBk = SetBkColor(hdc, rgbBk);
+ COLORREF origText = SetTextColor(hdc, rgbText);
+ UINT textAlign = SetTextAlign(hdc, TEXT_ALIGN);
+
+ HRGN backRgn, rgn;
+ backRgn = CreateRectRgnIndirect(prc);
+ rgn = CreateRectRgn(0,0,0,0);
+
+ RECT partRect;
+ WCHAR szBuffer[128] = {0};
+ INT cchBuffer = 0;
+
+ if (SUCCEEDED(GetName(szBuffer, ARRAYSIZE(szBuffer))))
+ cchBuffer = lstrlenW(szBuffer);
+
+ SetRect(&partRect, paintLeft, prc->top, prc->right - LABEL_MARGINCX, prc->bottom);
+ if (ExtTextOut(hdc, partRect.left + (partRect.right - partRect.left)/2, partRect.bottom - TEXT_OFFSET_BOTTOM,
+ ETO_OPAQUE | ETO_CLIPPED, &partRect, szBuffer, cchBuffer, NULL))
+ {
+ if (SetRectRgn(rgn, partRect.left, partRect.top, partRect.right, partRect.bottom))
+ CombineRgn(backRgn, backRgn, rgn, RGN_DIFF);
+ }
+
+ if (NULL != backRgn)
+ {
+ FillRgn(hdc, backRgn, GetBrush(hdc, state));
+ DeleteObject(backRgn);
+ }
+ if (NULL != rgn)
+ DeleteObject(rgn);
+
+
+ if (ODS_FOCUS == ((ODS_FOCUS | 0x0200/*ODS_NOFOCUSRECT*/) & state))
+ DrawFocusRect(hdc, prc);
+
+ if (TEXT_ALIGN != textAlign) SetTextAlign(hdc, textAlign);
+ if (origBk != rgbBk) SetBkColor(hdc, origBk);
+ if (origText != rgbText) SetTextColor(hdc, origText);
+ return TRUE;
+}
+
+INT_PTR SetupListboxLabel::KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey)
+{
+ return -1;
+}
+BOOL SetupListboxLabel::MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupListboxLabel::MouseLeave(SetupListbox *instance, const RECT *prcItem)
+{
+ return FALSE;
+}
+BOOL SetupListboxLabel::LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupListboxLabel::LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupListboxLabel::LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupListboxLabel::RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+BOOL SetupListboxLabel::RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+void SetupListboxLabel::CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured)
+{
+}
+
+BOOL SetupListboxLabel::GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ WCHAR szName[128] = {0};
+ if (FAILED(GetName(szName, ARRAYSIZE(szName))))
+ return FALSE;
+
+ if (NULL == pszBuffer ||
+ FAILED(StringCchPrintf(pszBuffer, cchBufferMax, L"lbl_empty_%s", szName)))
+ {
+ return FALSE;
+ }
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupListboxLabel.h b/Src/Plugins/Library/ml_online/Setup/setupListboxLabel.h
new file mode 100644
index 00000000..b1b474e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupListboxLabel.h
@@ -0,0 +1,57 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLISTBOX_LABEL_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLISTBOX_LABEL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "./setupListbox.h"
+
+
+class SetupListboxLabel: public SetupListboxItem
+{
+
+protected:
+ SetupListboxLabel(LPCWSTR pszName);
+ ~SetupListboxLabel();
+
+public:
+ static SetupListboxLabel *CreateInstance(LPCWSTR pszNamee);
+
+public:
+ ULONG AddRef();
+ ULONG Release();
+
+ HRESULT GetName(LPWSTR pszBuffer, INT cchBufferMax);
+ HRESULT SetName(LPCWSTR pszName);
+ BOOL IsNameNull();
+
+ void GetColors(HDC hdc, UINT state, COLORREF *rgbBkOut, COLORREF *rgbTextOut);
+ HBRUSH GetBrush(HDC hdc, UINT state);
+
+
+ /* SetupListboxItem */
+ BOOL MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy);
+ BOOL DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state);
+ INT_PTR KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey);
+ BOOL MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL MouseLeave(SetupListbox *instance, const RECT *prcItem);
+ BOOL LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ void CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured);
+ BOOL IsDisabled() { return TRUE; }
+ void Command(SetupListbox *instance, INT commandId, INT eventId) {}
+ HWND CreateDetailsView(HWND hParent) { return NULL; }
+ BOOL GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax);
+
+
+protected:
+ ULONG ref;
+ LPWSTR name;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLISTBOX_LABEL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupLog.cpp b/Src/Plugins/Library/ml_online/Setup/setupLog.cpp
new file mode 100644
index 00000000..a53c469f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupLog.cpp
@@ -0,0 +1,395 @@
+#include "./setupLog.h"
+#include "../common.h"
+#include "../api__ml_online.h"
+#include "../config.h"
+
+#include "../../nu/trace.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omwebstorage.h>
+#include <ifc_omstorageasync.h>
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define SETUPLOG_SEPARATOR ','
+#define SETUPLOG_SECTION "Setup"
+#define SETUPLOG_KEY_SUBSCRIBED "subscribed"
+#define SETUPLOG_KEY_UNSUBSCRIBED "unsubscribed"
+
+struct LOGPARSERPARAM
+{
+ LOGPARSERPARAM() : instance(NULL), operation(0) {}
+
+ SetupLog *instance;
+ UINT operation;
+};
+
+static size_t SetupLog_GetMaxServiceIdCount(SetupLog::ServiceMap *serviceMap)
+{
+ SetupLog::ServiceMap::iterator it;
+
+ size_t c1 = 0, c2 = 0;
+ for (it = serviceMap->begin(); it != serviceMap->end(); it++)
+ {
+ switch(it->second)
+ {
+ case SetupLog::opServiceAdded:
+ c1++;
+ break;
+ case SetupLog::opServiceRemoved:
+ c2++;
+ break;
+ }
+ }
+ return (c1 > c2) ? c1 : c2;
+}
+
+static HRESULT SetupLog_FormatServiceId(SetupLog::ServiceMap *serviceMap, INT operation, LPSTR pszBuffer, size_t cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_INVALIDARG;
+
+ *pszBuffer = '\0';
+
+ if (NULL == serviceMap)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ size_t remaining = cchBufferMax;
+ LPSTR cursor = pszBuffer;
+ SetupLog::ServiceMap::iterator it;
+
+ const char format[] = { SETUPLOG_SEPARATOR, '%', 'u', '\0'};
+
+ for (it = serviceMap->begin(); it != serviceMap->end() && SUCCEEDED(hr); it++)
+ {
+ if (it->second == operation)
+ {
+ hr = StringCchPrintfExA(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ ((cursor == pszBuffer) ? (format + 1) : format), it->first);
+ }
+ }
+ return hr;
+}
+
+static LPCSTR SetupLog_GetOperationKey(UINT operation)
+{
+ switch(operation)
+ {
+ case SetupLog::opServiceAdded: return SETUPLOG_KEY_SUBSCRIBED;
+ case SetupLog::opServiceRemoved: return SETUPLOG_KEY_UNSUBSCRIBED;
+ }
+ return NULL;
+}
+
+static LPCWSTR SetupLog_GetOperationAction(UINT operation)
+{
+ switch(operation)
+ {
+ case SetupLog::opServiceAdded: return L"add";
+ case SetupLog::opServiceRemoved: return L"remove";
+ }
+ return NULL;
+}
+
+static BOOL SetupLog_WriteOperationLog(UINT operation, LPCSTR pszValue)
+{
+ LPCSTR pszKey = SetupLog_GetOperationKey(operation);
+ if (NULL == pszKey) return FALSE;
+ return Config_WriteStr(SETUPLOG_SECTION, pszKey, pszValue);
+}
+
+static HRESULT SetupLog_ReadOperationLog(UINT operation, LPSTR pszBuffer, UINT cchBufferMax, UINT *cchReaded)
+{
+ LPCSTR pszKey = SetupLog_GetOperationKey(operation);
+ if (NULL == pszKey) return E_INVALIDARG;
+
+ DWORD readed = Config_ReadStr(SETUPLOG_SECTION, pszKey, NULL, pszBuffer, cchBufferMax);
+
+ if (NULL != cchReaded)
+ {
+ *cchReaded = readed;
+ }
+
+ return S_OK;
+}
+
+SetupLog::SetupLog() : ref(1)
+{
+}
+
+SetupLog::~SetupLog()
+{
+
+}
+
+SetupLog *SetupLog::Open()
+{
+ SetupLog *instance = new SetupLog();
+ if (NULL == instance) return NULL;
+
+ INT cchBuffer = 32000;
+ LPSTR buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer)
+ {
+ instance->Release();
+ return NULL;
+ }
+
+ UINT cchReaded = 0;
+ const UINT szOperations[] = { opServiceAdded,
+ opServiceRemoved, };
+
+ UINT serviceId;
+ for (INT i = 0; i < ARRAYSIZE(szOperations); i++)
+ {
+ if (SUCCEEDED(SetupLog_ReadOperationLog(szOperations[i], buffer, cchBuffer, &cchReaded)) && cchReaded > 0)
+ {
+ LPSTR cursor = buffer;
+ LPSTR block = cursor;
+
+ for(;;)
+ {
+ if (SETUPLOG_SEPARATOR == *cursor || '\0' == *cursor)
+ {
+ while (' ' == *block && block < cursor) block++;
+
+ if (block < cursor &&
+ FALSE != StrToIntExA(block, STIF_SUPPORT_HEX, (INT*)&serviceId) &&
+ 0 != serviceId)
+ {
+ instance->LogServiceById(serviceId, szOperations[i]);
+ }
+
+ if ('\0' == *cursor)
+ break;
+
+ cursor++;
+ block = cursor;
+ }
+ else
+ {
+ cursor++;
+ }
+ }
+ }
+ }
+
+ Plugin_FreeAnsiString(buffer);
+ return instance;
+}
+
+ULONG SetupLog::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupLog::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+BOOL SetupLog::IsOperationSupported(UINT operation)
+{
+ switch(operation)
+ {
+ case SetupLog::opServiceAdded:
+ case SetupLog::opServiceRemoved:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+HRESULT SetupLog::LogServiceById(UINT serviceUid, UINT operation)
+{
+ if (0 == serviceUid || FALSE == IsOperationSupported(operation))
+ return E_INVALIDARG;
+
+ serviceMap[serviceUid] = operation;
+ return S_OK;
+}
+
+HRESULT SetupLog::LogService(ifc_omservice *service, UINT operation)
+{
+ if (NULL == service || !IsOperationSupported(operation))
+ return E_INVALIDARG;
+
+ return LogServiceById(service->GetId(), operation);
+}
+
+HRESULT SetupLog::Save()
+{
+ LPSTR buffer = NULL;
+ size_t cchBuffer = SetupLog_GetMaxServiceIdCount(&serviceMap) * 11;
+
+ if (0 != cchBuffer)
+ {
+ cchBuffer += 1;
+ buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer)
+ return E_OUTOFMEMORY;
+ }
+
+ const UINT szOperations[] = { opServiceAdded,
+ opServiceRemoved, };
+
+ for (INT i = 0; i < ARRAYSIZE(szOperations); i++)
+ {
+ LPCSTR value = (NULL != buffer &&
+ SUCCEEDED(SetupLog_FormatServiceId(&serviceMap, szOperations[i], buffer, cchBuffer)) &&
+ '\0' != *buffer) ? buffer : NULL;
+ SetupLog_WriteOperationLog(szOperations[i], value);
+ }
+
+ if (NULL != buffer)
+ Plugin_FreeAnsiString(buffer);
+
+ return S_OK;
+}
+
+HRESULT SetupLog::Erase()
+{
+ HRESULT hr = S_OK;
+ if (FALSE == Config_WriteStr(SETUPLOG_SECTION, SETUPLOG_KEY_SUBSCRIBED, NULL))
+ hr = E_FAIL;
+ if (FALSE == Config_WriteStr(SETUPLOG_SECTION, SETUPLOG_KEY_UNSUBSCRIBED, NULL))
+ hr = E_FAIL;
+
+ return hr;
+}
+
+struct LOGSENDJOBPARAM
+{
+ LOGSENDJOBPARAM() : totalJobs(0), storage(NULL), completeEvent(NULL) {}
+
+ ULONG totalJobs;
+ CRITICAL_SECTION lock;
+ ifc_omstorage *storage;
+ HANDLE completeEvent;
+};
+
+static void CALLBACK SetupLog_SendCompleted(ifc_omstorageasync *async)
+{
+ if (NULL != async)
+ {
+ LOGSENDJOBPARAM *param = NULL;
+ if (SUCCEEDED(async->GetData((void**)&param)) && NULL != param)
+ {
+ EnterCriticalSection(&param->lock);
+
+ if (NULL != param->storage)
+ {
+ param->storage->EndLoad(async, NULL);
+ param->storage->Release();
+ }
+
+ LONG r = InterlockedDecrement((LONG*)&param->totalJobs);
+ if (0 == r)
+ {
+ if (NULL != param->completeEvent)
+ SetEvent(param->completeEvent);
+
+ LeaveCriticalSection(&param->lock);
+ DeleteCriticalSection(&param->lock);
+ free(param);
+ param = NULL;
+ }
+ else
+ {
+ LeaveCriticalSection(&param->lock);
+ }
+ }
+ }
+}
+
+HRESULT SetupLog::Send(HANDLE completeEvent)
+{
+ size_t cchAlloc = serviceMap.size();
+ if (0 == cchAlloc)
+ {
+ if (NULL != completeEvent) SetEvent(completeEvent);
+ return S_OK;
+ }
+
+ UINT *buffer = (UINT*)calloc(cchAlloc, sizeof(UINT));
+ LOGSENDJOBPARAM *param = (LOGSENDJOBPARAM*)calloc(1, sizeof(LOGSENDJOBPARAM));
+
+ if (NULL == buffer || NULL == param)
+ {
+ if (NULL != buffer) { free(buffer); buffer = NULL; }
+ if (NULL != param) { free(param); param = NULL; }
+ if (NULL != completeEvent) { SetEvent(completeEvent); completeEvent = NULL; }
+ return E_OUTOFMEMORY;
+ }
+
+ ifc_omstorage *storage = NULL;
+ HRESULT hr = OMSERVICEMNGR->QueryStorage(&SUID_OmStorageUrl, &storage);
+ if (SUCCEEDED(hr) && storage != NULL)
+ {
+ const UINT szOperations[] = { opServiceAdded,
+ opServiceRemoved, };
+ param->totalJobs = 0;
+ param->completeEvent = completeEvent;
+ param->storage = storage;
+
+ InitializeCriticalSection(&param->lock);
+ EnterCriticalSection(&param->lock);
+
+ for (INT i = 0; i < ARRAYSIZE(szOperations); i++)
+ {
+ size_t count = 0;
+ hr = S_OK;
+ for (SetupLog::ServiceMap::iterator it = serviceMap.begin(); it != serviceMap.end() && SUCCEEDED(hr); it++)
+ {
+ if (it->second == szOperations[i])
+ {
+ buffer[count] = it->first;
+ count++;
+ }
+ }
+
+ if (0 != count)
+ {
+ LPWSTR url = NULL;
+ LPCWSTR action = SetupLog_GetOperationAction(szOperations[i]);
+ if (NULL != action &&
+ SUCCEEDED(Plugin_BuildActionUrl(&url, action, buffer, count)))
+ {
+ ifc_omstorageasync *async = NULL;;
+ if (SUCCEEDED(storage->BeginLoad(url, NULL, SetupLog_SendCompleted, param, &async)))
+ {
+ InterlockedIncrement((LONG*)&param->totalJobs);
+ storage->AddRef();
+ async->Release();
+ }
+ Plugin_FreeString(url);
+ }
+ }
+ }
+
+ if (0 == param->totalJobs)
+ {
+ LeaveCriticalSection(&param->lock);
+ DeleteCriticalSection(&param->lock);
+ hr = E_FAIL;
+ if (param) { free(param); param = NULL; }
+ }
+ else
+ {
+ LeaveCriticalSection(&param->lock);
+ }
+
+ storage->Release();
+ }
+
+ if (buffer) { free(buffer); buffer = NULL; }
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupLog.h b/Src/Plugins/Library/ml_online/Setup/setupLog.h
new file mode 100644
index 00000000..e701046a
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupLog.h
@@ -0,0 +1,54 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLOG_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLOG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <map>
+
+class ifc_omservice;
+
+class SetupLog
+{
+public:
+ typedef enum
+ {
+ opUnknown = 0,
+ opServiceAdded = 1,
+ opServiceRemoved = 2,
+ };
+
+protected:
+ SetupLog();
+ ~SetupLog();
+
+public:
+ static SetupLog *Open();
+ static HRESULT Erase();
+
+public:
+ ULONG AddRef();
+ ULONG Release();
+
+ HRESULT LogServiceById(UINT serviceUid, UINT operation);
+ HRESULT LogService(ifc_omservice *service, UINT operation);
+ HRESULT Save();
+ HRESULT Send(HANDLE completeEvent);
+
+ BOOL IsOperationSupported(UINT operation);
+
+
+
+protected:
+ typedef std::map<UINT, UINT> ServiceMap;
+ friend static size_t SetupLog_GetMaxServiceIdCount(SetupLog::ServiceMap *serviceMap);
+ friend static HRESULT SetupLog_FormatServiceId(SetupLog::ServiceMap *serviceMap, INT operation, LPSTR pszBuffer, size_t cchBufferMax);
+
+protected:
+ ULONG ref;
+ ServiceMap serviceMap;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPLOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupPage.cpp b/Src/Plugins/Library/ml_online/Setup/setupPage.cpp
new file mode 100644
index 00000000..8b2db86d
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupPage.cpp
@@ -0,0 +1,648 @@
+#include "./setupPage.h"
+#include "./setupListbox.h"
+#include "./setupGroupList.h"
+#include "./setupGroupFilter.h"
+#include "./setupListboxLabel.h"
+#include "../common.h"
+#include "../config.h"
+#include "../resource.h"
+#include "../api__ml_online.h"
+
+#include "./setupDetails.h"
+#include "./setupLog.h"
+
+#include "../../winamp/setup/svc_setup.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omfilestorage.h>
+#include <ifc_omwebstorage.h>
+
+#include <windows.h>
+#include <strsafe.h>
+#include <vector>
+
+#define FEATURED_SERVICES_URL L"http://services.winamp.com/svc/default"
+
+#define IDC_DETAILS 10000
+
+typedef std::vector<ifc_omservice*> ServiceList;
+typedef std::vector<UINT> ServiceIdList;
+
+HWND SetupPage_CreateWindow(HWND hParent, SetupPage *page);
+static INT_PTR SetupPage_ModalLoop(HWND hwnd, HACCEL hAccel, HANDLE hCancel);
+
+struct AppendServiceToStringData
+{
+ AppendServiceToStringData() : formatFirst(NULL), formatNext(NULL), cursor(NULL),
+ remaining(0), inserted(0), result(S_OK) {}
+
+ LPCWSTR formatFirst;
+ LPCWSTR formatNext;
+ LPWSTR cursor;
+ size_t remaining;
+ size_t inserted;
+ HRESULT result;
+};
+
+static BOOL CALLBACK SetupPage_AppendServiceToStringCallback(UINT serviceId, void *data)
+{
+ AppendServiceToStringData *param = (AppendServiceToStringData*)data;
+ if (NULL == param) return FALSE;
+
+ param->result = StringCchPrintfEx(param->cursor, param->remaining, &param->cursor, &param->remaining,
+ STRSAFE_NULL_ON_FAILURE,
+ ((0 == param->inserted) ? param->formatFirst : param->formatNext), serviceId);
+
+ if (FAILED(param->result))
+ return FALSE;
+
+ param->inserted++;
+ return TRUE;
+}
+
+static BOOL CALLBACK SetupPage_AppendServiceIdCallback(UINT serviceId, void *data)
+{
+ ServiceIdList *list = (ServiceIdList*)data;
+ if (NULL == list) return FALSE;
+ list->push_back(serviceId);
+ return TRUE;
+}
+
+static HRESULT SetupPage_GetFeaturedServicesUrl(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ LPWSTR cursor = pszBuffer;
+ size_t remaining = cchBufferMax;
+
+ HRESULT hr = StringCchCopyEx(cursor, remaining, FEATURED_SERVICES_URL, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ if (FAILED(hr))
+ return hr;
+
+ AppendServiceToStringData param;
+ param.cursor = cursor;
+ param.remaining = remaining;
+ param.formatFirst = L"?svc_ids=%u";
+ param.formatNext = L",%u";
+ param.inserted = 0;
+ param.result = S_OK;
+
+ hr = Config_ReadServiceIdList("Setup", "featuredExtra", ',', SetupPage_AppendServiceToStringCallback, &param);
+ if (SUCCEEDED(hr))
+ hr = param.result;
+
+ return hr;
+}
+
+SetupPage* SetupPage::CreateInstance()
+{
+ SetupPage *instance = new SetupPage();
+ return instance;
+}
+
+SetupPage::SetupPage()
+ : ref(1), hwnd(NULL), name(NULL), title(NULL),
+ groupList(NULL), completeEvent(NULL),
+ servicesInitialized(FALSE)
+{
+ WasabiApi_AddRef();
+ SetupDetails_Initialize();
+}
+
+SetupPage::~SetupPage()
+{
+ if (NULL != name)
+ {
+ Plugin_FreeString(name);
+ name = NULL;
+ }
+
+ if (NULL != title)
+ {
+ Plugin_FreeString(title);
+ title = NULL;
+ }
+
+ if (NULL != groupList)
+ {
+ groupList->Release();
+ groupList = NULL;
+ }
+
+ if (NULL != completeEvent)
+ {
+ CloseHandle(completeEvent);
+ completeEvent = NULL;
+ }
+
+ SetupDetails_Uninitialize();
+
+ if (FALSE != servicesInitialized)
+ {
+ if (NULL != OMBROWSERMNGR)
+ OMBROWSERMNGR->Finish();
+ }
+
+ WasabiApi_Release();
+}
+
+size_t SetupPage::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t SetupPage::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int SetupPage::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+ return E_NOTIMPL;
+}
+
+HRESULT SetupPage::GetName(bool bShort, const wchar_t **pszName)
+{
+ InitializeServices();
+
+ if (false == bShort)
+ {
+ if (NULL == title)
+ {
+ WCHAR szBuffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SETUPPAGE_TITLE, szBuffer, ARRAYSIZE(szBuffer));
+ title = Plugin_CopyString(szBuffer);
+ }
+ *pszName = title;
+ }
+ else
+ {
+ if (NULL == name)
+ {
+ WCHAR szBuffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_ONLINE_SERVICES, szBuffer, ARRAYSIZE(szBuffer));
+ name = Plugin_CopyString(szBuffer);
+ }
+ *pszName = name;
+ }
+ return S_OK;
+}
+
+HRESULT SetupPage::Save(HWND hwndText)
+{
+ if (NULL == groupList)
+ return S_OK;
+
+ SetupLog *log = SetupLog::Open();
+ HRESULT hr = groupList->Save(log);
+
+ if (NULL != log)
+ {
+ log->Save();
+ log->Release();
+ }
+
+ return hr;
+}
+
+HRESULT SetupPage::Revert(void)
+{
+ HRESULT hr(S_OK);
+
+ if (NULL != groupList)
+ {
+ groupList->Release();
+ groupList = NULL;
+ }
+ return hr;
+}
+
+HRESULT SetupPage::IsDirty(void)
+{
+ return (NULL != groupList && groupList->IsModified()) ? S_OK : S_FALSE;
+}
+
+HRESULT SetupPage::Validate(void)
+{
+ return S_OK;
+}
+
+HRESULT SetupPage::InitializeServices()
+{
+ if (FALSE != servicesInitialized)
+ return S_FALSE;
+
+ HWND hWinamp = NULL;
+ svc_setup *setupSvc = QueryWasabiInterface(svc_setup, UID_SVC_SETUP);
+ if (NULL == setupSvc) return E_UNEXPECTED;
+ HRESULT hr = setupSvc->GetWinampWnd(&hWinamp);
+ ReleaseWasabiInterface(UID_SVC_SETUP, setupSvc);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = WasabiApi_LoadDefaults();
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != OMBROWSERMNGR &&
+ NULL != OMSERVICEMNGR &&
+ NULL != OMUTILITY)
+ {
+ hr = OMBROWSERMNGR->Initialize(NULL, hWinamp);
+ }
+ else
+ hr = E_UNEXPECTED;
+
+ if (SUCCEEDED(hr))
+ servicesInitialized = TRUE;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT SetupPage::CreateView(HWND hParent, HWND *phwnd)
+{
+ if (NULL == phwnd)
+ return E_INVALIDARG;
+
+ if (FAILED(InitializeServices()))
+ {
+ *phwnd = NULL;
+ return E_FAIL;
+ }
+
+ *phwnd = SetupPage_CreateWindow(hParent, this);
+ return (NULL == phwnd) ? E_FAIL : S_OK;
+}
+
+
+BOOL SetupPage::UpdateListAsync(INT groupId)
+{
+ if (NULL == hwnd)
+ return FALSE;
+
+ return PostMessage(hwnd, SPM_UPDATELIST, (WPARAM)groupId, NULL);
+}
+
+BOOL SetupPage::AttachWindow(HWND hAttach)
+{
+ hwnd = hAttach;
+
+ if (NULL == completeEvent)
+ completeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+
+ if (NULL == groupList)
+ {
+ groupList = SetupGroupList::CreateInstance();
+ if (NULL != groupList)
+ {
+ WCHAR szBuffer[4096] = {0};
+ SetupPage_GetFeaturedServicesUrl(szBuffer, ARRAYSIZE(szBuffer));
+
+ SetupGroup *group = SetupGroup::CreateInstance(ID_FEATUREDGROUP, MAKEINTRESOURCE(IDS_SERVICEGROUP_FEATURED),
+ szBuffer, &SUID_OmStorageUrl, &FUID_SetupFeaturedGroupFilter,
+ SetupGroup::styleSortAlphabetically | SetupGroup::styleDefaultSubscribed | SetupGroup::styleSaveAll);
+ if (NULL != group)
+ {
+ group->SetLongName(MAKEINTRESOURCE(IDS_SERVICEGROUP_FEATUREDLONG));
+ group->SetDescription(MAKEINTRESOURCE(IDS_SERVICEGROUP_FEATURED_DESC));
+ groupList->AddGroup(group);
+ group->RequestReload();
+ group->Release();
+ }
+
+ group = SetupGroup::CreateInstance(ID_KNOWNGROUP, MAKEINTRESOURCE(IDS_SERVICEGROUP_KNOWN),
+ L"*.ini", &SUID_OmStorageIni, &FUID_SetupKnownGroupFilter,
+ SetupGroup::styleSortAlphabetically);
+ if (NULL != group)
+ {
+ group->SetLongName(MAKEINTRESOURCE(IDS_SERVICEGROUP_KNOWNLONG));
+ group->SetDescription(MAKEINTRESOURCE(IDS_SERVICEGROUP_KNOWN_DESC));
+ group->RequestReload();
+ groupList->AddGroup(group);
+ group->Release();
+ }
+ }
+ }
+
+ if (NULL != groupList)
+ groupList->SetPageWnd(hwnd);
+
+ HWND hList = GetDlgItem(hwnd, IDC_SERVICELIST);
+ if (NULL != hList)
+ {
+ SetupListbox *listbox;
+ if (SUCCEEDED(SetupListbox::CreateInstance(hList, groupList, &listbox)))
+ {
+ }
+ ListboxSelectionChanged();
+ }
+
+ return TRUE;
+}
+
+void SetupPage::DetachWindow()
+{
+ hwnd = NULL;
+ if (NULL != groupList)
+ {
+ groupList->SetPageWnd(NULL);
+ }
+}
+
+
+HRESULT SetupPage_AppendUnselectedServices(LPCSTR pszSection, LPCSTR pszKey, SetupGroup *group)
+{
+ size_t index = group->GetRecordCount();
+ size_t filterIndex, filterSize;
+
+ ServiceIdList list;
+ Config_ReadServiceIdList(pszSection, pszKey, ',', SetupPage_AppendServiceIdCallback, &list);
+ filterSize = list.size();
+
+ while(index--)
+ {
+ SetupRecord *record = group->GetRecord(index);
+ if (NULL == record || FALSE != record->IsSelected()) continue;
+
+ ifc_omservice *service = record->GetService();
+ if (NULL == service) continue;
+
+ UINT serviceId = service->GetId();
+ for(filterIndex = 0; filterIndex < filterSize; filterIndex++)
+ {
+ if (list[filterIndex] == serviceId)
+ break;
+ }
+
+ if (filterIndex == filterSize)
+ list.push_back(serviceId);
+ }
+
+ if (0 != list.size())
+ {
+ size_t bufferAlloc = (list.size() * 11) * sizeof(CHAR);
+ LPSTR buffer = Plugin_MallocAnsiString(bufferAlloc);
+ if (NULL != buffer)
+ {
+ filterSize = list.size();
+ LPSTR cursor = buffer;
+ size_t remaining = bufferAlloc;
+ for(index = 0; index < filterSize; index++)
+ {
+ if (FAILED(StringCchPrintfExA(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ ((0 == index) ? "%u" : ",%u"), list[index])))
+ {
+ break;
+ }
+ }
+ Config_WriteStr(pszSection, pszKey, buffer);
+ Plugin_FreeAnsiString(buffer);
+ }
+ }
+ else
+ Config_WriteStr(pszSection, pszKey, NULL);
+
+ return S_OK;
+}
+
+HRESULT SetupPage::Execute(HWND hwndText)
+{
+ SetupGroup *group = NULL;
+ SetupLog *log = SetupLog::Open();
+ WCHAR szBuffer[128] = {0};
+
+ if (FAILED(InitializeServices()))
+ return E_FAIL;
+
+ if (NULL == groupList)
+ {
+ groupList = SetupGroupList::CreateInstance();
+ if (NULL == groupList) return E_UNEXPECTED;
+ }
+
+ if (S_OK != groupList->FindGroupById(ID_FEATUREDGROUP, &group) && group != NULL)
+ {
+ WCHAR szBuffer[4096] = {0};
+ SetupPage_GetFeaturedServicesUrl(szBuffer, ARRAYSIZE(szBuffer));
+
+ group = SetupGroup::CreateInstance(ID_FEATUREDGROUP, MAKEINTRESOURCE(IDS_SERVICEGROUP_FEATURED),
+ szBuffer, &SUID_OmStorageUrl, &FUID_SetupFeaturedGroupFilter,
+ SetupGroup::styleDefaultSubscribed | SetupGroup::styleSaveAll);
+
+ if (NULL != group)
+ {
+ group->SetLongName(MAKEINTRESOURCE(IDS_SERVICEGROUP_FEATUREDLONG));
+ group->SetDescription(MAKEINTRESOURCE(IDS_SERVICEGROUP_FEATURED_DESC));
+ groupList->AddGroup(group);
+ group->RequestReload();
+ }
+ }
+
+ if (NULL == completeEvent)
+ completeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (NULL != group)
+ {
+ if (SUCCEEDED(group->SignalLoadCompleted(completeEvent)))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DOWNLOADSERVICE_JOB, szBuffer, ARRAYSIZE(szBuffer));
+ SetWindowText(hwndText, szBuffer);
+ SetupPage_ModalLoop(hwndText, NULL, completeEvent);
+ }
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_SAVESERVICE_JOB, szBuffer, ARRAYSIZE(szBuffer));
+ SetWindowText(hwndText, szBuffer);
+ group->Save(log);
+ SetupPage_AppendUnselectedServices("Setup", "featuredHistory", group);
+ group->Release();
+ group = NULL;
+ }
+
+ // ensure that promotions are subscribed
+ if (S_OK != groupList->FindGroupById(ID_KNOWNGROUP, &group) && group != NULL)
+ {
+ group = SetupGroup::CreateInstance(ID_KNOWNGROUP, MAKEINTRESOURCE(IDS_SERVICEGROUP_KNOWN),
+ L"*.ini", &SUID_OmStorageIni, &FUID_SetupKnownGroupFilter,
+ SetupGroup::styleSortAlphabetically);
+ if (NULL != group)
+ {
+ group->SetLongName(MAKEINTRESOURCE(IDS_SERVICEGROUP_KNOWNLONG));
+ group->SetDescription(MAKEINTRESOURCE(IDS_SERVICEGROUP_KNOWN_DESC));
+ group->RequestReload();
+ groupList->AddGroup(group);
+ }
+ }
+
+ if (NULL != group)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SAVESERVICE_JOB, szBuffer, ARRAYSIZE(szBuffer));
+ SetWindowText(hwndText, szBuffer);
+
+ ResetEvent(completeEvent);
+ if (SUCCEEDED(group->SignalLoadCompleted(completeEvent)))
+ SetupPage_ModalLoop(hwndText, NULL, completeEvent);
+
+ group->Save(log);
+ group->Release();
+ group = NULL;
+ }
+
+ if (NULL != log)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_REGISTERINGSERVICE_JOB, szBuffer, ARRAYSIZE(szBuffer));
+ SetWindowText(hwndText, szBuffer);
+ ResetEvent(completeEvent);
+ log->Send(completeEvent);
+ SetupPage_ModalLoop(hwndText, NULL, completeEvent);
+ log->Release();
+ }
+
+ SetupLog::Erase();
+ Config_WriteStr("Setup", "featuredExtra", NULL); // delete promo offer
+ return S_OK;
+}
+
+HRESULT SetupPage::Cancel(HWND hwndText)
+{
+ if (NULL != completeEvent)
+ SetEvent(completeEvent);
+ return S_OK;
+}
+
+HRESULT SetupPage::IsCancelSupported(void)
+{
+ return S_OK;
+}
+void SetupPage::ListboxSelectionChanged()
+{
+ if (NULL == hwnd) return;
+ HWND hList = GetDlgItem(hwnd, IDC_SERVICELIST);
+ if (NULL == hList) return;
+
+ SetupListboxItem *item = NULL;
+ INT iSelection = (INT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
+ if (LB_ERR == iSelection ||
+ FAILED(groupList->FindListboxItem(iSelection, &item)))
+ {
+ item = NULL;
+ }
+
+ HWND hDiscard = GetDlgItem(hwnd, IDC_DETAILS);
+ if (NULL != hDiscard)
+ {
+ WCHAR szPanel[64] = {0}, szItem[64] = {0};
+ if (FALSE != SetupDetails_GetUniqueName(hDiscard, szPanel, ARRAYSIZE(szPanel)) &&
+ FALSE != item->GetUniqueName(szItem, ARRAYSIZE(szItem)) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, 0, szPanel, - 1, szItem, -1))
+ {
+ return;
+ }
+ }
+
+ HWND hDetails = (NULL != item) ? item->CreateDetailsView(hwnd) : NULL;
+ if (NULL != hDiscard)
+ {
+ SetWindowLongPtr(hDiscard, GWL_STYLE, GetWindowLongPtr(hDiscard, GWL_STYLE) & ~WS_VISIBLE);
+ }
+
+ if (NULL != hDetails)
+ {
+ HWND hPlaceholder = GetDlgItem(hwnd, IDC_PLACEHOLDER);
+ if (NULL != hPlaceholder)
+ {
+ RECT windowRect;
+ if (GetWindowRect(hPlaceholder, &windowRect))
+ {
+ MapWindowPoints(HWND_DESKTOP,hwnd, (POINT*)&windowRect,2);
+ SetWindowPos(hDetails, NULL, windowRect.left, windowRect.top,
+ windowRect.right - windowRect.left, windowRect.bottom - windowRect.top,
+ SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+ SetWindowLongPtr(hDetails, GWLP_ID, IDC_DETAILS);
+ ShowWindow(hDetails, SW_SHOWNA);
+ RedrawWindow(hDetails, NULL, NULL, RDW_ERASENOW |RDW_UPDATENOW | RDW_ALLCHILDREN);
+ }
+ }
+
+ if (NULL != hDiscard)
+ {
+ DestroyWindow(hDiscard);
+ }
+}
+
+static INT_PTR SetupPage_ModalLoop(HWND hwnd, HACCEL hAccel, HANDLE hCancel)
+{
+ MSG msg = {0};
+ HWND hParent = NULL;
+ while (NULL != (hParent = GetAncestor(hwnd, GA_PARENT)))
+ {
+ hwnd = hParent;
+ }
+
+ if (NULL == hwnd)
+ return 0;
+
+ for (;;)
+ {
+ DWORD status = MsgWaitForMultipleObjectsEx(1, &hCancel, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
+ if (WAIT_OBJECT_0 == status)
+ {
+ return 0;
+ }
+ else if ((WAIT_OBJECT_0 + 1)== status)
+ {
+ while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ //if (!CallMsgFilter(&msg, MSGF_DIALOGBOX))
+ {
+ if (msg.message == WM_QUIT)
+ {
+ PostQuitMessage((INT)msg.wParam);
+ return msg.wParam;
+ }
+
+ if (!TranslateAcceleratorW(hwnd, hAccel, &msg) &&
+ !IsDialogMessageW(hwnd, &msg))
+ {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+#define CBCLASS SetupPage
+START_MULTIPATCH;
+ START_PATCH(MPIID_SETUPPAGE)
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, ADDREF, AddRef);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, RELEASE, Release);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, QUERYINTERFACE, QueryInterface);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, API_SETUPPAGE_GET_NAME, GetName);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, API_SETUPPAGE_CREATEVIEW, CreateView);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, API_SETUPPAGE_SAVE, Save);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, API_SETUPPAGE_REVERT, Revert);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, API_SETUPPAGE_ISDIRTY, IsDirty);
+ M_CB(MPIID_SETUPPAGE, ifc_setuppage, API_SETUPPAGE_VALIDATE, Validate);
+
+ NEXT_PATCH(MPIID_SETUPJOB)
+ M_CB(MPIID_SETUPJOB, ifc_setupjob, ADDREF, AddRef);
+ M_CB(MPIID_SETUPJOB, ifc_setupjob, RELEASE, Release);
+ M_CB(MPIID_SETUPJOB, ifc_setupjob, QUERYINTERFACE, QueryInterface);
+ M_CB(MPIID_SETUPJOB, ifc_setupjob, API_SETUPJOB_EXECUTE, Execute);
+ M_CB(MPIID_SETUPJOB, ifc_setupjob, API_SETUPJOB_CANCEL, Cancel);
+ M_CB(MPIID_SETUPJOB, ifc_setupjob, API_SETUPJOB_ISCANCELSUPPORTED, IsCancelSupported);
+
+ END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupPage.h b/Src/Plugins/Library/ml_online/Setup/setupPage.h
new file mode 100644
index 00000000..ea45357f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupPage.h
@@ -0,0 +1,85 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPPAGE_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPPAGE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <bfc/multipatch.h>
+
+#include "../../winamp/setup/ifc_setuppage.h"
+#include "../../winamp/setup/ifc_setupjob.h"
+
+#include "./setupGroupList.h"
+
+class SetupListboxLabel;
+
+#define ID_KNOWNGROUP 0
+#define ID_FEATUREDGROUP 1
+
+#define MPIID_SETUPPAGE 10
+#define MPIID_SETUPJOB 20
+
+#define SPM_FIRST (WM_APP + 2)
+#define SPM_UPDATELIST (SPM_FIRST + 0)
+
+class SetupPage : public MultiPatch<MPIID_SETUPPAGE, ifc_setuppage>,
+ public MultiPatch<MPIID_SETUPJOB, ifc_setupjob>
+{
+protected:
+ typedef enum
+ {
+ flagInitWasabi = 0x00000001,
+ };
+
+protected:
+ SetupPage();
+ virtual ~SetupPage();
+
+public:
+ static SetupPage* CreateInstance();
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_setuppage */
+ HRESULT GetName(bool bShort, const wchar_t **pszName);
+ HRESULT Save(HWND hText);
+ HRESULT CreateView(HWND hParent, HWND *phwnd);
+ HRESULT Revert(void);
+ HRESULT IsDirty(void);
+ HRESULT Validate(void);
+
+ /* ifc_setupjob */
+ HRESULT Execute(HWND hwndText);
+ HRESULT Cancel(HWND hwndText);
+ HRESULT IsCancelSupported(void);
+
+public:
+ BOOL AttachWindow(HWND hAttach);
+ void DetachWindow();
+
+ void ListboxSelectionChanged();
+ BOOL UpdateListAsync(INT groupId);
+
+protected:
+ HRESULT InitializeServices();
+
+private:
+ size_t ref;
+ HWND hwnd;
+ LPWSTR name;
+ LPWSTR title;
+ SetupGroupList *groupList;
+ HANDLE completeEvent;
+ BOOL servicesInitialized;
+
+protected:
+ RECVS_MULTIPATCH;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPPAGE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupPageWnd.cpp b/Src/Plugins/Library/ml_online/Setup/setupPageWnd.cpp
new file mode 100644
index 00000000..338df688
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupPageWnd.cpp
@@ -0,0 +1,145 @@
+#include "./setupPage.h"
+#include "./setupListbox.h"
+#include "../common.h"
+#include "../resource.h"
+#include "../api__ml_online.h"
+
+static ATOM SETUPPAGE_PROP = 0;
+
+#define GetPage(__hwnd) ((SetupPage*)GetPropW((__hwnd), MAKEINTATOM(SETUPPAGE_PROP)))
+
+static INT_PTR WINAPI SetupPage_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+
+HWND SetupPage_CreateWindow(HWND hParent, SetupPage *page)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_SETUPPAGE, hParent, SetupPage_DialogProc, (LPARAM)page);
+}
+
+static INT_PTR SetupPage_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
+{
+ if (0 == SETUPPAGE_PROP)
+ {
+ SETUPPAGE_PROP = GlobalAddAtom(L"omSetupPageProp");
+ if (0 == SETUPPAGE_PROP) return FALSE;
+ }
+
+ SetupPage *page = (SetupPage*)lParam;
+ if (NULL != page && page->AttachWindow(hwnd))
+ {
+ SetProp(hwnd, MAKEINTATOM(SETUPPAGE_PROP), page);
+ }
+
+ return FALSE;
+}
+
+static void SetupPage_OnDestroy(HWND hwnd)
+{
+ SetupPage *page = GetPage(hwnd);
+ if (NULL != page)
+ {
+ page->DetachWindow();
+ }
+ RemoveProp(hwnd, MAKEINTATOM(SETUPPAGE_PROP));
+}
+
+static void SetupPage_OnCommand(HWND hwnd, INT controlId, INT eventId, HWND hControl)
+{
+ SetupPage *page;
+ switch(controlId)
+ {
+ case IDC_SERVICELIST:
+ switch(eventId)
+ {
+ case LBN_SELCHANGE:
+ page = GetPage(hwnd);
+ if (NULL != page)
+ page->ListboxSelectionChanged();
+ break;
+ }
+ break;
+ }
+}
+
+static BOOL SetupPage_OnMeasureItem(HWND hwnd, MEASUREITEMSTRUCT *pmis)
+{
+ SetupListbox *instance;
+ switch(pmis->CtlID)
+ {
+ case IDC_SERVICELIST:
+ instance = SetupListbox::GetInstance(GetDlgItem(hwnd, pmis->CtlID));
+ if (NULL != instance)
+ {
+ return instance->MeasureItem(pmis->itemID, &pmis->itemWidth, &pmis->itemHeight);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static BOOL SetupPage_OnDrawItem(HWND hwnd, DRAWITEMSTRUCT *pdis)
+{
+ SetupListbox *instance;
+ switch(pdis->CtlID)
+ {
+ case IDC_SERVICELIST:
+ instance = SetupListbox::GetInstance(pdis->hwndItem);
+ if (NULL != instance)
+ {
+ return instance->DrawItem(pdis->hDC, &pdis->rcItem, pdis->itemID, pdis->itemState, pdis->itemAction);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static INT_PTR SetupPage_OnCharToItem(HWND hwnd, INT vKey, INT caretPos, HWND hList)
+{
+
+ if (IDC_SERVICELIST == GetDlgCtrlID(hList))
+ {
+ SetupListbox *instance = SetupListbox::GetInstance(hList);
+ if (NULL != instance)
+ return instance->CharToItem(vKey, caretPos);
+ }
+ return -1;
+}
+
+static INT_PTR SetupPage_OnKeyToItem(HWND hwnd, INT vKey, INT caretPos, HWND hList)
+{
+ if (IDC_SERVICELIST == GetDlgCtrlID(hList))
+ {
+ SetupListbox *instance = SetupListbox::GetInstance(hList);
+ if (NULL != instance)
+ return instance->KeyToItem(vKey, caretPos);
+ }
+ return -1;
+}
+static void SetupPage_OnUpdateList(HWND hwnd, INT groupId)
+{
+ HWND hList = GetDlgItem(hwnd, IDC_SERVICELIST);
+ if (NULL != hList)
+ {
+ SetupListbox *listbox = SetupListbox::GetInstance(hList);
+ if (NULL != listbox)
+ {
+ listbox->UpdateCount();
+ }
+ }
+}
+static INT_PTR WINAPI SetupPage_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return SetupPage_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: SetupPage_OnDestroy(hwnd); break;
+ case WM_COMMAND: SetupPage_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ case WM_MEASUREITEM: return SetupPage_OnMeasureItem(hwnd, (MEASUREITEMSTRUCT*)lParam);
+ case WM_DRAWITEM: return SetupPage_OnDrawItem(hwnd, (DRAWITEMSTRUCT*)lParam);
+ case WM_CHARTOITEM: return SetupPage_OnCharToItem(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
+ case WM_VKEYTOITEM: return SetupPage_OnKeyToItem(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
+ case SPM_UPDATELIST: SetupPage_OnUpdateList(hwnd, (INT)wParam); return TRUE;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupRecord.cpp b/Src/Plugins/Library/ml_online/Setup/setupRecord.cpp
new file mode 100644
index 00000000..995ed222
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupRecord.cpp
@@ -0,0 +1,567 @@
+#include "./main.h"
+#include "../api__ml_online.h"
+#include "./setupRecord.h"
+#include "./setupDetails.h"
+#include "./setupLog.h"
+#include "../resource.h"
+
+#include "./serviceHelper.h"
+#include "./serviceHost.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omwebstorage.h>
+#include <ifc_omserviceenum.h>
+#include <ifc_omservicecopier.h>
+
+#include <wininet.h>
+#include <strsafe.h>
+
+#define RECORD_MARGINCX 6
+#define RECORD_MARGINCY 2
+#define CHECKBOX_MARGIN_RIGHT 2
+
+#define TEXT_OFFSET_LEFT 3
+#define TEXT_OFFSET_BOTTOM RECORD_MARGINCY
+#define TEXT_ALIGN (TA_LEFT | TA_BOTTOM)
+
+SetupRecord::SetupRecord(ifc_omservice *serviceToUse)
+ : ref(1), service(serviceToUse), flags(0), async(NULL)
+{
+ if (NULL != service)
+ {
+ if (S_OK == ServiceHelper_IsSubscribed(service))
+ flags |= recordSelected;
+ service->AddRef();
+ }
+
+ InitializeCriticalSection(&lock);
+}
+
+SetupRecord::~SetupRecord()
+{
+ EnterCriticalSection(&lock);
+
+ if (NULL != async)
+ {
+ ifc_omstorage *storage = NULL;
+ HRESULT hr = OMSERVICEMNGR->QueryStorage(&SUID_OmStorageUrl, &storage);
+ if (SUCCEEDED(hr) && storage != NULL)
+ {
+ storage->RequestAbort(async, TRUE);
+ }
+
+ async->Release();
+ async = NULL;
+ }
+
+ if (NULL != service)
+ service->Release();
+
+ LeaveCriticalSection(&lock);
+ DeleteCriticalSection(&lock);
+}
+
+SetupRecord *SetupRecord::CreateInstance(ifc_omservice *serviceToUse)
+{
+ if (NULL == serviceToUse) return NULL;
+ return new SetupRecord(serviceToUse);
+}
+
+ULONG SetupRecord::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+ULONG SetupRecord::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+HRESULT SetupRecord::GetServiceName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == service) return E_UNEXPECTED;
+ return service->GetName(pszBuffer, cchBufferMax);
+}
+
+HRESULT SetupRecord::GetDisplayName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ HRESULT hr = GetServiceName(pszBuffer, cchBufferMax);
+ if (SUCCEEDED(hr) && L'\0' == *pszBuffer)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEFAULT_SERVICENAME, pszBuffer, cchBufferMax);
+ DownloadDetails();
+ }
+ return hr;
+}
+
+HRESULT SetupRecord::DownloadDetails()
+{
+ HRESULT hr;
+ EnterCriticalSection(&lock);
+ if (NULL == async && 0 == (recordDownloaded & flags))
+ {
+ WCHAR szUrl[INTERNET_MAX_URL_LENGTH] = {0};
+ hr = ServiceHelper_GetDetailsUrl(szUrl, ARRAYSIZE(szUrl), service, FALSE);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage = NULL;
+ hr = OMSERVICEMNGR->QueryStorage(&SUID_OmStorageUrl, &storage);
+ if (SUCCEEDED(hr) && storage != NULL)
+ {
+ ServiceHost *serviceHost = NULL;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ hr = storage->BeginLoad(szUrl, serviceHost, SetupRecord_ServiceDownloadedCallback, this, &async);
+ storage->Release();
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+ }
+ }
+ }
+ else
+ {
+ hr = S_FALSE;
+ }
+ LeaveCriticalSection(&lock);
+ return hr;
+}
+
+HRESULT SetupRecord::Save(SetupLog *log)
+{
+ if (NULL == service) return E_POINTER;
+
+ HRESULT hr = ServiceHelper_Subscribe(service, IsSelected(), SHF_SAVE);
+ if (S_OK == hr)
+ {
+ if (NULL != log)
+ {
+ INT operation = (IsSelected()) ? SetupLog::opServiceAdded : SetupLog::opServiceRemoved;
+ log->LogService(service, operation);
+ }
+
+ }
+
+ return hr;
+}
+
+BOOL SetupRecord::IsModified()
+{
+ if (NULL == service)
+ return FALSE;
+
+ if (S_OK == ServiceHelper_IsSubscribed(service) != (FALSE != IsSelected()))
+ return TRUE;
+ if (S_OK == ServiceHelper_IsModified(service))
+ return TRUE;
+
+ return FALSE;
+}
+
+BOOL SetupRecord::IsSelected()
+{
+ return (0 != (recordSelected & flags));
+}
+
+void SetupRecord::SetSelected(BOOL fSelected)
+{
+ if ((FALSE == fSelected) == !IsSelected())
+ return;
+
+ if (FALSE == fSelected)
+ flags &= ~recordSelected;
+ else
+ flags |= recordSelected;
+}
+
+BOOL SetupRecord::AdjustCheckboxRect(SetupListbox *instance, RECT *prcItem)
+{
+ SIZE checkSize;
+ if (!instance->GetCheckboxMetrics(NULL, IsSelected(), 0, &checkSize))
+ return FALSE;
+
+ prcItem->left += RECORD_MARGINCX;
+ prcItem->right = prcItem->left + checkSize.cx;
+
+ if (checkSize.cy > (prcItem->bottom - prcItem->top))
+ checkSize.cy = (prcItem->bottom - prcItem->top);
+ prcItem->top += ((prcItem->bottom - prcItem->top) - checkSize.cy) / 2;
+ prcItem->bottom = prcItem->top + checkSize.cy;
+ return TRUE;
+}
+
+BOOL SetupRecord::MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy)
+{
+ HDC hdc = GetDCEx(instance->GetHwnd(), NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == hdc) return FALSE;
+
+ HFONT originalFont = (HFONT)SelectObject(hdc, instance->GetFont());
+
+ SIZE checkSize;
+ instance->GetCheckboxMetrics(hdc, IsSelected(), 0, &checkSize);
+
+ if (NULL != cy)
+ {
+ *cy = 0;
+ TEXTMETRIC tm = {0};
+ if (GetTextMetrics(hdc, &tm))
+ {
+ *cy = tm.tmHeight + tm.tmExternalLeading;
+ if (checkSize.cy > (INT)*cy) *cy = checkSize.cy;
+ *cy += RECORD_MARGINCY*2;
+ }
+ }
+
+ if (NULL != cx)
+ {
+ *cx = checkSize.cx;
+ WCHAR szBuffer[128] = {0};
+ if (SUCCEEDED(GetDisplayName(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ INT cchBuffer = lstrlenW(szBuffer);
+ SIZE textSize;
+ if (0 != cchBuffer && GetTextExtentPoint32(hdc, szBuffer, cchBuffer, &textSize))
+ {
+ *cx += textSize.cx;
+ if (0 != checkSize.cx)
+ *cx += CHECKBOX_MARGIN_RIGHT + RECORD_MARGINCX;
+ }
+ }
+ if (0 != *cx) *cx += RECORD_MARGINCX*2;
+ }
+
+ SelectObject(hdc, originalFont);
+ ReleaseDC(instance->GetHwnd(), hdc);
+ return TRUE;
+}
+
+void SetupRecord::GetColors(HDC hdc, UINT state, COLORREF *rgbBkOut, COLORREF *rgbTextOut)
+{
+ COLORREF rgbBk, rgbText;
+
+ if (0 != (ODS_DISABLED & state))
+ {
+ rgbBk = GetBkColor(hdc);
+ rgbText = GetSysColor(COLOR_GRAYTEXT);
+ }
+ else if (0 != (ODS_SELECTED & state))
+ {
+ if (0 == (ODS_INACTIVE & state))
+ {
+ rgbBk = GetSysColor(COLOR_HIGHLIGHT);
+ rgbText = GetSysColor(COLOR_HIGHLIGHTTEXT);
+ }
+ else
+ {
+ rgbBk = GetSysColor(COLOR_3DFACE);
+ rgbText = GetTextColor(hdc);
+ }
+ }
+ else
+ {
+ rgbBk = GetBkColor(hdc);
+ rgbText = GetTextColor(hdc);
+ }
+
+ if (NULL != rgbBkOut) *rgbBkOut = rgbBk;
+ if (NULL != rgbTextOut) *rgbTextOut = rgbText;
+}
+
+BOOL SetupRecord::DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state)
+{
+ LONG paintLeft = prc->left + RECORD_MARGINCX;
+ RECT partRect;
+
+ UINT checkState = state & ~ODS_SELECTED;
+ if (0 == (checkboxPressed & flags))
+ {
+ if (0 != (checkboxHighlighted & flags))
+ checkState |= ODS_HOTLIGHT;
+ }
+ else
+ {
+ checkState |= ((0 != (checkboxHighlighted & flags)) ? ODS_SELECTED : ODS_HOTLIGHT);
+ }
+
+ HRGN backRgn, rgn;
+ backRgn = CreateRectRgnIndirect(prc);
+ rgn = CreateRectRgn(0,0,0,0);
+
+ SetRectEmpty(&partRect);
+ instance->GetCheckboxMetrics(hdc, IsSelected(), checkState, (((SIZE*)&partRect) + 1));
+
+ INT space = (prc->bottom - prc->top) - (partRect.bottom- partRect.top);
+ INT offsetY = space / 2 + space%2;
+ if (offsetY < 0) offsetY = 0;
+
+ OffsetRect(&partRect, paintLeft, prc->top + offsetY);
+ if (instance->DrawCheckbox(hdc, IsSelected(), checkState, &partRect, prc))
+ {
+ paintLeft = partRect.right + CHECKBOX_MARGIN_RIGHT;
+ if (SetRectRgn(rgn, partRect.left, partRect.top, partRect.right, partRect.bottom))
+ CombineRgn(backRgn, backRgn, rgn, RGN_DIFF);
+
+ }
+
+ COLORREF rgbBk, rgbText;
+ GetColors(hdc, state, &rgbBk, &rgbText);
+
+ COLORREF origBk = SetBkColor(hdc, rgbBk);
+ COLORREF origText = SetTextColor(hdc, rgbText);
+ UINT textAlign = SetTextAlign(hdc, TEXT_ALIGN);
+
+ WCHAR szBuffer[128] = {0};
+ INT cchBuffer = 0;
+ if (SUCCEEDED(GetDisplayName(szBuffer, ARRAYSIZE(szBuffer))))
+ cchBuffer = lstrlenW(szBuffer);
+
+ SetRect(&partRect, paintLeft, prc->top, prc->right, prc->bottom);
+ if (ExtTextOut(hdc, partRect.left + TEXT_OFFSET_LEFT, partRect.bottom - TEXT_OFFSET_BOTTOM,
+ ETO_OPAQUE | ETO_CLIPPED, &partRect, szBuffer, cchBuffer, NULL))
+ {
+ if (SetRectRgn(rgn, partRect.left, partRect.top, partRect.right, partRect.bottom))
+ CombineRgn(backRgn, backRgn, rgn, RGN_DIFF);
+ }
+
+ if (ODS_FOCUS == ((ODS_FOCUS | 0x0200/*ODS_NOFOCUSRECT*/) & state))
+ DrawFocusRect(hdc, &partRect);
+
+ if (NULL != backRgn)
+ {
+ PaintRgn(hdc, backRgn);
+ DeleteObject(backRgn);
+ }
+ if (NULL != rgn)
+ DeleteObject(rgn);
+
+ if (TEXT_ALIGN != textAlign) SetTextAlign(hdc, textAlign);
+ if (origBk != rgbBk) SetBkColor(hdc, origBk);
+ if (origText != rgbText) SetTextColor(hdc, origText);
+
+ return TRUE;
+}
+
+INT_PTR SetupRecord::KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey)
+{
+ switch(vKey)
+ {
+ case VK_SPACE:
+ InvertCheckbox(instance, prcItem);
+ return -2;
+ }
+ return -1;
+}
+
+BOOL SetupRecord::MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ RECT checkboxRect;
+ BOOL fInvalidate = FALSE;
+ CopyRect(&checkboxRect, prcItem);
+ AdjustCheckboxRect(instance, &checkboxRect);
+
+ if (prcItem->top <= pt.y && pt.y < prcItem->bottom &&
+ checkboxRect.left <= pt.x && pt.x < checkboxRect.right)
+ {
+ if (0 == (checkboxHighlighted & flags))
+ {
+ flags |= checkboxHighlighted;
+ fInvalidate = TRUE;
+ }
+ }
+ else
+ {
+ if (0 != (checkboxHighlighted & flags))
+ {
+ flags &= ~checkboxHighlighted;
+ fInvalidate = TRUE;
+ }
+ }
+
+ if (FALSE != fInvalidate)
+ instance->InvalidateRect(&checkboxRect, FALSE);
+
+ return FALSE;
+}
+
+BOOL SetupRecord::MouseLeave(SetupListbox *instance, const RECT *prcItem)
+{
+ if (0 != (checkboxHighlighted & flags))
+ {
+ flags &= ~checkboxHighlighted;
+ RECT checkboxRect;
+ CopyRect(&checkboxRect, prcItem);
+ AdjustCheckboxRect(instance, &checkboxRect);
+ instance->InvalidateRect(&checkboxRect, FALSE);
+ }
+ return FALSE;
+}
+
+BOOL SetupRecord::LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ RECT checkboxRect;
+ BOOL handled = FALSE;
+ CopyRect(&checkboxRect, prcItem);
+ AdjustCheckboxRect(instance, &checkboxRect);
+
+ if (prcItem->top <= pt.y && pt.y < prcItem->bottom &&
+ prcItem->left <= pt.x && pt.x < (checkboxRect.right + CHECKBOX_MARGIN_RIGHT))
+ {
+ handled = TRUE;
+ if (checkboxRect.left <= pt.x && pt.x < checkboxRect.right)
+ {
+ flags |= (checkboxHighlighted | checkboxPressed);
+ instance->SetCapture(this);
+ instance->InvalidateRect(&checkboxRect, FALSE);
+ }
+ }
+ return handled;
+}
+
+BOOL SetupRecord::LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ RECT checkboxRect;
+ BOOL handled = FALSE;
+ CopyRect(&checkboxRect, prcItem);
+ AdjustCheckboxRect(instance, &checkboxRect);
+
+ if (0 != (checkboxPressed & flags))
+ {
+ flags &= ~checkboxPressed;
+ if (this == instance->GetCapture())
+ instance->ReleaseCapture();
+
+ if (prcItem->top <= pt.y && pt.y < prcItem->bottom &&
+ checkboxRect.left <= pt.x && pt.x < checkboxRect.right)
+ {
+ SetSelected(!IsSelected());
+ handled = TRUE;
+ }
+ instance->InvalidateRect(&checkboxRect, FALSE);
+ }
+ return handled;
+}
+
+BOOL SetupRecord::LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ InvertCheckbox(instance, prcItem);
+ return TRUE;
+}
+
+BOOL SetupRecord::RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+
+BOOL SetupRecord::RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt)
+{
+ return FALSE;
+}
+
+void SetupRecord::CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured)
+{
+}
+
+void SetupRecord::InvertCheckbox(SetupListbox *instance, const RECT *prcItem)
+{
+ SetSelected(!IsSelected());
+
+ if (NULL != instance && NULL != prcItem)
+ {
+ RECT checkboxRect;
+ CopyRect(&checkboxRect, prcItem);
+ AdjustCheckboxRect(instance, &checkboxRect);
+ instance->InvalidateRect(&checkboxRect, FALSE);
+ }
+}
+
+HWND SetupRecord::CreateDetailsView(HWND hParent)
+{
+ DownloadDetails();
+
+ WCHAR szName[64] = {0};
+ if (FALSE == GetUniqueName(szName, ARRAYSIZE(szName)))
+ szName[0] = L'\0';
+
+ return SetupDetails_CreateServiceView(hParent, szName, service);
+}
+
+BOOL SetupRecord::GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer ||
+ FAILED(StringCchPrintf(pszBuffer, cchBufferMax, L"record_svc_%u", service->GetId())))
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void SetupRecord::OnDownloadCompleted()
+{
+ ifc_omstorage *storage = NULL;
+ HRESULT hr = OMSERVICEMNGR->QueryStorage(&SUID_OmStorageUrl, &storage);
+ if (SUCCEEDED(hr) && service != NULL)
+ {
+ ifc_omserviceenum *serviceEnum = NULL;
+ hr = storage->EndLoad(async, &serviceEnum);
+ if (SUCCEEDED(hr) && serviceEnum != NULL)
+ {
+ EnterCriticalSection(&lock);
+
+ ifc_omservice *result = NULL;
+ while(S_OK == serviceEnum->Next(1, &result, NULL))
+ {
+ if (result)
+ {
+ if (result->GetId() == service->GetId())
+ {
+ ifc_omservicecopier *copier;
+ if (SUCCEEDED(result->QueryInterface(IFC_OmServiceCopier, (void**)&copier)))
+ {
+ copier->CopyTo(service, NULL);
+ copier->Release();
+ }
+ result->Release();
+ flags |= recordDownloaded;
+ break;
+ }
+ else
+ {
+ result->Release();
+ }
+ }
+ result = NULL;
+ }
+
+ LeaveCriticalSection(&lock);
+
+ serviceEnum->Release();
+ }
+
+ storage->Release();
+ }
+
+ EnterCriticalSection(&lock);
+
+ async->Release();
+ async = NULL;
+
+ LeaveCriticalSection(&lock);
+}
+
+void CALLBACK SetupRecord_ServiceDownloadedCallback(ifc_omstorageasync *result)
+{
+ if (NULL == result) return;
+ SetupRecord *record = NULL;
+ if (SUCCEEDED(result->GetData((void**)&record)) && NULL != record)
+ {
+ record->OnDownloadCompleted();
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupRecord.h b/Src/Plugins/Library/ml_online/Setup/setupRecord.h
new file mode 100644
index 00000000..b6573e1a
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupRecord.h
@@ -0,0 +1,84 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPRECORD_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPRECORD_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "./setupListbox.h"
+
+class ifc_omservice;
+class ifc_omstorageasync;
+class SetupLog;
+
+class SetupRecord : public SetupListboxItem
+
+{
+protected:
+ typedef enum
+ {
+ recordSelected = 0x0001,
+ recordDownloaded = 0x0002,
+ checkboxHighlighted = 0x0100,
+ checkboxPressed = 0x0200,
+ } RecordFlags;
+
+protected:
+ SetupRecord(ifc_omservice *serviceToUse);
+ ~SetupRecord();
+
+public:
+ static SetupRecord *CreateInstance(ifc_omservice *serviceToUse);
+
+public:
+ ULONG AddRef();
+ ULONG Release();
+
+ ifc_omservice *GetService() { return service; }
+
+ BOOL IsModified();
+ BOOL IsSelected();
+ void SetSelected(BOOL fSelected);
+
+ HRESULT GetServiceName(LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT GetDisplayName(LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT Save(SetupLog *log);
+ HRESULT DownloadDetails();
+
+ /* SetupListboxItem */
+ BOOL MeasureItem(SetupListbox *instance, UINT *cx, UINT *cy);
+ BOOL DrawItem(SetupListbox *instance, HDC hdc, const RECT *prc, UINT state);
+ INT_PTR KeyToItem(SetupListbox *instance, const RECT *prcItem, INT vKey);
+ BOOL MouseMove(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL MouseLeave(SetupListbox *instance, const RECT *prcItem);
+ BOOL LButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL LButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL LButtonDblClk(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL RButtonDown(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ BOOL RButtonUp(SetupListbox *instance, const RECT *prcItem, UINT mouseFlags, POINT pt);
+ void CaptureChanged(SetupListbox *instance, const RECT *prcItem, SetupListboxItem *captured);
+ BOOL IsDisabled() { return FALSE; }
+ void Command(SetupListbox *instance, INT commandId, INT eventId) {}
+ HWND CreateDetailsView(HWND hParent);
+ BOOL GetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax);
+
+
+protected:
+ BOOL AdjustCheckboxRect(SetupListbox *instance, RECT *prcItem);
+ void GetColors(HDC hdc, UINT state, COLORREF *rgbBkOut, COLORREF *rgbTextOut);
+ void InvertCheckbox(SetupListbox *instance, const RECT *prcItem);
+ void OnDownloadCompleted();
+
+private:
+ friend static void CALLBACK SetupRecord_ServiceDownloadedCallback(ifc_omstorageasync *result);
+
+protected:
+ ULONG ref;
+ ifc_omservice *service;
+ ifc_omstorageasync *async;
+ CRITICAL_SECTION lock;
+ UINT flags;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUPRECORD_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/Setup/setupServicePanel.cpp b/Src/Plugins/Library/ml_online/Setup/setupServicePanel.cpp
new file mode 100644
index 00000000..eb7c2737
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupServicePanel.cpp
@@ -0,0 +1,797 @@
+#include "../common.h"
+#include "./setupServicePanel.h"
+#include "./setupDetails.h"
+#include "./setupPage.h"
+#include "../resource.h"
+#include "../api__ml_online.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omservicedetails.h>
+#include <ifc_omcachemanager.h>
+#include <ifc_omcachegroup.h>
+#include <ifc_omcacherecord.h>
+#include <ifc_imageloader.h>
+#include <ifc_omgraphics.h>
+#include <ifc_omserviceeventmngr.h>
+#include <ifc_omserviceeditor.h>
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define GetPanel(__hwnd) ((ServicePanel*)GetPropW((__hwnd), MAKEINTATOM(DETAILS_PROP)))
+
+#define GET_IDETAILS(__service, __details)\
+ (NULL != (service) && SUCCEEDED((service)->QueryInterface(IFC_OmServiceDetails, (void**)&(__details))))
+
+
+ServicePanel::ServicePanel(LPCWSTR pszName, ifc_omservice *service)
+ : ref(1), name(NULL), service(NULL), hwnd(NULL), fontTitle(NULL), fontMeta(NULL), thumbnailCache(NULL)
+{
+ name = Plugin_CopyString(pszName);
+ this->service = service;
+ if (NULL != service)
+ service->AddRef();
+}
+
+ServicePanel::~ServicePanel()
+{
+ Plugin_FreeString(name);
+
+ if (NULL != service)
+ service->Release();
+
+ if (NULL != fontTitle)
+ DeleteObject(fontTitle);
+
+ if (NULL != fontMeta)
+ DeleteObject(fontMeta);
+
+ if (NULL != thumbnailCache)
+ thumbnailCache->Release();
+}
+
+HWND ServicePanel::CreateInstance(HWND hParent, LPCWSTR pszName, ifc_omservice *service, ServicePanel **instance)
+{
+ ServicePanel *panel = new ServicePanel(pszName, service);
+ if (NULL == panel)
+ {
+ if (NULL != instance) *instance = NULL;
+ return NULL;
+ }
+
+ HWND hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_SETUP_SERVICEDETAILS, hParent, ServicePanel_DialogProc, (LPARAM)panel);
+
+ if (NULL != instance)
+ {
+ if (NULL != hwnd)
+ {
+ *instance = panel;
+ panel->AddRef();
+ }
+ else
+ *instance = NULL;
+ }
+
+ panel->Release();
+ return hwnd;
+}
+
+size_t ServicePanel::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t ServicePanel::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int ServicePanel::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_OmServiceEvent))
+ *object = static_cast<ifc_omserviceevent*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+static void CALLBACK ThreadCallback_ServiceChange(Dispatchable *instance, ULONG_PTR param1, ULONG_PTR param2)
+{
+ ifc_omserviceevent *panel = (ifc_omserviceevent*)instance;
+ ifc_omservice *service = (ifc_omservice*)param1;
+ if (NULL != service)
+ {
+ if (NULL != panel)
+ panel->ServiceChange(service, (UINT)param2);
+ service->Release();
+ }
+}
+void ServicePanel::ServiceChange(ifc_omservice *service, unsigned int modifiedFlags)
+{
+
+ DWORD currentTID = GetCurrentThreadId();
+ DWORD windowTID = GetWindowThreadProcessId(hwnd, NULL);
+ if (NULL != windowTID && currentTID != windowTID)
+ {
+ if(NULL != OMUTILITY)
+ {
+ service->AddRef();
+ if (FAILED(OMUTILITY->PostMainThreadCallback2(ThreadCallback_ServiceChange, (ifc_omserviceevent*)this, (ULONG_PTR)service, (ULONG_PTR)modifiedFlags)))
+ service->Release();
+ }
+ return;
+ }
+
+
+ if ( 0 != (ifc_omserviceeditor::modifiedName & modifiedFlags))
+ {
+ UpdateName();
+ HWND hPage = GetParent(hwnd);
+ if (NULL != hPage)
+ PostMessage(hPage, SPM_UPDATELIST, (WPARAM)service->GetId(), NULL);
+ }
+
+ if ( 0 != (ifc_omserviceeditor::modifiedDescription & modifiedFlags))
+ UpdateDescription();
+
+ if ( 0 != (ifc_omserviceeditor::modifiedThumbnail& modifiedFlags))
+ UpdateThumbnail();
+
+ if ( 0 != ((ifc_omserviceeditor::modifiedAuthorFirst |
+ ifc_omserviceeditor::modifiedAuthorLast |
+ ifc_omserviceeditor::modifiedUpdated |
+ ifc_omserviceeditor::modifiedPublished) & modifiedFlags))
+ {
+ UpdateMeta();
+ }
+
+}
+
+HRESULT ServicePanel::LoadLocalThumbnail(LPCWSTR pszPath)
+{
+ HWND hThumbnail = GetDlgItem(hwnd, IDC_THUMBNAIL);
+ if (NULL == hThumbnail) return E_UNEXPECTED;
+
+ SendMessage(hThumbnail, WM_SETREDRAW, FALSE, 0L);
+
+ HBITMAP hBitmap = NULL;
+
+ BITMAPINFOHEADER header;
+ void *pixelData;
+
+ ifc_omimageloader *imageLoader;
+ if (SUCCEEDED(OMUTILITY->QueryImageLoader(NULL, pszPath, FALSE, &imageLoader)))
+ {
+ imageLoader->LoadBitmapEx(&hBitmap, &header, &pixelData);
+ imageLoader->Release();
+ }
+
+ if (NULL == hBitmap &&
+ SUCCEEDED(OMUTILITY->QueryImageLoader(WASABI_API_ORIG_HINST, MAKEINTRESOURCE(IDR_SERVICE64X64_IMAGE), FALSE, &imageLoader)))
+ {
+ imageLoader->LoadBitmapEx(&hBitmap, &header, &pixelData);
+ imageLoader->Release();
+ }
+
+ HBITMAP hTest = (HBITMAP)SendMessage(hThumbnail, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBitmap);
+ if (NULL != hTest)
+ DeleteObject(hTest);
+
+ if (NULL != hBitmap)
+ {
+ hTest = (HBITMAP)SendMessage(hThumbnail, STM_GETIMAGE, IMAGE_BITMAP, 0L);
+ if (hTest != hBitmap)
+ { // this is XP and up image copy was created and alpha channel will be handled properly
+ DeleteObject(hBitmap);
+ }
+ else
+ { // fix alpha channel
+ if (32 == header.biBitCount)
+ {
+ HDC hdcFixed = CreateCompatibleDC(NULL);
+ if (NULL != hdcFixed)
+ {
+ BITMAPINFOHEADER headerFixed;
+ CopyMemory(&headerFixed, &header, sizeof(BITMAPINFOHEADER));
+ BYTE *pixelsFixed;
+ INT cx = header.biWidth;
+ INT cy = abs(header.biHeight);
+ HBITMAP bitmapFixed = CreateDIBSection(NULL, (LPBITMAPINFO)&headerFixed, DIB_RGB_COLORS, (void**)&pixelsFixed, NULL, 0);
+
+ if (NULL != bitmapFixed)
+ {
+ HBITMAP bitmapOrig = (HBITMAP)SelectObject(hdcFixed, bitmapFixed);
+ HBRUSH hb = (HBRUSH)SendMessage(hwnd, WM_CTLCOLORDLG, (WPARAM)hdcFixed, (LPARAM)hwnd);
+ if (NULL == hb)
+ hb = GetSysColorBrush(COLOR_3DFACE);
+ RECT rect;
+ SetRect(&rect, 0, 0, cx, cy);
+ FillRect(hdcFixed, &rect, hb);
+
+ ifc_omgraphics *graphics;
+ if (SUCCEEDED(OMUTILITY->GetGraphics(&graphics)))
+ {
+ HDC hdcSrc = CreateCompatibleDC(NULL);
+ if (NULL != hdcSrc)
+ {
+ HBITMAP bitmapSrcOrig = (HBITMAP)SelectObject(hdcSrc, hBitmap);
+ BLENDFUNCTION bf;
+ bf.BlendOp = AC_SRC_OVER;
+ bf.BlendFlags = 0;
+ bf.SourceConstantAlpha = 0xFF;
+ bf.AlphaFormat = AC_SRC_ALPHA;
+
+ RECT blendRect;
+ SetRect(&blendRect, 0, 0, cx, cy);
+
+ graphics->Premultiply((BYTE*)pixelData, cx, cy);
+ graphics->AlphaBlend(hdcFixed, &blendRect, hdcSrc, &blendRect, bf);
+
+ SelectObject(hdcSrc, bitmapSrcOrig);
+ DeleteDC(hdcSrc);
+ }
+ graphics->Release();
+ }
+
+ SelectObject(hdcFixed, bitmapOrig);
+ SendMessage(hThumbnail, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bitmapFixed);
+ DeleteObject(hBitmap);
+
+ }
+ DeleteDC(hdcFixed);
+ }
+ }
+ }
+ }
+
+ RECT clientRect;
+ if (GetClientRect(hThumbnail, &clientRect))
+ {
+ INT cx = clientRect.right - clientRect.left;
+ INT cy = clientRect.bottom - clientRect.top;
+ if (64 != cx || 64 != cy)
+ {
+ SetWindowPos(hThumbnail, NULL, 0, 0, 64, 64, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+ }
+
+ SendMessage(hThumbnail, WM_SETREDRAW, TRUE, 0L);
+
+ if (0 != ShowWindow(hThumbnail, (NULL != hBitmap) ? SW_SHOWNA : SW_HIDE))
+ InvalidateRect(hThumbnail, NULL, TRUE);
+
+ return S_OK;
+}
+
+static void CALLBACK ThreadCallback_PathChanged(Dispatchable *instance, ULONG_PTR param1, ULONG_PTR param2)
+{
+ ifc_omcachecallback *panel = (ifc_omcachecallback*)instance;
+ ifc_omcacherecord *record = (ifc_omcacherecord*)param1;
+ if (NULL != record)
+ {
+ if (NULL != panel)
+ panel->PathChanged(record);
+ record->Release();
+ }
+}
+
+void ServicePanel::PathChanged(ifc_omcacherecord *record)
+{
+ if (NULL == hwnd || FALSE == IsWindow(hwnd))
+ return;
+
+ DWORD currentTID = GetCurrentThreadId();
+ DWORD windowTID = GetWindowThreadProcessId(hwnd, NULL);
+ if (NULL != windowTID && currentTID != windowTID)
+ {
+ if(NULL != OMUTILITY)
+ {
+ record->AddRef();
+ if (FAILED(OMUTILITY->PostMainThreadCallback2(ThreadCallback_PathChanged, (ifc_omcachecallback*)this, (ULONG_PTR)record, 0L)))
+ record->Release();
+ }
+ return;
+ }
+
+ WCHAR szPath[2048] = {0};
+ if (FAILED(record->GetPath(szPath, ARRAYSIZE(szPath))))
+ szPath[0] = L'\0';
+
+ LoadLocalThumbnail(szPath);
+}
+
+void ServicePanel::Attach(HWND hwnd)
+{
+ this->hwnd = hwnd;
+ if (NULL != hwnd &&
+ FALSE != SetProp(hwnd, MAKEINTATOM(DETAILS_PROP), this))
+ {
+ AddRef();
+ }
+}
+
+void ServicePanel::Detach()
+{
+ RemoveProp(hwnd, MAKEINTATOM(DETAILS_PROP));
+
+ if (NULL != thumbnailCache)
+ {
+ thumbnailCache->UnregisterCallback(this);
+ thumbnailCache->Release();
+ thumbnailCache = NULL;
+ }
+
+ if (NULL != service)
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->UnregisterHandler(this);
+ eventManager->Release();
+ }
+ }
+
+ Release();
+}
+
+HFONT ServicePanel::PickTitleFont(LPCWSTR pszTitle, INT cchTitle, INT maxWidth)
+{
+ HFONT dialogFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+
+ LOGFONT lf;
+ if (0 == GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ return NULL;
+
+ HFONT titleFont = NULL;
+ if (cchTitle > 0)
+ {
+ LOGFONT lf;
+ if (0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Arial Bold");
+ lf.lfWidth = 0;
+ lf.lfWeight = FW_DONTCARE;
+ lf.lfQuality = 5/*ANTIALIASED_QUALITY*/;
+
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ HFONT origFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
+ SIZE textSize;
+
+ INT heightLimit = (lf.lfHeight < 0) ? 1 : -1;
+ lf.lfHeight += (lf.lfHeight < 0) ? -2 : +2;
+ do
+ {
+ textSize.cx = 0;
+ if (NULL != titleFont) DeleteObject(titleFont);
+ titleFont = CreateFontIndirect(&lf);
+ if (NULL != titleFont)
+ {
+ SelectObject(hdc, titleFont);
+ GetTextExtentPoint32(hdc, pszTitle, cchTitle, &textSize);
+ }
+ lf.lfHeight += (lf.lfHeight < 0) ? 1 : -1;
+
+ } while(textSize.cx > maxWidth && lf.lfHeight != heightLimit);
+
+ if (0 == textSize.cx)
+ {
+ DeleteObject(titleFont);
+ titleFont = NULL;
+ }
+
+ SelectObject(hdc, origFont);
+ ReleaseDC(hwnd, hdc);
+ }
+ }
+ }
+
+ if (NULL == titleFont &&
+ 0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ titleFont = CreateFontIndirect(&lf);
+ }
+ return titleFont;
+}
+
+LPCWSTR ServicePanel::FormatDate(LPCWSTR pszDate, LPWSTR pszBuffer, INT cchBufferMax)
+{
+ SYSTEMTIME st;
+ ZeroMemory(&st, sizeof(SYSTEMTIME));
+ LPCWSTR cursor;
+
+ cursor = pszDate;
+ INT index = 0;
+
+ for(;;)
+ {
+
+ INT iVal;
+
+ if (FALSE == StrToIntEx(cursor, STIF_DEFAULT, &iVal) || iVal < 1)
+ {
+ index = 0;
+ break;
+ }
+
+ if (0 == index)
+ {
+ if (iVal < 2000 || iVal > 2100)
+ break;
+ st.wYear = iVal;
+ index++;
+ }
+ else if (1 == index)
+ {
+ if (iVal < 1 || iVal > 12)
+ break;
+ st.wMonth = iVal;
+ index++;
+ }
+ else if (2 == index)
+ {
+ if (iVal < 1 || iVal > 31)
+ break;
+ st.wDay = iVal;
+ index++;
+ }
+ else
+ {
+ index = 0;
+ break;
+ }
+
+ while(L'\0' != *cursor && L'-' != *cursor) cursor++;
+ if (L'-' == *cursor) cursor++;
+ if (L'\0' == *cursor)
+ break;
+
+ }
+
+ if (3 == index &&
+ 0 != GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, pszBuffer, cchBufferMax))
+ {
+ return pszBuffer;
+ }
+
+ return pszDate;
+}
+
+
+
+HRESULT ServicePanel::GetFullName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+ *pszBuffer = L'\0';
+
+ if (NULL == service) return E_UNEXPECTED;
+
+ ifc_omservicedetails *details;
+ HRESULT hr = service->QueryInterface(IFC_OmServiceDetails, (void**)&details);
+ if (SUCCEEDED(hr))
+ {
+ hr = details->GetAuthorFirst(pszBuffer, cchBufferMax);
+ if (SUCCEEDED(hr))
+ {
+ UINT cchBuffer = lstrlen(pszBuffer);
+ LPWSTR cursor = pszBuffer + cchBuffer;
+ size_t remaining = cchBufferMax - cchBuffer;
+
+ if (cursor != pszBuffer)
+ {
+ hr = StringCchCopyEx(cursor, remaining, L" ", &cursor, &remaining, 0);
+ if (SUCCEEDED(hr))
+ {
+ hr = details->GetAuthorLast(cursor, (UINT)remaining);
+ if (FAILED(hr) || L'\0' == *cursor)
+ {
+ pszBuffer[cchBuffer] = L'\0';
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+void ServicePanel::UpdateName()
+{
+ HWND hTitle = GetDlgItem(hwnd, IDC_TITLE);
+ if (NULL == hTitle) return;
+
+ WCHAR szBuffer[128] = {0};
+ if (NULL == service ||
+ FAILED(service->GetName(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ szBuffer[0] = L'\0';
+ }
+
+ INT cchBuffer = lstrlen(szBuffer);
+ RECT rc;
+ GetClientRect(hTitle, &rc);
+ HFONT font = PickTitleFont(szBuffer, cchBuffer, rc.right - rc.left);
+ if (NULL != font)
+ {
+ if (NULL != fontTitle)
+ DeleteObject(fontTitle);
+
+ fontTitle = font;
+ SendMessage(hTitle, WM_SETFONT, (WPARAM)fontTitle, 0L);
+ }
+
+
+ SetWindowText(hTitle, szBuffer);
+ InvalidateRect(hTitle, NULL, TRUE);
+}
+
+
+void ServicePanel::UpdateDescription()
+{
+ HWND hDescription = GetDlgItem(hwnd, IDC_DESCRIPTION);
+ if (NULL == hDescription) return;
+
+ WCHAR szBuffer[4096] = {0};
+ ifc_omservicedetails *details = 0;
+ if (GET_IDETAILS(service, details))
+ {
+ details->GetDescription(szBuffer, ARRAYSIZE(szBuffer));
+ details->Release();
+ }
+
+ SetupDetails_SetDescription(hDescription, szBuffer);
+}
+
+void ServicePanel::UpdateMeta()
+{
+ HWND hMeta = GetDlgItem(hwnd, IDC_SERVICEMETA);
+ if (NULL == hMeta) return;
+
+ WCHAR szBuffer[512] = {0};
+ ifc_omservicedetails *svcdetails = 0;
+ if (GET_IDETAILS(service, svcdetails))
+ {
+ WCHAR szValue[256] = {0}, szPrefix[64] = {0};
+ HRESULT hr = S_OK;
+ LPWSTR cursor = szBuffer;
+ size_t remaining = ARRAYSIZE(szBuffer);
+
+ if (SUCCEEDED(GetFullName(szValue, ARRAYSIZE(szValue))) && L'\0' != szValue[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SERVICE_BYAUTHOR, szPrefix, ARRAYSIZE(szPrefix));
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"%s%s", szPrefix, szValue);
+ }
+
+ if (SUCCEEDED(svcdetails->GetUpdated(szValue, ARRAYSIZE(szValue))) && L'\0' != szValue[0])
+ {
+ if (cursor != szBuffer)
+ hr = StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ if (SUCCEEDED(hr))
+ {
+ WCHAR szDate[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SERVICE_LASTUPDATED, szPrefix, ARRAYSIZE(szPrefix));
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"%s%s", szPrefix, FormatDate(szValue, szDate, ARRAYSIZE(szDate)));
+ }
+ }
+
+ svcdetails->Release();
+ }
+
+ if (NULL == fontMeta)
+ {
+ HFONT dialogFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+ LOGFONT lf;
+ if (0 != GetObject(dialogFont, sizeof(LOGFONT), &lf))
+ {
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Tahoma");
+ lf.lfWidth = 0;
+ lf.lfHeight += (lf.lfHeight < 0) ? 1 : -1;
+ lf.lfQuality = ANTIALIASED_QUALITY;
+ fontMeta = CreateFontIndirect(&lf);
+ }
+
+ if (NULL != fontMeta)
+ {
+ SendMessage(hMeta, WM_SETFONT, (WPARAM)fontMeta, 0L);
+ }
+ }
+
+ SetWindowText(hMeta, szBuffer);
+ if (0 != ShowWindow(hMeta, (L'\0' != szBuffer[0]) ? SW_SHOWNA : SW_HIDE))
+ InvalidateRect(hMeta, NULL, TRUE);
+}
+
+void ServicePanel::UpdateThumbnail()
+{
+ if (NULL != thumbnailCache)
+ {
+ thumbnailCache->UnregisterCallback(this);
+ thumbnailCache->Release();
+ thumbnailCache = NULL;
+ }
+ LoadLocalThumbnail(NULL);
+
+ ifc_omservicedetails *details = 0;
+ if (GET_IDETAILS(service, details))
+ {
+ WCHAR szPath[2048] = {0};
+ if (SUCCEEDED(details->GetThumbnail(szPath, ARRAYSIZE(szPath))) && L'\0' != szPath[0])
+ {
+ ifc_omcachemanager *cacheManager;
+ if (SUCCEEDED(OMUTILITY->GetCacheManager(&cacheManager)))
+ {
+ ifc_omcachegroup *cacheGroup;
+ if (SUCCEEDED(cacheManager->Find(L"thumbnails", TRUE, &cacheGroup, NULL)))
+ {
+
+ if (SUCCEEDED(cacheGroup->Find(szPath, TRUE, &thumbnailCache, FALSE)))
+ {
+ thumbnailCache->RegisterCallback(this);
+ if (SUCCEEDED(thumbnailCache->GetPath(szPath, ARRAYSIZE(szPath))))
+ LoadLocalThumbnail(szPath);
+ }
+ cacheGroup->Release();
+ }
+ cacheManager->Release();
+ }
+ }
+ details->Release();
+ }
+}
+
+
+
+
+INT_PTR ServicePanel::OnInitDialog(HWND hFocus, LPARAM lParam)
+{
+ UpdateName();
+ UpdateDescription();
+ UpdateThumbnail();
+ UpdateMeta();
+
+ if (NULL != service)
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->RegisterHandler(this);
+ eventManager->Release();
+ }
+ }
+
+ return FALSE;
+}
+
+void ServicePanel::OnDestroy()
+{
+
+ HWND hThumbnail = GetDlgItem(hwnd, IDC_THUMBNAIL);
+ if (NULL != hThumbnail)
+ {
+ HBITMAP hBitmap = (HBITMAP)SendMessage(hThumbnail, STM_SETIMAGE, IMAGE_BITMAP, 0L);
+ if (NULL != hBitmap)
+ DeleteObject(hBitmap);
+ }
+
+ Detach();
+}
+
+
+INT_PTR ServicePanel::OnDialogColor(HDC hdc, HWND hControl)
+{
+ HWND hParent = GetAncestor(hwnd, GA_PARENT);
+ if (NULL != hParent && hParent != hwnd)
+ return (INT_PTR)SendMessage(hParent, WM_CTLCOLORDLG, (WPARAM)hdc, (LPARAM)hControl);
+
+ return 0;
+}
+
+
+INT_PTR ServicePanel::OnStaticColor(HDC hdc, HWND hControl)
+{
+ INT_PTR result = 0;
+ HWND hParent = GetAncestor(hwnd, GA_PARENT);
+ if (NULL != hParent && hParent != hwnd)
+ result = (INT_PTR)SendMessage(hParent, WM_CTLCOLORSTATIC, (WPARAM)hdc, (LPARAM)hControl);
+
+ INT controlId = GetDlgCtrlID(hControl);
+ switch(controlId)
+ {
+ case IDC_SERVICEMETA:
+ {
+ COLORREF rgbBk = GetBkColor(hdc);
+ COLORREF rgbFg = GetTextColor(hdc);
+
+ ifc_omgraphics *graphics;
+ if (SUCCEEDED(OMUTILITY->GetGraphics(&graphics)))
+ {
+ graphics->BlendColor(rgbFg, rgbBk, 180, &rgbFg);
+ graphics->Release();
+ }
+
+ SetTextColor(hdc, rgbFg);
+ }
+ break;
+ }
+ return result;
+}
+
+
+INT_PTR ServicePanel::OnGetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer) return FALSE;
+ return SUCCEEDED(StringCchCopy(pszBuffer, cchBufferMax, (NULL != name) ? name : L""));
+}
+
+INT_PTR ServicePanel::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return OnInitDialog((HWND)wParam, lParam);
+ case WM_DESTROY: OnDestroy(); break;
+ case WM_CTLCOLORDLG: return OnDialogColor((HDC)wParam, (HWND)lParam);
+ case WM_CTLCOLORSTATIC: return OnStaticColor((HDC)wParam, (HWND)lParam);
+
+ case NSDM_GETUNIQUENAME: MSGRESULT(hwnd, OnGetUniqueName((LPWSTR)lParam, (UINT)wParam));
+ }
+ return 0;
+}
+
+static INT_PTR WINAPI ServicePanel_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ ServicePanel *panel = GetPanel(hwnd);
+ if (NULL == panel)
+ {
+ if (WM_INITDIALOG == uMsg)
+ {
+ panel = (ServicePanel*)lParam;
+ if (NULL != panel)
+ panel->Attach(hwnd);
+ }
+
+ if (NULL == panel) return 0;
+ }
+
+ return panel->DialogProc(uMsg, wParam, lParam);
+}
+
+
+
+#define CBCLASS ServicePanel
+START_MULTIPATCH;
+ START_PATCH(MPIID_SERVICEEVENT)
+ M_CB(MPIID_SERVICEEVENT, ifc_omserviceevent, ADDREF, AddRef);
+ M_CB(MPIID_SERVICEEVENT, ifc_omserviceevent, RELEASE, Release);
+ M_CB(MPIID_SERVICEEVENT, ifc_omserviceevent, QUERYINTERFACE, QueryInterface);
+ M_VCB(MPIID_SERVICEEVENT, ifc_omserviceevent, API_SERVICECHANGE, ServiceChange);
+
+
+ NEXT_PATCH(MPIID_CACHECALLBACK)
+ M_CB(MPIID_CACHECALLBACK, ifc_omcachecallback, ADDREF, AddRef);
+ M_CB(MPIID_CACHECALLBACK, ifc_omcachecallback, RELEASE, Release);
+ M_CB(MPIID_CACHECALLBACK, ifc_omcachecallback, QUERYINTERFACE, QueryInterface);
+ M_VCB(MPIID_CACHECALLBACK, ifc_omcachecallback, API_PATHCHANGED, PathChanged);
+
+ END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_online/Setup/setupServicePanel.h b/Src/Plugins/Library/ml_online/Setup/setupServicePanel.h
new file mode 100644
index 00000000..f60adac1
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/Setup/setupServicePanel.h
@@ -0,0 +1,79 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_SETUP_SERVICE_PANEL_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_SETUP_SERVICE_PANEL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <bfc/multipatch.h>
+#include <ifc_omserviceevent.h>
+#include <ifc_omcachecallback.h>
+
+class ifc_omservice;
+
+#define MPIID_SERVICEEVENT 10
+#define MPIID_CACHECALLBACK 20
+
+
+class ServicePanel : public MultiPatch<MPIID_SERVICEEVENT, ifc_omserviceevent>,
+ public MultiPatch<MPIID_CACHECALLBACK, ifc_omcachecallback>
+{
+protected:
+ ServicePanel(LPCWSTR pszName, ifc_omservice *service);
+ ~ServicePanel();
+
+public:
+ static HWND CreateInstance(HWND hParent, LPCWSTR pszName, ifc_omservice *service, ServicePanel **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_omserviceevent */
+ void ServiceChange(ifc_omservice *service, unsigned int modifiedFlags);
+
+ /* ifc_omcachecallback */
+ void PathChanged(ifc_omcacherecord *record);
+
+protected:
+ void Attach(HWND hwnd);
+ void Detach();
+
+ void UpdateName();
+ void UpdateDescription();
+ void UpdateMeta();
+ void UpdateThumbnail();
+
+ HFONT PickTitleFont(LPCWSTR pszTitle, INT cchTitle, INT maxWidth);
+ LPCWSTR FormatDate(LPCWSTR pszDate, LPWSTR pszBuffer, INT cchBufferMax);
+ HRESULT GetFullName(LPWSTR pszBuffer, UINT cchBufferMax);
+
+ INT_PTR OnInitDialog(HWND hFocus, LPARAM lParam);
+ void OnDestroy();
+ INT_PTR OnDialogColor(HDC hdc, HWND hControl);
+ INT_PTR OnStaticColor(HDC hdc, HWND hControl);
+ INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+ INT_PTR OnGetUniqueName(LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT LoadLocalThumbnail(LPCWSTR pszPath);
+
+private:
+ friend static INT_PTR WINAPI ServicePanel_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+protected:
+ size_t ref;
+ HWND hwnd;
+ LPWSTR name;
+ ifc_omservice *service;
+ ifc_omcacherecord *thumbnailCache;
+ HFONT fontTitle;
+ HFONT fontMeta;
+
+private:
+ RECVS_MULTIPATCH;
+};
+
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_SETUP_SERVICE_PANEL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/api__ml_online.h b/Src/Plugins/Library/ml_online/api__ml_online.h
new file mode 100644
index 00000000..e40852bf
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/api__ml_online.h
@@ -0,0 +1,62 @@
+#ifndef NULLOSFT_ONLINEMEDIA_WASABI_API_HEADER
+#define NULLOSFT_ONLINEMEDIA_WASABI_API_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#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/Config/api_config.h"
+#include "../Agave/Language/api_language.h"
+
+#include <api/memmgr/api_memmgr.h>
+extern api_memmgr *memManagerApi;
+#define WASABI_API_MEMMNGR memManagerApi
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_securityApi;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_securityApi
+
+#include <api/service/svcs/svc_imgload.h>
+extern svc_imageLoader *pngLoaderApi;
+#define WASABI_API_PNGLOADER pngLoaderApi
+EXTERN_C const GUID pngLoaderGUID;
+
+#include "../auth/api_auth.h"
+extern api_auth *authApi;
+#define AGAVE_API_AUTH authApi
+
+#include <obj_ombrowser.h>
+extern obj_ombrowser *browserManager;
+#define OMBROWSERMNGR browserManager
+
+#include <ifc_omservicemanager.h>
+extern ifc_omservicemanager *serviceManager;
+#define OMSERVICEMNGR serviceManager
+
+#include <ifc_omutility.h>
+extern ifc_omutility *omUtility;
+#define OMUTILITY omUtility
+
+HRESULT WasabiApi_Initialize(HINSTANCE hInstance, api_service *serviceApi);
+HRESULT WasabiApi_LoadDefaults();
+ULONG WasabiApi_AddRef(void);
+ULONG WasabiApi_Release(void);
+
+void *Wasabi_QueryInterface(REFGUID interfaceGuid);
+void Wasabi_ReleaseInterface(REFGUID interfaceGuid, void *pInstance);
+
+#define QueryWasabiInterface(__interfaceType, __interfaceGuid) ((##__interfaceType*)Wasabi_QueryInterface(__interfaceGuid))
+#define ReleaseWasabiInterface(__interfaceGuid, __interfaceInstance) (Wasabi_ReleaseInterface((__interfaceGuid), (__interfaceInstance)))
+
+#endif // NULLOSFT_ONLINEMEDIA_WASABI_API_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/browserEvent.cpp b/Src/Plugins/Library/ml_online/browserEvent.cpp
new file mode 100644
index 00000000..d24442a7
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/browserEvent.cpp
@@ -0,0 +1,100 @@
+#include "main.h"
+#include "./browserEvent.h"
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omserviceeventmngr.h>
+#include <ifc_omservicecommand.h>
+
+#include <browserView.h>
+#include <browserPopup.h>
+
+BrowserEvent::BrowserEvent()
+ : ref(1)
+{
+}
+
+BrowserEvent::~BrowserEvent()
+{
+}
+
+HRESULT BrowserEvent::CreateInstance(BrowserEvent **instance)
+{
+ if (NULL == instance) return E_POINTER;
+ *instance = new BrowserEvent();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+ return S_OK;
+}
+
+size_t BrowserEvent::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t BrowserEvent::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int BrowserEvent::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_OmBrowserEvent))
+ *object = static_cast<ifc_ombrowserevent*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+void BrowserEvent::WindowCreate(HWND hwnd, const GUID *windowType)
+{
+ if (NULL != windowType)
+ {
+ if (IsEqualGUID(*windowType, WTID_BrowserView) ||
+ IsEqualGUID(*windowType, WTID_BrowserPopup))
+ {
+ ifc_omservice *service;
+ if (FALSE != BrowserControl_GetService(hwnd, &service))
+ {
+ UINT flags;
+ if (SUCCEEDED(service->GetFlags(&flags)) &&
+ 0 == ((SVCF_SPECIAL | SVCF_VALIDATED | SVCF_VERSIONCHECK) & flags))
+ {
+ ServiceHelper_BeginVersionCheck(service);
+ }
+ service->Release();
+ }
+ }
+ }
+}
+
+void BrowserEvent::WindowClose(HWND hwnd, const GUID *windowType)
+{
+}
+
+#define CBCLASS BrowserEvent
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+VCB(API_WINDOWCREATE, WindowCreate)
+VCB(API_WINDOWCLOSE, WindowClose)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_online/browserEvent.h b/Src/Plugins/Library/ml_online/browserEvent.h
new file mode 100644
index 00000000..07d10f52
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/browserEvent.h
@@ -0,0 +1,41 @@
+#ifndef NULLSOFT_ONLINEMEDIA_PLUGIN_BROWSER_EVENT_HANDLER_HEADER
+#define NULLSOFT_ONLINEMEDIA_PLUGIN_BROWSER_EVENT_HANDLER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <ifc_ombrowserevent.h>
+
+class BrowserEvent : public ifc_ombrowserevent
+{
+
+protected:
+ BrowserEvent();
+ ~BrowserEvent();
+
+public:
+ static HRESULT CreateInstance(BrowserEvent **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_ombrowserevent */
+ void WindowCreate(HWND hwnd, const GUID *windowType);
+ void WindowClose(HWND hwnd, const GUID *windowType);
+
+
+protected:
+ ULONG ref;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+
+
+#endif //NULLSOFT_ONLINEMEDIA_PLUGIN_BROWSER_EVENT_HANDLER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/commands.cpp b/Src/Plugins/Library/ml_online/commands.cpp
new file mode 100644
index 00000000..0762991f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/commands.cpp
@@ -0,0 +1,418 @@
+#include "main.h"
+#include "./commands.h"
+#include "./api__ml_online.h"
+#include "./resource.h"
+#include "./navigation.h"
+#include "./preferences.h"
+#include "./messagebox.h"
+#include "./serviceHelper.h"
+#include "../winamp/wa_ipc.h"
+#include "./import.h"
+#include <ifc_omservice.h>
+#include <browserView.h>
+#include <wininet.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define BEGIN_COMMAND_SELECT(__commandId) switch(commandId) {
+#define END_COMMAND_SELECT }
+
+#define OMCOMMAND(__commandId, __commandCode, __resultOut) case (__commandId):\
+ { BOOL result = ##__commandCode; \
+ if (NULL != (__resultOut)) { *(__resultOut) = result;}\
+ return TRUE;}
+
+BOOL Command_SetServiceRating(ifc_omservice *service, INT rating)
+{
+ return SUCCEEDED(ServiceHelper_SetRating(service, rating, SHF_NOTIFY | SHF_VERBAL | SHF_SAVE));
+}
+
+BOOL Command_OpenServiceView(ifc_omservice *service)
+{
+ BOOL resultOk = FALSE;;
+ Navigation *navigation;
+ if (NULL != service && SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ HNAVITEM hItem = navigation->FindService(service->GetId(), NULL);
+ if (NULL != hItem)
+ {
+ HRESULT hr = navigation->SelectItem(hItem, NULL);
+
+ if (SUCCEEDED(hr))
+ resultOk = TRUE;
+ }
+ navigation->Release();
+ }
+
+ return resultOk;
+}
+
+HRESULT Command_NavigateService(ifc_omservice *service, LPCWSTR pszUrl, BOOL fActiveOnly)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ ifc_omservice *activeService;
+ HWND hView = navigation->GetActiveView(&activeService);
+ if (NULL == hView || activeService->GetId() != service->GetId())
+ hView = NULL;
+
+ if (NULL != activeService)
+ activeService->Release();
+
+ HRESULT hr = S_OK;
+
+ if (NULL != hView)
+ {
+ if (FALSE == BrowserView_Navigate(hView, pszUrl, TRUE))
+ hr = E_FAIL;
+ }
+ else
+ {
+ hr = (FALSE == fActiveOnly) ?
+ navigation->ShowService(service->GetId(), pszUrl) : E_NOTIMPL;
+ }
+
+ navigation->Release();
+
+ return hr;
+}
+
+HRESULT Command_EditService( ifc_omservice *service )
+{
+ if ( NULL == service )
+ return E_INVALIDARG;
+
+ WCHAR szBuffer[ 2048 ] = { 0 };
+
+ HRESULT hr = Plugin_MakeResourcePath( szBuffer, ARRAYSIZE( szBuffer ), RT_HTML, MAKEINTRESOURCE( IDR_HTML_EDITOR ), RESPATH_TARGETIE | RESPATH_COMPACT );
+ if ( FAILED( hr ) )
+ return hr;
+
+ INT cchUrl = lstrlen( szBuffer );
+ LPWSTR pszParam = szBuffer + cchUrl;
+ INT cchParamMax = ARRAYSIZE( szBuffer ) - cchUrl;
+
+ hr = StringCchPrintf( pszParam, cchParamMax, L"?serviceId=%u", service->GetId() );
+ if ( FAILED( hr ) )
+ return hr;
+
+ return Command_NavigateService( service, szBuffer, FALSE );
+}
+
+BOOL Command_OpenServicePopup(ifc_omservice *service)
+{
+ BOOL resultOk = FALSE;;
+ Navigation *navigation;
+ if (NULL != service && SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ HNAVITEM hItem = navigation->FindService(service->GetId(), NULL);
+ if (NULL != hItem)
+ {
+ HWND hPopup;
+ HRESULT hr = navigation->CreatePopup(hItem, &hPopup);
+ if (SUCCEEDED(hr))
+ {
+ ShowWindow(hPopup, SW_SHOWNORMAL);
+ resultOk = TRUE;
+ }
+ }
+ navigation->Release();
+ }
+
+ return resultOk;
+}
+
+BOOL Command_ReportService(ifc_omservice *service)
+{
+ HWND hWinamp = Plugin_GetWinamp();
+ if (NULL == hWinamp || !IsWindow(hWinamp))
+ return FALSE;
+
+ if (NULL == service)
+ return FALSE;
+
+ WCHAR szUrl[256] = {0};
+ WCHAR szClient[128] = {0};
+
+ OMBROWSERMNGR->GetClientId(szClient, ARRAYSIZE(szClient));
+
+ StringCchPrintf(szUrl, ARRAYSIZE(szUrl), L"http://www.winamp.com/legal/abuse?svc_id=%u&unique=%s",
+ service->GetId(), szClient);
+
+ SENDWAIPC(hWinamp, IPC_OPEN_URL, szUrl);
+ return TRUE;
+}
+
+BOOL Command_UnsubscribeService(ifc_omservice *service)
+{
+ return (SUCCEEDED(ServiceHelper_Subscribe(service, FALSE, SHF_NOTIFY | SHF_VERBAL | SHF_SAVE)));
+}
+
+BOOL Command_ShowServiceInfo(ifc_omservice *service)
+{
+ if (NULL == service)
+ return FALSE;
+
+ BOOL resultOk = FALSE;
+
+ HRESULT hr;
+ WCHAR szUrl[INTERNET_MAX_URL_LENGTH] = {0}, szName[INTERNET_MAX_URL_LENGTH] = {0};
+
+ DWORD cchName = ARRAYSIZE(szName);
+ if (FAILED(service->GetName(szUrl, ARRAYSIZE(szUrl))) ||
+ FAILED(UrlEscape(szUrl, szName, &cchName, URL_ESCAPE_SEGMENT_ONLY | URL_ESCAPE_PERCENT)))
+ {
+ StringCchCopy(szName, ARRAYSIZE(szName), L"Info");
+ }
+
+ hr = StringCchPrintf(szUrl, ARRAYSIZE(szUrl), L"http://client.winamp.com/service/detail/%s/%d#", szName, service->GetId());
+ if (FAILED(hr)) return hr;
+
+ Navigation *navigation;
+ hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ HNAVITEM hRoot = navigation->FindService(ROOTSERVICE_ID, NULL);
+ if (NULL != hRoot)
+ {
+ HNAVITEM hActive = navigation->GetActive(NULL);
+ if (hActive == hRoot)
+ {
+ HWND hView = navigation->GetActiveView(NULL);
+ if (NULL != hView && FALSE != BrowserView_Navigate(hView, szUrl, TRUE))
+ resultOk = TRUE;
+ }
+
+ if (FALSE == resultOk && SUCCEEDED(navigation->SelectItem(hRoot, szUrl)))
+ resultOk = TRUE;
+ }
+ navigation->Release();
+ }
+ return resultOk;
+}
+
+BOOL Command_ResetServicePolicy(ifc_omservice *service)
+{
+ return (SUCCEEDED(ServiceHelper_ResetPermissions(service, SHF_NOTIFY | SHF_VERBAL)));
+}
+
+BOOL Command_ResetSubscription()
+{
+ HRESULT hr = ServiceHelper_ResetSubscription(SHF_VERBAL);
+ return SUCCEEDED(hr);
+}
+
+static BOOL Command_OpenPreferences()
+{
+ return Preferences_Show();
+}
+
+static BOOL Command_OpenHelp()
+{
+ return (BOOL)SENDWAIPC(Plugin_GetWinamp(), IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8112533645844-Online-Services");
+}
+
+static BOOL Command_NavigateView(HWND hView, LPCWSTR navigateUrl)
+{
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ HWND hActive = navigation->GetActiveView(NULL);
+ if (hActive != hView) hView = NULL;
+
+ BOOL resultOk = ( NULL != hView && FALSE != BrowserView_Navigate( hView, navigateUrl, TRUE ) );
+
+ navigation->Release();
+
+ return resultOk;
+}
+
+HRESULT Command_ImportFiles()
+{
+ HWND hOwner = Plugin_GetDialogOwner();
+ return ImportService_FromFile(hOwner);
+}
+
+HRESULT Command_ImportUrl()
+{
+ HWND hOwner = Plugin_GetDialogOwner();
+ return ImportService_FromUrl(hOwner);
+}
+
+HRESULT Command_CreateService()
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (FAILED(hr)) return hr;
+
+ HNAVITEM hItem;
+ hr = navigation->CreateUserService(&hItem);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ hr= navigation->GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ Command_EditService(service);
+ service->Release();
+ }
+ }
+ navigation->Release();
+ return hr;
+}
+
+HRESULT Command_LocateService(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ WCHAR szPath[512];
+
+ HRESULT hr = service->GetAddress(szPath, ARRAYSIZE(szPath));
+ if (FAILED(hr)) return hr;
+
+ if (L'\0' == szPath[0])
+ return E_FAIL;
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ navigation->Release();
+
+ if (WASABI_API_EXPLORERFINDFILE)
+ {
+ WASABI_API_EXPLORERFINDFILE->AddFile(szPath);
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+ return E_UNEXPECTED;
+}
+
+HRESULT Command_EditServiceExternal(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ WCHAR szPath[512];
+
+ HRESULT hr = service->GetAddress(szPath, ARRAYSIZE(szPath));
+ if (FAILED(hr)) return hr;
+
+ if (L'\0' == szPath[0])
+ return E_FAIL;
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ HWND hOwner = navigation->GetActiveView(NULL);
+ navigation->Release();
+
+ if (NULL == hOwner)
+ hOwner = Plugin_GetLibrary();
+
+ HINSTANCE hInst = ShellExecute(hOwner, L"open", szPath, NULL, NULL, SW_SHOWNORMAL);
+ hr = ((INT_PTR)hInst > 32) ? S_OK : E_FAIL;
+ return hr;
+}
+
+BOOL Command_ProcessService(HWND hView, ifc_omservice *service, INT commandId, BOOL *fSuccess)
+{
+ BEGIN_COMMAND_SELECT(commandId)
+ OMCOMMAND(ID_RATING_VALUE_5, Command_SetServiceRating(service, 5), fSuccess);
+ OMCOMMAND(ID_RATING_VALUE_4, Command_SetServiceRating(service, 4), fSuccess);
+ OMCOMMAND(ID_RATING_VALUE_3, Command_SetServiceRating(service, 3), fSuccess);
+ OMCOMMAND(ID_RATING_VALUE_2, Command_SetServiceRating(service, 2), fSuccess);
+ OMCOMMAND(ID_RATING_VALUE_1, Command_SetServiceRating(service, 1), fSuccess);
+ OMCOMMAND(ID_VIEW_OPEN, Command_OpenServiceView(service), fSuccess);
+ OMCOMMAND(ID_VIEW_OPENPOPUP, Command_OpenServicePopup(service), fSuccess);
+ //OMCOMMAND(ID_SERVICE_REPORT, Command_ReportService(service), fSuccess);
+ OMCOMMAND(ID_SERVICE_UNSUBSCRIBE, Command_UnsubscribeService(service), fSuccess);
+ //OMCOMMAND(ID_SERVICE_GETINFO, Command_ShowServiceInfo(service), fSuccess);
+ OMCOMMAND(ID_SERVICE_RESETPOLICY, Command_ResetServicePolicy(service), fSuccess);
+ OMCOMMAND(ID_SERVICE_IMPORT_FILE, Command_ImportFiles(), fSuccess);
+ OMCOMMAND(ID_SERVICE_IMPORT_URL, Command_ImportUrl(), fSuccess);
+ OMCOMMAND(ID_NAVIGATION_REFRESH, Command_NavigateView(hView, NAVIGATE_REFRESH), fSuccess);
+
+ OMCOMMAND(ID_SERVICE_NEW, Command_CreateService(), fSuccess);
+ OMCOMMAND(ID_SERVICE_EDIT, Command_EditService(service), fSuccess);
+ OMCOMMAND(ID_SERVICE_LOCATE, Command_LocateService(service), fSuccess);
+ OMCOMMAND(ID_SERVICE_EDITEXTERNAL, Command_EditServiceExternal(service), fSuccess);
+
+ END_COMMAND_SELECT
+
+ return FALSE;
+}
+
+BOOL Command_ProcessGeneral(INT commandId, BOOL *fSuccess)
+{
+ BEGIN_COMMAND_SELECT(commandId)
+ //OMCOMMAND(ID_SERVICEMANAGER_RESET, Command_ResetSubscription(), fSuccess);
+ OMCOMMAND(ID_PLUGIN_PREFERENCES, Command_OpenPreferences(), fSuccess);
+ OMCOMMAND(ID_PLUGIN_HELP, Command_OpenHelp(), fSuccess);
+ END_COMMAND_SELECT
+
+ return FALSE;
+}
+
+static void CALLBACK BrowserOptions_Callback(HWND hOptions, UINT type, ULONG_PTR user)
+{
+ HWND hLibrary = (HWND)user;
+ switch(type)
+ {
+ case BOCALLBACK_INIT:
+ {
+ HWND hView = (HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL != hView)
+ {
+ RECT viewRect, optionsRect;
+ if (GetWindowRect(hView, &viewRect) && GetWindowRect(hOptions, &optionsRect))
+ {
+ INT x = viewRect.left + ((viewRect.right - viewRect.left) - (optionsRect.right - optionsRect.left))/2;
+ INT y = viewRect.top + ((viewRect.bottom - viewRect.top) - (optionsRect.bottom - optionsRect.top))/2;
+ SetWindowPos(hOptions, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
+ SendMessage(hOptions, DM_REPOSITION, 0, 0L);
+ }
+ }
+ }
+ break;
+ }
+}
+
+HRESULT Command_ShowBrowserOptions()
+{
+ HWND hWinamp = Plugin_GetWinamp();
+ if (NULL == hWinamp || NULL == OMBROWSERMNGR)
+ return E_UNEXPECTED;
+
+ HRESULT hr = OMBROWSERMNGR->Initialize(NULL, hWinamp);
+ if (SUCCEEDED(hr))
+ {
+ HWND hOwner = Plugin_GetDialogOwner();
+ hr = OMBROWSERMNGR->ShowOptions(hOwner, BOSTYLE_NORMAL | BOSTYLE_SHOWDEBUG,
+ BrowserOptions_Callback, (ULONG_PTR)hOwner);
+ }
+ return hr;
+}
+
+BOOL Command_ProcessView(HWND hView, INT commandId, BOOL *fSuccess)
+{
+ BEGIN_COMMAND_SELECT(commandId)
+ OMCOMMAND(ID_NAVIGATION_HOME, Command_NavigateView(hView, NAVIGATE_HOME), fSuccess);
+ OMCOMMAND(ID_NAVIGATION_BACK, Command_NavigateView(hView, NAVIGATE_BACK), fSuccess);
+ OMCOMMAND(ID_NAVIGATION_FORWARD, Command_NavigateView(hView, NAVIGATE_FORWARD), fSuccess);
+ OMCOMMAND(ID_NAVIGATION_REFRESH, Command_NavigateView(hView, NAVIGATE_REFRESH), fSuccess);
+ OMCOMMAND(ID_NAVIGATION_STOP, Command_NavigateView(hView, NAVIGATE_STOP), fSuccess);
+ OMCOMMAND(ID_OMBROWSER_OPTIONS, Command_ShowBrowserOptions(), fSuccess);
+ OMCOMMAND(ID_SERVICE_IMPORT_FILE, Command_ImportFiles(), fSuccess);
+ OMCOMMAND(ID_SERVICE_IMPORT_URL, Command_ImportUrl(), fSuccess);
+ OMCOMMAND(ID_SERVICE_NEW, Command_CreateService(), fSuccess);
+ END_COMMAND_SELECT
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/commands.h b/Src/Plugins/Library/ml_online/commands.h
new file mode 100644
index 00000000..79aec555
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/commands.h
@@ -0,0 +1,29 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_COMMANDS_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_COMMANDS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ifc_omservice;
+
+// returns TRUE if command was handled (fSuccess will have result code if not NULL).
+
+HRESULT Command_NavigateService(ifc_omservice *service, LPCWSTR pszUrl, BOOL fActiveOnly);
+BOOL Command_ProcessService(HWND hView, ifc_omservice *service, INT commandId, BOOL *fSuccess);
+BOOL Command_ProcessView(HWND hView, INT commandId, BOOL *fSuccess);
+BOOL Command_ProcessGeneral(INT commandId, BOOL *fSuccess);
+
+BOOL Command_ReportService(ifc_omservice *service);
+BOOL Command_UnsubscribeService(ifc_omservice *service);
+BOOL Command_ShowServiceInfo(ifc_omservice *service);
+BOOL Command_ResetServicePolicy(ifc_omservice *service);
+BOOL Command_ResetSubscription();
+BOOL Command_SetServiceRating(ifc_omservice *service, INT rating);
+HRESULT Command_CreateService(void);
+BOOL Command_OpenServiceView(ifc_omservice *service);
+BOOL Command_OpenServicePopup(ifc_omservice *service);
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_COMMANDS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/common.cpp b/Src/Plugins/Library/ml_online/common.cpp
new file mode 100644
index 00000000..f3d88809
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/common.cpp
@@ -0,0 +1,374 @@
+#include "./common.h"
+#include "./api__ml_online.h"
+#include "./main.h"
+
+#include "../winamp/wa_ipc.h"
+
+#include <strsafe.h>
+
+LPWSTR Plugin_MallocString(size_t cchLen)
+{
+ return (LPWSTR)calloc(cchLen, sizeof(WCHAR));
+}
+
+void Plugin_FreeString(LPWSTR pszString)
+{
+ if (NULL != pszString)
+ {
+ free(pszString);
+ pszString = NULL;
+ }
+}
+
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen)
+{
+ return (LPWSTR)realloc(pszString, sizeof(WCHAR) * cchLen);
+}
+
+LPWSTR Plugin_CopyString(LPCWSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenW(pszSource) + 1;
+
+ LPWSTR copy = Plugin_MallocString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(WCHAR) * cchSource);
+ }
+ return copy;
+}
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen)
+{
+ return (LPSTR)calloc(cchLen, sizeof(CHAR));
+}
+
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenA(pszSource) + 1;
+
+ LPSTR copy = Plugin_MallocAnsiString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(CHAR) * cchSource);
+ }
+ return copy;
+
+}
+void Plugin_FreeAnsiString(LPSTR pszString)
+{
+ Plugin_FreeString((LPWSTR)pszString);
+}
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
+{
+ INT cchBuffer = WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, lpDefaultChar, lpUsedDefaultChar);
+ if (0 == cchBuffer) return NULL;
+
+ LPSTR buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, buffer, cchBuffer, lpDefaultChar, lpUsedDefaultChar))
+ {
+ Plugin_FreeAnsiString(buffer);
+ return NULL;
+ }
+ return buffer;
+}
+
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte)
+{
+ if (NULL == lpMultiByteStr) return NULL;
+ INT cchBuffer = MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0);
+ if (NULL == cchBuffer) return NULL;
+
+ if (cbMultiByte > 0) cchBuffer++;
+
+ LPWSTR buffer = Plugin_MallocString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, buffer, cchBuffer))
+ {
+ Plugin_FreeString(buffer);
+ return NULL;
+ }
+
+ if (cbMultiByte > 0)
+ {
+ buffer[cchBuffer - 1] = L'\0';
+ }
+ return buffer;
+}
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource)
+{
+ return (IS_INTRESOURCE(pszResource)) ?
+ (LPWSTR)pszResource :
+ Plugin_CopyString(pszResource);
+}
+
+void Plugin_FreeResString(LPWSTR pszResource)
+{
+ if (!IS_INTRESOURCE(pszResource))
+ Plugin_FreeString(pszResource);
+}
+
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString)
+{
+ if (NULL == pszBuffer)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ if (NULL == pszString)
+ {
+ pszBuffer[0] = L'\0';
+ }
+ else if (IS_INTRESOURCE(pszString))
+ {
+ if (NULL == WASABI_API_LNG)
+ hr = E_FAIL;
+ else
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszString, pszBuffer, cchBufferMax);
+ }
+ else
+ {
+ hr = StringCchCopy(pszBuffer, cchBufferMax, pszString);
+ }
+ return hr;
+}
+
+void Plugin_SafeRelease(IUnknown *pUnk)
+{
+ if (NULL != pUnk)
+ pUnk->Release();
+}
+
+HRESULT Plugin_MakeResourcePath(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszType, LPCWSTR pszName, UINT flags)
+{
+ HINSTANCE hInstance = WASABI_API_LNG_HINST;
+ if (NULL == hInstance || NULL == FindResource(hInstance, pszName, pszType))
+ hInstance = Plugin_GetInstance();
+
+ if (NULL == OMUTILITY)
+ return E_UNEXPECTED;
+
+ return OMUTILITY->MakeResourcePath(pszBuffer, cchBufferMax, hInstance, pszType, pszName, flags);
+}
+
+HWND Plugin_GetDialogOwner(void)
+{
+ HWND hOwner= Plugin_GetLibrary();
+ if (NULL == hOwner || FALSE == IsWindowVisible(hOwner) ||
+ FALSE == IsWindowEnabled(hOwner))
+ {
+ hOwner = Plugin_GetWinamp();
+ if (NULL != hOwner)
+ {
+ HWND hDlgParent = (HWND)SENDWAIPC(hOwner, IPC_GETDIALOGBOXPARENT, 0L);
+ if (NULL != hDlgParent)
+ hOwner = hDlgParent;
+ }
+ }
+ return hOwner;
+}
+
+HRESULT Plugin_AppendFileFilter(LPTSTR pszBuffer, size_t cchBufferMax, LPCTSTR pName, LPCTSTR pFilter, LPTSTR *ppBufferOut, size_t *pRemaining, BOOL bShowFilter)
+{
+ HRESULT hr;
+
+ LPTSTR pCursor = pszBuffer;
+
+ if (NULL != ppBufferOut)
+ *ppBufferOut = pszBuffer;
+
+ if (NULL != pRemaining)
+ *pRemaining = cchBufferMax;
+
+ if (NULL == pszBuffer || NULL == pName || NULL == pFilter)
+ return E_INVALIDARG;
+
+ pszBuffer[0] = TEXT('\0');
+
+ hr = StringCchCopyEx(pCursor, cchBufferMax, pName, &pCursor, &cchBufferMax,
+ STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE);
+ if (bShowFilter && SUCCEEDED(hr))
+ {
+ LPTSTR p = pCursor;
+ hr = StringCchPrintfEx(pCursor, cchBufferMax, &pCursor, &cchBufferMax,
+ STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE, TEXT(" (%s)"), pFilter);
+ if (SUCCEEDED(hr) && p != pCursor)
+ CharLowerBuff(p, (INT)(INT_PTR)(pCursor - p));
+ }
+ if (SUCCEEDED(hr))
+ {
+ pCursor++;
+ cchBufferMax--;
+ hr = StringCchCopyEx(pCursor, cchBufferMax, pFilter, &pCursor, &cchBufferMax,
+ STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE);
+ }
+
+ if (cchBufferMax < 1)
+ hr = STRSAFE_E_INSUFFICIENT_BUFFER;
+
+ pCursor++;
+ cchBufferMax--;
+
+ if (SUCCEEDED(hr))
+ {
+ pCursor[0] = TEXT('\0');
+ if (NULL != ppBufferOut)
+ *ppBufferOut = pCursor;
+ if (NULL != pRemaining)
+ *pRemaining = cchBufferMax;
+ }
+ else
+ {
+ pszBuffer[0] = TEXT('\0');
+ pszBuffer[1] = TEXT('\0');
+ }
+
+ return hr;
+}
+
+HRESULT Plugin_BuildActionUrl(LPWSTR *ppStringOut, LPCWSTR pszAction, UINT *pServiceUid, size_t cchServiceUid)
+{
+ if (NULL == ppStringOut)
+ return E_POINTER;
+
+ *ppStringOut = NULL;
+
+ if (NULL == pszAction || L'\0' == *pszAction ||
+ NULL == pServiceUid || 0 == cchServiceUid)
+ {
+ return E_INVALIDARG;
+ }
+
+ const WCHAR szPrefix[] = L"http://services.winamp.com/svc/action?action=%s\0";
+ const WCHAR szService[] = L"&svc_id=%u\0";
+ const WCHAR szClient[] = L"&unique_id=%s\0";
+
+ size_t cchBuffer = ARRAYSIZE(szPrefix) + ARRAYSIZE(szService) + ARRAYSIZE(szClient);
+ cchBuffer += lstrlen(pszAction);
+ cchBuffer += (cchServiceUid * 11);
+ cchBuffer += 32; // unique id
+
+ LPWSTR buffer = Plugin_MallocString(cchBuffer);
+ if (NULL == buffer)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr;
+ LPWSTR cursor = buffer;
+ size_t remaining = cchBuffer;
+
+
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ szPrefix, pszAction);
+
+ for (size_t i = 0; i < cchServiceUid && SUCCEEDED(hr); i++)
+ {
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ ((0 == i) ? szService : L",%u"), pServiceUid[i]);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ WCHAR szTemp[128] = {0};
+ hr = OMBROWSERMNGR->GetClientId(szTemp, ARRAYSIZE(szTemp));
+ if (SUCCEEDED(hr))
+ {
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, szClient, szTemp);
+ }
+ }
+
+ if (FAILED(hr))
+ {
+ Plugin_FreeString(buffer);
+ hr = E_FAIL;
+ }
+ else
+ {
+ *ppStringOut = buffer;
+ }
+ return hr;
+}
+
+INT Plugin_ParseKeywords(LPCWSTR input, INT cchInput, WCHAR separator, BOOL eatSpace, KWPARSERPROC callback, ULONG_PTR user)
+{
+ if (NULL == input)
+ return 0;
+
+ if (cchInput < 0)
+ cchInput = lstrlen(input);
+
+ if (cchInput <= 0)
+ return 0;
+
+ LPCWSTR end = (input + cchInput);
+
+ if(eatSpace)
+ while(input < end && L' ' == *input) input++;
+
+ if (L'\0' == *input)
+ return 0;
+
+ INT found = 0;
+
+ for (;;)
+ {
+ LPCWSTR pBlock = input;
+ while(input <= end && separator != *input) input++;
+ LPCWSTR last = (input - 1);
+ if (eatSpace)
+ while(last >= pBlock && L' ' == *last) last--;
+
+ if (last >= pBlock)
+ {
+ UINT code = callback(pBlock, (INT)(INT_PTR)(last - pBlock) + 1, user);
+ if (KWPARSER_FOUND & code) found++;
+ if (KWPARSER_ABORT == (0x01 & code))
+ return found;
+ }
+
+ if (input >= end || L'\0' == *input)
+ return found;
+
+ input++;
+ if(eatSpace)
+ while(input < end && L' ' == *input) input++;
+ }
+
+ return found;
+}
+
+INT Plugin_MessageBox(LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
+{
+ HWND hHost, hLibrary = Plugin_GetLibrary();
+ if (NULL != hLibrary && FALSE != IsWindowVisible(hLibrary))
+ {
+ hHost = (HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == hHost || FALSE == IsWindowVisible(hHost))
+ hHost = hLibrary;
+ }
+ else
+ hHost = Plugin_GetDialogOwner();
+
+ if(IS_INTRESOURCE(lpText) && NULL != lpText)
+ {
+ WCHAR szText[2048] = {0};
+ lpText = WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)lpText, szText, ARRAYSIZE(szText));
+ }
+
+ if(IS_INTRESOURCE(lpCaption) && NULL != lpCaption)
+ {
+ WCHAR szCaption[128] = {0};
+ lpCaption = WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)lpCaption, szCaption, ARRAYSIZE(szCaption));
+ }
+
+ return MessageBox(hHost, lpText, lpCaption, uType);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/common.h b/Src/Plugins/Library/ml_online/common.h
new file mode 100644
index 00000000..7fe1f7a3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/common.h
@@ -0,0 +1,81 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_COMMON_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_COMMON_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/trace.h"
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#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 MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+#ifndef GetWindowStyle
+#define GetWindowStyle(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_STYLE))
+#endif //GetWindowStyle
+
+#ifndef GetWindowStyleEx
+#define GetWindowStyleEx(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_EXSTYLE))
+#endif // GetWindowStyleEx
+
+LPWSTR Plugin_MallocString(size_t cchLen);
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
+void Plugin_FreeString(LPWSTR pszString);
+LPWSTR Plugin_CopyString(LPCWSTR pszSource);
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen);
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
+void Plugin_FreeAnsiString(LPSTR pszString);
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
+void Plugin_FreeResString(LPWSTR pszResource);
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
+
+void Plugin_SafeRelease(IUnknown *pUnk);
+HRESULT Plugin_MakeResourcePath(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszType, LPCWSTR pszName, UINT uFlags);
+
+HWND Plugin_GetDialogOwner(void);
+HRESULT Plugin_AppendFileFilter(LPTSTR pszBuffer, size_t cchBufferMax, LPCTSTR pName, LPCTSTR pFilter, LPTSTR *ppBufferOut, size_t *pRemaining, BOOL bShowFilter);
+
+
+HRESULT Plugin_BuildActionUrl(LPWSTR *ppStringOut, LPCWSTR pszAction, UINT *pServiceUid, size_t cchServiceUid);
+
+#define KWPARSER_ABORT ((UINT)0x00000000)
+#define KWPARSER_CONTINUE ((UINT)0x00000001)
+#define KWPARSER_FOUND ((UINT)0x80000000) // this is additional modifier
+
+typedef UINT (CALLBACK *KWPARSERPROC)(LPCWSTR /*pszKeyword*/, INT /*cchKeyword*/, ULONG_PTR /*user*/);
+
+INT Plugin_ParseKeywords(LPCWSTR input, INT cchInput, WCHAR separator, BOOL eatSpace, KWPARSERPROC callback, ULONG_PTR user);
+
+INT Plugin_MessageBox(LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_COMMON_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/config.cpp b/Src/Plugins/Library/ml_online/config.cpp
new file mode 100644
index 00000000..c6e87cb2
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/config.cpp
@@ -0,0 +1,304 @@
+#include "main.h"
+#include "./config.h"
+#include "./api__ml_online.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define CONFIG_SUFFIX L"Plugins\\ml"
+#define CONFIG_SECTION "ml_online_config"
+
+void C_Config::Flush(void)
+{
+#ifndef C_CONFIG_WIN32NATIVE
+ if (!m_dirty) return;
+ FILE *fp=fopen(m_inifile,"wt");
+ if (!fp) return;
+
+ fprintf(fp,"[ml_online_config]\n");
+ int x;
+ if (m_strs)
+ {
+ for (x = 0; x < m_num_strs; x ++)
+ {
+ char name[17] = {0};
+ memcpy(name,m_strs[x].name,16);
+ name[16]=0;
+ if (m_strs[x].value) fprintf(fp,"%s=%s\n",name,m_strs[x].value);
+ }
+ }
+ fclose(fp);
+ m_dirty=0;
+#endif
+}
+
+C_Config::~C_Config()
+{
+#ifndef C_CONFIG_WIN32NATIVE
+ int x;
+ Flush();
+ if (m_strs) for (x = 0; x < m_num_strs; x ++) free(m_strs[x].value);
+ free(m_strs);
+#endif
+
+ Plugin_FreeAnsiString(m_inifile);
+}
+
+C_Config::C_Config(char *ini)
+{
+ memset(m_strbuf,0,sizeof(m_strbuf));
+ m_inifile= Plugin_CopyAnsiString(ini);
+
+#ifndef C_CONFIG_WIN32NATIVE
+ m_dirty=0;
+ m_strs=NULL;
+ m_num_strs=m_num_strs_alloc=0;
+
+ // read config
+ FILE *fp=fopen(m_inifile,"rt");
+ if (!fp) return;
+
+ for (;;)
+ {
+ char buf[4096] = {0};
+ fgets(buf,sizeof(buf),fp);
+ if (!buf[0] || feof(fp)) break;
+ for (;;)
+ {
+ int l=strlen(buf);
+ if (l > 0 && (buf[l-1] == '\n' || buf[l-1] == '\r')) buf[l-1]=0;
+ else break;
+ }
+ if (buf[0] != '[')
+ {
+ char *p=strstr(buf,"=");
+ if (p)
+ {
+ *p++=0;
+ WriteString(buf,p);
+ }
+ }
+ }
+ m_dirty=0;
+ fclose(fp);
+#endif
+}
+
+void C_Config::WriteInt(char *name, int value)
+{
+ char buf[32] = {0};
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "%d",value);
+ WriteString(name,buf);
+}
+
+int C_Config::ReadInt(char *name, int defvalue)
+{
+#ifndef C_CONFIG_WIN32NATIVE
+ char *t=ReadString(name,"");
+ if (*t) return atoi(t);
+ return defvalue;
+#else
+ return GetPrivateProfileIntA("ml_online_config",name,defvalue,m_inifile);
+#endif
+}
+
+char *C_Config::WriteString(char *name, char *string)
+{
+#ifndef C_CONFIG_WIN32NATIVE
+ m_dirty=1;
+ for (int x = 0; x < m_num_strs; x ++)
+ {
+ if (m_strs[x].value && !strncmp(name,m_strs[x].name,16))
+ {
+ unsigned int l=(strlen(m_strs[x].value)+16)&~15;
+ if (strlen(string)<l)
+ {
+ strcpy(m_strs[x].value,string);
+ }
+ else
+ {
+ free(m_strs[x].value);
+ m_strs[x].value = (char *)malloc((strlen(string)+16)&~15);
+ strcpy(m_strs[x].value,string);
+ }
+ return m_strs[x].value;
+ }
+ }
+
+ // not already in there
+ if (m_num_strs >= m_num_strs_alloc || !m_strs)
+ {
+ m_old_num_strs_alloc = m_num_strs_alloc;
+ m_num_strs_alloc=m_num_strs*3/2+8;
+ strType *data = (strType*)::realloc(m_strs, sizeof(strType) * m_num_strs_alloc);
+ if (data)
+ {
+ m_strs = data;
+ }
+ else
+ {
+ data = (strType*)::malloc(sizeof(strType) * m_num_strs_alloc);
+ if (data)
+ {
+ memcpy(data, m_strs, sizeof(strType) * m_old_num_strs_alloc);
+ free(m_strs);
+ m_strs = data;
+ }
+ else m_num_strs_alloc = m_old_num_strs_alloc;
+ }
+ }
+ strncpy(m_strs[m_num_strs].name,name,16);
+ m_strs[m_num_strs].value = (char *)malloc((strlen(string)+16)&~15);
+ if (m_strs[m_num_strs].value)
+ {
+ strcpy(m_strs[m_num_strs].value,string);
+ return m_strs[m_num_strs++].value;
+ }
+ return "";
+#else
+ WritePrivateProfileStringA("ml_online_config",name,string,m_inifile);
+ return name;
+#endif
+}
+
+char *C_Config::ReadString( char *name, char *defstr )
+{
+#ifndef C_CONFIG_WIN32NATIVE
+ int x;
+ for ( x = 0; x < m_num_strs; x++ )
+ {
+ if ( m_strs[ x ].value && !::strncmp( name, m_strs[ x ].name, 16 ) )
+ {
+ return m_strs[ x ].value;
+ }
+ }
+ return defstr;
+#else
+ static char foobuf[] = "___________ml_online_lameness___________";
+ m_strbuf[ 0 ] = 0;
+ GetPrivateProfileStringA( "ml_online_config", name, foobuf, m_strbuf, sizeof( m_strbuf ), m_inifile );
+ if ( !strcmp( foobuf, m_strbuf ) || !strcmp( m_strbuf, "" ) )
+ {
+ return defstr;
+ }
+
+ m_strbuf[ sizeof( m_strbuf ) - 1 ] = 0;
+ return m_strbuf;
+#endif
+}
+
+static LPCSTR Config_GetPath()
+{
+ static LPSTR configPath = NULL;
+ if (NULL == configPath)
+ {
+ LPCWSTR p = (NULL != WASABI_API_APP) ? WASABI_API_APP->path_getUserSettingsPath() : NULL;
+ if (NULL != p)
+ {
+ WCHAR szBuffer[MAX_PATH * 2] = {0};
+ if (0 != PathCombine(szBuffer, p, CONFIG_SUFFIX))
+ {
+ OMUTILITY->EnsurePathExist(szBuffer);
+ PathAppend(szBuffer, L"ml_online.ini");
+ configPath = Plugin_WideCharToMultiByte(CP_UTF8, 0, szBuffer, -1, NULL, NULL);
+ }
+ }
+ }
+
+ return configPath;
+}
+
+DWORD Config_ReadStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpDefault, LPSTR lpReturnedString, DWORD nSize)
+{
+ if (NULL == lpSectionName) lpSectionName = CONFIG_SECTION;
+ return GetPrivateProfileStringA(lpSectionName, lpKeyName, lpDefault, lpReturnedString, nSize, Config_GetPath());
+}
+
+UINT Config_ReadInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nDefault)
+{
+ if (NULL == lpSectionName) lpSectionName = CONFIG_SECTION;
+ return GetPrivateProfileIntA(lpSectionName, lpKeyName, nDefault, Config_GetPath());
+}
+
+HRESULT Config_WriteStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpString)
+{
+ LPCSTR configPath = Config_GetPath();
+ if (NULL == configPath || '\0' == *configPath)
+ return E_UNEXPECTED;
+
+ if (NULL == lpSectionName) lpSectionName = CONFIG_SECTION;
+ if (0 != WritePrivateProfileStringA(lpSectionName, lpKeyName, lpString, configPath))
+ return S_OK;
+
+ DWORD errorCode = GetLastError();
+ return HRESULT_FROM_WIN32(errorCode);
+}
+
+HRESULT Config_WriteInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nValue)
+{
+ char szBuffer[32] = {0};
+ HRESULT hr = StringCchPrintfA(szBuffer, ARRAYSIZE(szBuffer), "%d", nValue);
+ if (FAILED(hr)) return hr;
+
+ if (NULL == lpSectionName) lpSectionName = CONFIG_SECTION;
+ return Config_WriteStr(lpSectionName, lpKeyName, szBuffer);
+}
+
+HRESULT Config_WriteSection(LPCSTR lpSectionName, LPCSTR lpData)
+{
+ LPCSTR configPath = Config_GetPath();
+ if (NULL == configPath || '\0' == *configPath)
+ return E_UNEXPECTED;
+
+ if (NULL == lpSectionName) lpSectionName = CONFIG_SECTION;
+ if (0 == WritePrivateProfileSectionA(lpSectionName, lpData, configPath))
+ return S_OK;
+
+ DWORD errorCode = GetLastError();
+ return HRESULT_FROM_WIN32(errorCode);
+}
+
+HRESULT Config_ReadServiceIdList(LPCSTR lpSectionName, LPCSTR lpKeyName, CHAR separator, ReadServiceIdCallback callback, void *data)
+{
+ if (NULL == callback)
+ return E_INVALIDARG;
+
+ DWORD bufferSize = 16384;
+ LPSTR buffer = Plugin_MallocAnsiString(bufferSize);
+ if (NULL == buffer) return E_OUTOFMEMORY;
+
+ DWORD bufferLen = Config_ReadStr(lpSectionName, lpKeyName, NULL, buffer, bufferSize);
+ if (0 != bufferLen)
+ {
+ LPSTR cursor = buffer;
+ LPSTR block = cursor;
+ UINT serviceId;
+ for(;;)
+ {
+ if (separator == *cursor || '\0' == *cursor)
+ {
+ while (' ' == *block && block < cursor) block++;
+
+ if (block < cursor &&
+ FALSE != StrToIntExA(block, STIF_SUPPORT_HEX, (INT*)&serviceId) &&
+ 0 != serviceId)
+ {
+
+ if (FALSE == callback(serviceId, data))
+ break;
+ }
+
+ if ('\0' == *cursor)
+ break;
+
+ cursor = CharNextA(cursor);
+ block = cursor;
+ }
+ else
+ cursor = CharNextA(cursor);
+ }
+ }
+
+ Plugin_FreeAnsiString(buffer);
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/config.h b/Src/Plugins/Library/ml_online/config.h
new file mode 100644
index 00000000..a4e3f034
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/config.h
@@ -0,0 +1,54 @@
+#ifndef NULLSOFT_ONLINEMEDIA_PLUGIN_CONFIG_HEADER
+#define NULLSOFT_ONLINEMEDIA_PLUGIN_CONFIG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+
+#define C_CONFIG_WIN32NATIVE
+class C_Config
+{
+ public:
+ C_Config(char *ini);
+ ~C_Config();
+ void Flush(void);
+ void WriteInt(char *name, int value);
+ char *WriteString(char *name, char *string);
+ int ReadInt(char *name, int defvalue);
+ char *ReadString(char *name, char *defvalue);
+
+ const char* GetPath() { return m_inifile; }
+
+ private:
+#ifndef C_CONFIG_WIN32NATIVE
+ typedef struct
+ {
+ char name[16];
+ char *value;
+ } strType;
+
+ strType *m_strs;
+ int m_dirty;
+ int m_num_strs, m_num_strs_alloc;
+#else
+ char m_strbuf[8192];
+#endif
+
+ char *m_inifile;
+};
+
+// set lpSectionName = NULL to write to default section;
+DWORD Config_ReadStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpDefault, LPSTR lpReturnedString, DWORD nSize);
+UINT Config_ReadInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nDefault);
+HRESULT Config_WriteStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpString);
+HRESULT Config_WriteInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nValue);
+HRESULT Config_WriteSection(LPCSTR lpSectionName, LPCSTR lpData);
+
+typedef BOOL (CALLBACK *ReadServiceIdCallback)(UINT /*serviceId*/, void* /*data*/);
+HRESULT Config_ReadServiceIdList(LPCSTR lpSectionName, LPCSTR lpKeyName, CHAR separator, ReadServiceIdCallback callback, void *data);
+
+
+#endif //NULLSOFT_ONLINEMEDIA_PLUGIN_CONFIG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/external.cpp b/Src/Plugins/Library/ml_online/external.cpp
new file mode 100644
index 00000000..4fcbfb4d
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/external.cpp
@@ -0,0 +1,273 @@
+#include "main.h"
+#include "./resource.h"
+#include "./external.h"
+#include "./navigation.h"
+#include "./commands.h"
+
+#include "../winamp/jsapi.h"
+#include "../winamp/jsapi_CallbackParameters.h"
+
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+
+#include <browserView.h>
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+
+#define DISPTABLE_CLASS ExternalDispatch
+
+DISPTABLE_BEGIN()
+ DISPENTRY_ADD(DISPATCH_SERVICE_OPEN, L"serviceOpen", OnServiceOpen)
+ DISPENTRY_ADD(DISPATCH_SERVICE_CREATE, L"serviceCreate", OnServiceCreate)
+ DISPENTRY_ADD(DISPATCH_SERVICE_GETINFO, L"serviceGetInfo", OnServiceGetInfo)
+ DISPENTRY_ADD(DISPATCH_SERVICE_SETINFO, L"serviceSetInfo", OnServiceSetInfo)
+DISPTABLE_END
+
+#undef DISPTABLE_CLASS
+
+
+static BOOL DispParam_GetStringOpt(LPCWSTR *str, DISPPARAMS *paramInfo, UINT paramNumber, UINT *argErr)
+{
+ if (paramInfo->cArgs < paramNumber ||
+ VT_NULL == paramInfo->rgvarg[paramInfo->cArgs - paramNumber].vt)
+ {
+ *str = NULL;
+ return FALSE;
+ }
+
+ JSAPI_GETSTRING((*str), paramInfo, paramNumber, argErr);
+ return TRUE;
+}
+
+ExternalDispatch::ExternalDispatch()
+ : ref(1)
+{
+}
+
+ExternalDispatch::~ExternalDispatch()
+{
+}
+
+HRESULT ExternalDispatch::CreateInstance(ExternalDispatch **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ *instance = new ExternalDispatch();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+LPCWSTR ExternalDispatch::GetName()
+{
+ return L"WebDev";
+}
+
+ULONG ExternalDispatch::AddRef(void)
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+
+ULONG ExternalDispatch::Release(void)
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+STDMETHODIMP ExternalDispatch::QueryInterface(REFIID riid, void **ppvObject)
+{
+ if (NULL == ppvObject) return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = static_cast<IDispatch*>(this);
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = static_cast<IUnknown*>(this);
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceOpen(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 1, 2);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+
+ UINT serviceId;
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 1, puArgErr);
+
+ LPCWSTR forceUrl = NULL;
+ if (pdispparams->cArgs > 1)
+ {
+ switch(pdispparams->rgvarg[0].vt)
+ {
+ case VT_BSTR: forceUrl = pdispparams->rgvarg[0].bstrVal; break;
+ case VT_I4: forceUrl = MAKEINTRESOURCE(pdispparams->rgvarg[0].lVal); break;
+ }
+ }
+
+ if (FALSE == DispParam_GetStringOpt(&forceUrl, pdispparams,2, puArgErr))
+ forceUrl = NULL;
+
+ HRESULT hr;
+
+ Navigation *navigation;
+ hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ if (NULL != navigation->FindService(serviceId, &service))
+ {
+ hr = Command_NavigateService(service, forceUrl, FALSE);
+ service->Release();
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ navigation->Release();
+ }
+
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceCreate(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ HRESULT hr = Command_CreateService();
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceGetInfo(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_DISPATCH);
+
+ UINT serviceId;
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 1, puArgErr);
+
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ if (NULL != navigation->FindService(serviceId, &service))
+ {
+ WCHAR szBuffer[2048];
+ JSAPI::CallbackParameters *params = new JSAPI::CallbackParameters;
+ params->AddLong(L"id", service->GetId());
+
+ if (FAILED(service->GetName(szBuffer, ARRAYSIZE(szBuffer)))) szBuffer[0] = L'\0';
+ params->AddString(L"name", szBuffer);
+
+ if (FAILED(service->GetUrl(szBuffer, ARRAYSIZE(szBuffer)))) szBuffer[0] = L'\0';
+ params->AddString(L"url", szBuffer);
+
+ if (FAILED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer)))) szBuffer[0] = L'\0';
+ params->AddString(L"icon", szBuffer);
+
+ params->AddBoolean(L"preauthorized", (S_OK == ServiceHelper_IsPreAuthorized(service)));
+
+ service->Release();
+ V_DISPATCH(pvarResult) = params;
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ navigation->Release();
+ }
+
+ if (FAILED(hr))
+ {
+ V_DISPATCH(pvarResult) = 0;
+ }
+
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceSetInfo(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 2, 5);
+
+ UINT serviceId;
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 1, puArgErr);
+
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ HNAVITEM hItem = navigation->FindService(serviceId, &service);
+ if (NULL != hItem)
+ {
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ LPCWSTR value = NULL;
+
+ editor->BeginUpdate();
+
+ if (FALSE != DispParam_GetStringOpt(&value, pdispparams, 2, puArgErr) && FAILED(editor->SetName(value, FALSE)))
+ hr = E_FAIL;
+
+ if (FALSE != DispParam_GetStringOpt(&value, pdispparams, 3, puArgErr) && FAILED(ServiceHelper_UpdateIcon(editor, value)))
+ hr = E_FAIL;
+
+ if (FALSE != DispParam_GetStringOpt(&value, pdispparams, 4, puArgErr) && FAILED(editor->SetUrl(value, FALSE)))
+ hr = E_FAIL;
+
+ VARIANT_BOOL authorized = JSAPI_PARAM_OPTIONAL(pdispparams, 5, boolVal, VARIANT_FALSE);
+ if (S_OK == editor->SetFlags((VARIANT_TRUE == authorized) ? SVCF_PREAUTHORIZED : 0, SVCF_PREAUTHORIZED))
+ {
+ if (VARIANT_TRUE == authorized)
+ ServiceHelper_ResetPermissions(service, 0);
+ }
+
+ editor->EndUpdate();
+ editor->Release();
+ }
+
+ if(SUCCEEDED(hr))
+ hr = ServiceHelper_Save(service);
+
+ service->Release();
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ navigation->Release();
+ }
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/external.h b/Src/Plugins/Library/ml_online/external.h
new file mode 100644
index 00000000..e3dfe6ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/external.h
@@ -0,0 +1,49 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_EXTERNAL_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_EXTERNAL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/dispatchTable.h"
+
+class ExternalDispatch : public IDispatch
+{
+
+public:
+ typedef enum
+ {
+ DISPATCH_SERVICE_OPEN = 700,
+ DISPATCH_SERVICE_CREATE = 701,
+ DISPATCH_SERVICE_GETINFO = 702,
+ DISPATCH_SERVICE_SETINFO = 703,
+ } DispatchCodes;
+
+protected:
+ ExternalDispatch();
+ ~ExternalDispatch();
+
+public:
+ static HRESULT CreateInstance(ExternalDispatch **instance);
+ static LPCWSTR GetName();
+
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+protected:
+ DISPTABLE_INCLUDE();
+ DISPHANDLER_REGISTER(OnServiceOpen);
+ DISPHANDLER_REGISTER(OnServiceCreate);
+ DISPHANDLER_REGISTER(OnServiceGetInfo);
+ DISPHANDLER_REGISTER(OnServiceSetInfo);
+
+protected:
+ ULONG ref;
+
+};
+
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_EXTERNAL_HEADER
diff --git a/Src/Plugins/Library/ml_online/forceUrl.cpp b/Src/Plugins/Library/ml_online/forceUrl.cpp
new file mode 100644
index 00000000..3ad4a1e9
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/forceUrl.cpp
@@ -0,0 +1,51 @@
+#include "main.h"
+#include "./forceUrl.h"
+
+ForceUrl::ForceUrl() : id((UINT)-1), url(NULL)
+{
+}
+ForceUrl::~ForceUrl()
+{
+ Plugin_FreeString(url);
+}
+
+HRESULT ForceUrl::Set(UINT serviceId, LPCWSTR pszUrl)
+{
+ Plugin_FreeString(url);
+
+ id = serviceId;
+ url = Plugin_CopyString(pszUrl);
+
+ return S_OK;
+}
+
+HRESULT ForceUrl::Peek(UINT serviceId, LPWSTR *pszUrl)
+{
+ if (NULL == pszUrl) return E_POINTER;
+ if (serviceId == id && NULL != url)
+ {
+ *pszUrl = url;
+ url = NULL;
+ id = ((UINT)-1);
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+HRESULT ForceUrl::Remove(UINT serviceId)
+{
+ if (id == serviceId)
+ {
+ Plugin_FreeString(url);
+ url = NULL;
+ id = ((UINT)-1);
+ return S_OK;
+ }
+ return S_FALSE;
+}
+
+void ForceUrl::FreeString(LPWSTR pszValue)
+{
+ Plugin_FreeString(pszValue);
+}
diff --git a/Src/Plugins/Library/ml_online/forceUrl.h b/Src/Plugins/Library/ml_online/forceUrl.h
new file mode 100644
index 00000000..28a3f863
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/forceUrl.h
@@ -0,0 +1,27 @@
+#ifndef NULLOSFT_ONLINEMEDIA_FORCE_URL_HEADER
+#define NULLOSFT_ONLINEMEDIA_FORCE_URL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ForceUrl
+{
+public:
+ ForceUrl();
+ ~ForceUrl();
+
+public:
+ HRESULT Set(UINT serviceId, LPCWSTR pszUrl);
+ HRESULT Peek(UINT serviceId, LPWSTR *pszUrl);
+ HRESULT Remove(UINT serviceId);
+
+ void FreeString(LPWSTR pszValue);
+private:
+ UINT id;
+ LPWSTR url;
+};
+
+#endif //NULLOSFT_ONLINEMEDIA_FORCE_URL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/handler.cpp b/Src/Plugins/Library/ml_online/handler.cpp
new file mode 100644
index 00000000..2191725e
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/handler.cpp
@@ -0,0 +1,168 @@
+#include "main.h"
+#include "navigation.h"
+#include "servicehelper.h"
+#include "handler.h"
+#include "ifc_omservice.h"
+#include "../Agave/URIHandler/svc_urihandler.h"
+#include <api/service/waservicefactory.h>
+#include "api.h"
+
+#include <shlwapi.h>
+
+static uint8_t quickhex(wchar_t c)
+{
+ int hexvalue = c;
+ if (hexvalue & 0x10)
+ hexvalue &= ~0x30;
+ else
+ {
+ hexvalue &= 0xF;
+ hexvalue += 9;
+ }
+ return hexvalue;
+}
+
+static uint8_t DecodeEscape(const wchar_t *&str)
+{
+ uint8_t a = quickhex(*++str);
+ uint8_t b = quickhex(*++str);
+ str++;
+ return a * 16 + b;
+}
+
+static void DecodeEscapedUTF8(wchar_t *&output, const wchar_t *&input)
+{
+ uint8_t utf8_data[1024] = {0}; // hopefully big enough!!
+ int num_utf8_words=0;
+ bool error=false;
+
+ while (input && *input == '%' && num_utf8_words < sizeof(utf8_data))
+ {
+ if (iswxdigit(input[1]) && iswxdigit(input[2]))
+ {
+ utf8_data[num_utf8_words++]=DecodeEscape(input);
+ }
+ else if (input[1] == '%')
+ {
+ input+=2;
+ utf8_data[num_utf8_words++]='%';
+ }
+ else
+ {
+ error = true;
+ break;
+ }
+ }
+
+ int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, 0, 0);
+ MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, output, len);
+ output += len;
+
+ if (error)
+ {
+ *output++ = *input++;
+ }
+}
+
+static void UrlDecode(const wchar_t *input, wchar_t *output, size_t len)
+{
+ const wchar_t *stop = output+len-4; // give ourself a cushion large enough to hold a full UTF-16 sequence
+ const wchar_t *itr = input;
+ while (itr && *itr)
+ {
+ if (output >= stop)
+ {
+ *output=0;
+ return;
+ }
+
+ switch (*itr)
+ {
+ case '%':
+ DecodeEscapedUTF8(output, itr);
+ break;
+ case '&':
+ *output = 0;
+ return;
+ default:
+ *output++ = *itr++;
+ break;
+ }
+ }
+ *output = 0;
+}
+// first parameter has param name either null or = terminated, second is null terminated
+static bool ParamCompare(const wchar_t *url_param, const wchar_t *param_name)
+{
+ while (url_param && *url_param && *param_name && *url_param!=L'=')
+ {
+ if (*url_param++ != *param_name++)
+ return false;
+ }
+ return true;
+}
+
+static bool get_request_parm(const wchar_t *params, const wchar_t *param_name, wchar_t *value, size_t value_len)
+{
+ const wchar_t *t=params;
+ while (t && *t && *t != L'?') // find start of parameters
+ t++;
+
+ while (t && *t)
+ {
+ t++; // skip ? or &
+ if (ParamCompare(t, param_name))
+ {
+ while (t && *t && *t != L'=' && *t != '&') // find start of value
+ t++;
+ switch(*t)
+ {
+ case L'=':
+ UrlDecode(++t, value, value_len);
+ return true;
+ case 0:
+ case L'&': // no value
+ *value=0;
+ return true;
+ default: // shouldn't get here
+ return false;
+ }
+ }
+ while (t && *t && *t != L'&') // find next parameter
+ t++;
+ }
+ return false;
+}
+
+int OnlineServicesURIHandler::ProcessFilename(const wchar_t *filename)
+{
+ if (HANDLED != IsMine(filename))
+ return NOT_HANDLED;
+
+ UINT serviceId = 0;
+ wchar_t szBuffer[512]=L"";
+ if (get_request_parm(filename, L"id", szBuffer, ARRAYSIZE(szBuffer)) &&
+ L'\0' != szBuffer[0])
+ {
+ if (FALSE == StrToIntEx(szBuffer, STIF_SUPPORT_HEX, (INT*)&serviceId))
+ serviceId = 0;
+ }
+
+ ServiceHelper_ShowService(serviceId, SHOWMODE_ENSUREVISIBLE);
+ return HANDLED_EXCLUSIVE;
+}
+
+int OnlineServicesURIHandler::IsMine(const wchar_t *filename)
+{
+ if (!_wcsnicmp(filename, L"winamp://Online Services", 24) || !_wcsnicmp(filename, L"winamp://Online%20Services", 26))
+ return HANDLED;
+ else
+ return NOT_HANDLED;
+}
+
+#define CBCLASS OnlineServicesURIHandler
+START_DISPATCH;
+CB(PROCESSFILENAME, ProcessFilename);
+CB(ISMINE, IsMine);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/handler.h b/Src/Plugins/Library/ml_online/handler.h
new file mode 100644
index 00000000..a228a396
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/handler.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "../Agave/URIHandler/svc_urihandler.h"
+
+// {1E8830FA-0BDA-45fa-B106-BDF56C93BADD}
+static const GUID ml_online_uri_handler =
+{ 0x1e8830fa, 0xbda, 0x45fa, { 0xb1, 0x6, 0xbd, 0xf5, 0x6c, 0x93, 0xba, 0xdd } };
+
+
+class OnlineServicesURIHandler : public svc_urihandler
+{
+public:
+ static const char *getServiceName() { return "Online Services URI Handler"; }
+ static GUID getServiceGuid() { return ml_online_uri_handler; }
+ int ProcessFilename(const wchar_t *filename);
+ int IsMine(const wchar_t *filename); // just like ProcessFilename but don't actually process
+
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/import.h b/Src/Plugins/Library/ml_online/import.h
new file mode 100644
index 00000000..b1c079c7
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/import.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_IMPORT_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_IMPORT_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+HRESULT ImportService_GetFileSupported();
+HRESULT ImportService_GetUrlSupported();
+
+HRESULT ImportService_FromFile(HWND hOwner);
+HRESULT ImportService_FromUrl(HWND hOwner);
+
+void ImportService_SaveRecentUrl();
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_IMPORT_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/importFile.cpp b/Src/Plugins/Library/ml_online/importFile.cpp
new file mode 100644
index 00000000..d186306d
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/importFile.cpp
@@ -0,0 +1,265 @@
+#include "main.h"
+#include "./import.h"
+#include "./api__ml_online.h"
+#include "./resource.h"
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+#include "./navigation.h"
+
+#include <ifc_omstorage.h>
+#include <ifc_omfilestorage.h>
+#include <ifc_omstorageenum.h>
+#include <ifc_omservice.h>
+#include <ifc_omserviceenum.h>
+
+#include <strsafe.h>
+
+static HRESULT ImportFile_GetEnumerator(ifc_omstorageenumerator **enumerator)
+{
+ if (NULL == OMSERVICEMNGR) return E_UNEXPECTED;
+ return OMSERVICEMNGR->EnumStorage(&STID_OmFileStorage, ifc_omstorage::capPublic | ifc_omstorage::capLoad, enumerator);
+}
+
+HRESULT ImportService_GetFileSupported()
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportFile_GetEnumerator(&enumerator);
+
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage;
+ if(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ storage->Release();
+ hr = S_OK;
+ }
+ else
+ {
+ hr = S_FALSE;
+ }
+
+ enumerator->Release();
+ }
+ return hr;
+}
+
+static HRESULT ImportFile_GetFilter(LPWSTR pszBuffer, UINT cchBufferMax, DWORD *defaultIndex)
+{
+ if (NULL != defaultIndex)
+ *defaultIndex = 0;
+
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ HRESULT hr;
+ WCHAR szName[128] = {0}, szList[512] = {0};
+
+ LPWSTR cursor = pszBuffer;
+ size_t remaining = cchBufferMax;
+
+ LPWSTR listC = szList;
+ size_t listR = ARRAYSIZE(szList);
+
+ DWORD counter = 0;
+
+ szList[0] = L'\0';
+ pszBuffer[0] = L'\0';
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_FILEFILTER_ALL, szName, ARRAYSIZE(szName));
+ hr = Plugin_AppendFileFilter(cursor, remaining, szName, L"*.*", &cursor, &remaining, TRUE);
+ if (FAILED(hr)) return hr;
+ counter++;
+
+ ifc_omstorageenumerator *enumerator;
+ hr = ImportFile_GetEnumerator(&enumerator);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage;
+ ifc_omfilestorage *fileStorage;
+ while(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ if (SUCCEEDED(storage->QueryInterface(IFC_OmFileStorage, (void**)&fileStorage)))
+ {
+ WCHAR szFilter[64] = {0};
+ if (SUCCEEDED(fileStorage->GetFilter(szFilter, ARRAYSIZE(szFilter))) &&
+ L'\0' != szFilter[0] &&
+ SUCCEEDED(storage->GetDescription(szName, ARRAYSIZE(szName))))
+ {
+ hr = Plugin_AppendFileFilter(cursor, remaining, szName, szFilter, &cursor, &remaining, TRUE);
+ if (FAILED(hr)) break;
+
+ counter++;
+
+ if (listC == szList || SUCCEEDED(StringCchCopyEx(listC, listR, L";", &listC, &listR, 0)))
+ StringCchCopyEx(listC, listR, szFilter, &listC, &listR, 0);
+ }
+ fileStorage->Release();
+ }
+ storage->Release();
+ }
+ enumerator->Release();
+ }
+
+ if (SUCCEEDED(hr) && L'\0' != szList[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_FILEFILTER_ALLKNOWN, szName, ARRAYSIZE(szName));
+ hr = Plugin_AppendFileFilter(cursor, remaining, szName, szList, &cursor, &remaining, TRUE);
+ if (FAILED(hr)) return hr;
+
+ counter++;
+
+ if (NULL != defaultIndex)
+ *defaultIndex = counter;
+ }
+
+ return hr;
+}
+
+static HRESULT ImportFile_ProcessFile(HWND hOwner, ifc_omstorageenumerator *enumerator,
+ ifc_omservicehost *serviceHost, ifc_omstorage *serviceStorage,
+ LPCWSTR pszFile, ULONG *converted)
+{
+ ifc_omstorage *storage;
+ enumerator->Reset();
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_FAIL;
+
+ ULONG loaded(0), saved(0);
+ while(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ ifc_omserviceenum *serviceEnum;
+ HRESULT hr = storage->Load(pszFile, serviceHost, &serviceEnum);
+ if(SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ while(S_OK == serviceEnum->Next(1, &service, NULL))
+ {
+ loaded++;
+ if (SUCCEEDED(service->SetAddress(NULL)))
+ {
+ service->UpdateFlags(SVCF_SUBSCRIBED | SVCF_PREAUTHORIZED);
+
+ ULONG savedOk;
+ if (SUCCEEDED(serviceStorage->Save(&service, 1, ifc_omstorage::saveClearModified, &savedOk)))
+ {
+ navigation->CreateItem(service, 1);
+ saved += savedOk;
+ }
+ }
+ service->Release();
+ }
+ serviceEnum->Release();
+ break;
+ }
+ else if (OMSTORAGE_E_UNKNOWN_FORMAT != hr)
+ {
+ break;
+ }
+
+ storage->Release();
+ }
+
+ if (NULL != converted)
+ *converted = saved;
+
+ navigation->Release();
+
+ return S_OK;
+}
+
+static HRESULT ImportFile_ProcessList(HWND hOwner, LPCWSTR pszList)
+{
+ if (NULL == pszList)
+ return E_INVALIDARG;
+
+ LPCWSTR base, block, c;
+ base = pszList;
+ c = base;
+ block = NULL;
+ ULONG converted;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportFile_GetEnumerator(&enumerator);
+ if (FAILED(hr)) return hr;
+
+ ServiceHost *serviceHost;
+ hr = ServiceHost::GetCachedInstance(&serviceHost);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *serviceStorage;
+ hr = ServiceHelper_QueryStorage(&serviceStorage);
+ if (SUCCEEDED(hr))
+ {
+ ULONG scanned(0);
+ while(L'\0' != *c)
+ {
+ block = c;
+ while (L'\0' != *c) c++;
+ if (c != block && block != base)
+ {
+ WCHAR szBuffer[MAX_PATH * 2] = {0};
+ scanned++;
+ if (SUCCEEDED(StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), L"%s\\%s", base, block)) &&
+ SUCCEEDED(ImportFile_ProcessFile(hOwner, enumerator, serviceHost, serviceStorage, szBuffer, &converted)))
+ {
+ }
+ }
+ c++;
+ }
+
+ if (pszList == block && c != pszList &&
+ SUCCEEDED(ImportFile_ProcessFile(hOwner, enumerator, serviceHost, serviceStorage, pszList, &converted)))
+ {
+ }
+ serviceStorage->Release();
+ }
+ serviceHost->Release();
+ }
+
+ enumerator->Release();
+ return hr;
+}
+
+HRESULT ImportService_FromFile(HWND hOwner)
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ OPENFILENAME of = {0};
+ of.lStructSize = sizeof(of);
+
+ WCHAR szFilter[1024] = {0};
+ HRESULT hr = ImportFile_GetFilter(szFilter, ARRAYSIZE(szFilter), &of.nFilterIndex);
+ if (FAILED(hr)) return hr;
+
+ UINT cchResultMax = 16384;
+ LPWSTR pszResult = Plugin_MallocString(cchResultMax);
+ if (NULL == pszResult) return E_OUTOFMEMORY;
+ *pszResult = L'\0';
+
+ of.hwndOwner = hOwner;
+ of.lpstrFilter = szFilter;
+ of.lpstrFile = pszResult;
+ of.nMaxFile = cchResultMax;
+ of.lpstrInitialDir = WASABI_API_APP->path_getUserSettingsPath();
+ of.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_IMPORT_FILES);
+ of.Flags = OFN_ENABLESIZING | OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_ALLOWMULTISELECT;
+
+ if (0 == GetOpenFileName(&of))
+ {
+ INT err = CommDlgExtendedError();
+ hr = (0 == err) ? S_FALSE : E_FAIL;
+ }
+ else
+ {
+ hr = ImportFile_ProcessList(hOwner, pszResult);
+ }
+
+ Plugin_FreeString(pszResult);
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/importUrl.cpp b/Src/Plugins/Library/ml_online/importUrl.cpp
new file mode 100644
index 00000000..201bea1c
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/importUrl.cpp
@@ -0,0 +1,321 @@
+#include "main.h"
+#include "./import.h"
+#include "./api__ml_online.h"
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+#include "./navigation.h"
+#include "./resource.h"
+#include "./config.h"
+
+#include <ifc_omstorage.h>
+#include <ifc_omwebstorage.h>
+#include <ifc_omstorageenum.h>
+#include <ifc_omservice.h>
+#include <ifc_omserviceenum.h>
+
+#include <vector>
+
+#include <strsafe.h>
+
+
+typedef std::vector<WCHAR*> StringList;
+
+static StringList recentList;
+static BOOL recentListModified = FALSE;
+
+typedef struct __OPENURLDLG
+{
+ HWND hOwner;
+ LPCWSTR pszAddress;
+ LPWSTR pszBuffer;
+ UINT cchBufferMax;
+} OPENURLDLG;
+
+static INT_PTR ImportUrlDlg_Show(OPENURLDLG *poud);
+
+static HRESULT ImportUrl_GetEnumerator(ifc_omstorageenumerator **enumerator)
+{
+ if (NULL == OMSERVICEMNGR) return E_UNEXPECTED;
+ return OMSERVICEMNGR->EnumStorage(&STID_OmWebStorage, ifc_omstorage::capPublic | ifc_omstorage::capLoad, enumerator);
+}
+
+HRESULT ImportService_GetUrlSupported()
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportUrl_GetEnumerator(&enumerator);
+
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage;
+ if(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ storage->Release();
+ hr = S_OK;
+ }
+ else
+ {
+ hr = S_FALSE;
+ }
+
+ enumerator->Release();
+ }
+ return hr;
+}
+
+HRESULT ImportUrl_LoadAddress(HWND hOwner, LPCWSTR pszAddress, ifc_omstorageenumerator *enumerator,
+ ServiceHost *serviceHost, ifc_omstorage *serviceStorage, Navigation *navigation)
+{
+ HRESULT hr = S_OK;
+ ULONG loaded(0);
+
+ ifc_omstorage *storage;
+ enumerator->Reset();
+
+ while(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ ifc_omstorageasync *async;
+ hr = storage->BeginLoad(pszAddress, serviceHost, NULL, NULL, &async);
+ if(SUCCEEDED(hr))
+ {
+ ifc_omserviceenum *serviceEnum;
+ hr = storage->EndLoad(async, &serviceEnum);
+ async->Release();
+
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ while(S_OK == serviceEnum->Next(1, &service, NULL))
+ {
+ loaded++;
+ if (SUCCEEDED(service->SetAddress(NULL)))
+ {
+ ULONG savedOk;
+ if (SUCCEEDED(serviceStorage->Save(&service, 1, ifc_omstorage::saveClearModified, &savedOk)))
+ {
+ navigation->CreateItem(service, 1);
+ }
+ }
+ service->Release();
+ }
+ serviceEnum->Release();
+ }
+ break;
+ }
+ else if (OMSTORAGE_E_UNKNOWN_FORMAT != hr)
+ {
+ break;
+ }
+ storage->Release();
+ }
+
+ return hr;
+}
+
+HRESULT ImportService_FromUrl(HWND hOwner)
+{
+ OPENURLDLG dlg = {0};
+ WCHAR szBuffer[4096] = {0};
+
+ dlg.hOwner = hOwner;
+ dlg.pszAddress = NULL;
+ dlg.pszBuffer = szBuffer;
+ dlg.cchBufferMax = ARRAYSIZE(szBuffer);
+ if (IDOK != ImportUrlDlg_Show(&dlg))
+ return S_FALSE;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportUrl_GetEnumerator(&enumerator);
+ if (SUCCEEDED(hr))
+ {
+ Navigation *navigation;
+ hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *serviceStorage;
+ hr = ServiceHelper_QueryStorage(&serviceStorage);
+ if (SUCCEEDED(hr))
+ {
+ ServiceHost *serviceHost;
+ hr = ServiceHost::GetCachedInstance(&serviceHost);
+ if (SUCCEEDED(hr))
+ {
+ hr = ImportUrl_LoadAddress(hOwner, szBuffer, enumerator, serviceHost, serviceStorage, navigation);
+ serviceHost->Release();
+ }
+ serviceStorage->Release();
+ }
+ navigation->Release();
+ }
+ enumerator->Release();
+ }
+ return hr;
+}
+
+static INT_PTR ImportUrlDlg_OnInit(HWND hwnd, HWND hFocus, LPARAM param)
+{
+ OPENURLDLG *poud = (OPENURLDLG*)param;
+ if (NULL != poud)
+ {
+ SetProp(hwnd, L"OPENURLDLG", poud);
+
+ HWND hAddress = GetDlgItem(hwnd, IDC_ADDRESS);
+ if (NULL != hAddress)
+ {
+ LPWSTR p;
+
+ size_t count = recentList.size();
+ if (0 == count)
+ {
+ char szKey[32] = {0}, szBuffer[4096] = {0};
+ for(int i = 1; i < 101; i++)
+ {
+ if (FAILED(StringCchPrintfA(szKey, ARRAYSIZE(szKey), "entry%d", i)) ||
+ 0 == Config_ReadStr("RecentUrl", szKey, NULL, szBuffer, ARRAYSIZE(szBuffer)) ||
+ '\0' == szBuffer[0])
+ {
+ break;
+ }
+
+ p = Plugin_MultiByteToWideChar(CP_UTF8, 0, szBuffer, -1);
+ if (NULL != p) recentList.push_back(p);
+ }
+ count = recentList.size();
+ }
+
+ for (size_t i = 0; i < count; i ++)
+ {
+ p = recentList[i];
+ if(NULL != p && L'\0' != *p)
+ SendMessage(hAddress, CB_ADDSTRING, 0, (LPARAM)p);
+ }
+
+ if (NULL != poud->pszAddress)
+ SetWindowText(hAddress, poud->pszAddress);
+ }
+
+ RECT ownerRect;
+ if (NULL != poud->hOwner && IsWindowVisible(poud->hOwner) &&
+ GetWindowRect(poud->hOwner, &ownerRect))
+ {
+ RECT myRect;
+ GetWindowRect(hwnd, &myRect);
+ LONG x = ownerRect.left + ((ownerRect.right - ownerRect.left) - (myRect.right - myRect.left))/2;
+ LONG y = ownerRect.top + ((ownerRect.bottom - ownerRect.top) - (myRect.bottom - myRect.top))/2;
+ SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+ }
+
+ return 0;
+}
+
+static void ImportUrlDlg_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, L"OPENURLDLG");
+
+}
+static INT_PTR ImportUrlDlg_ReturnAddress(HWND hwnd)
+{
+ OPENURLDLG *poud = (OPENURLDLG*)GetProp(hwnd, L"OPENURLDLG");
+ if (NULL == poud) return -1;
+
+ HWND hAddress = GetDlgItem(hwnd, IDC_ADDRESS);
+ if (NULL == hAddress) return -2;
+
+ if (0 == GetWindowText(hAddress, poud->pszBuffer, poud->cchBufferMax))
+ return -3;
+
+ if (NULL != poud->pszBuffer && L'\0' != *poud->pszBuffer)
+ {
+ LPWSTR p;
+ size_t index = recentList.size();
+ while(index--)
+ {
+ p = recentList[index];
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, poud->pszBuffer, -1, p, -1))
+ {
+ Plugin_FreeString(p);
+ recentList.erase(recentList.begin() + index);
+ }
+ }
+
+ p = Plugin_CopyString(poud->pszBuffer);
+ if (NULL != p)
+ {
+ recentList.insert(recentList.begin(), p);
+ recentListModified = TRUE;
+ }
+ }
+ return IDOK;
+}
+
+static void ImportUrlDlg_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND hControl)
+{
+ switch(commandId)
+ {
+ case IDOK:
+ EndDialog(hwnd, ImportUrlDlg_ReturnAddress(hwnd));
+ break;
+ case IDCANCEL:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+ }
+
+}
+static INT_PTR WINAPI ImportUrlDlg_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return ImportUrlDlg_OnInit(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: ImportUrlDlg_OnDestroy(hwnd); break;
+ case WM_COMMAND: ImportUrlDlg_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ }
+
+ return 0;
+
+}
+
+
+static INT_PTR ImportUrlDlg_Show(OPENURLDLG *poud)
+{
+ if (NULL == poud || NULL == poud->pszBuffer)
+ return -1;
+
+ HWND hParent = poud->hOwner;
+ if (NULL == poud->hOwner)
+ hParent = Plugin_GetLibrary();
+
+ return WASABI_API_DIALOGBOXPARAMW(IDD_OPENURL, hParent, ImportUrlDlg_DialogProc, (LPARAM)poud);
+
+}
+
+void ImportService_SaveRecentUrl()
+{
+ if (FALSE == recentListModified)
+ return;
+
+ Config_WriteSection("RecentUrl", NULL);
+
+ size_t count = recentList.size();
+ if (count > 100) count = 100;
+
+ char szKey[32], szBuffer[4096];
+ UINT entry = 1;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ LPCWSTR p = recentList[i];
+ if (NULL != p && L'\0' != *p &&
+ 0 != WideCharToMultiByte(CP_UTF8, 0, p, -1, szBuffer, ARRAYSIZE(szBuffer), NULL, NULL) &&
+ SUCCEEDED(StringCchPrintfA(szKey, ARRAYSIZE(szKey), "entry%d", entry)) &&
+ SUCCEEDED(Config_WriteStr("RecentUrl", szKey, szBuffer)))
+ {
+ entry++;
+ }
+ }
+
+ recentListModified = FALSE;
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/jsapi2_omcom.cpp b/Src/Plugins/Library/ml_online/jsapi2_omcom.cpp
new file mode 100644
index 00000000..bebe30d3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/jsapi2_omcom.cpp
@@ -0,0 +1,136 @@
+#include "./main.h"
+#include "./api__ml_online.h"
+#include "./jsapi2_omcom.h"
+#include "./navigation.h"
+
+#include <ifc_omservice.h>
+#include <browserView.h>
+
+#include "../Winamp/JSAPI.h"
+
+
+JSAPI2::OnlineServicesAPI::OnlineServicesAPI(const wchar_t *_key, JSAPI::ifc_info *_info)
+{
+ info = _info;
+ key = _key;
+ refCount = 1;
+}
+
+#define DISP_TABLE \
+ CHECK_ID(Login)\
+
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+};
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = JSAPI_DISP_ENUMIFY(str); continue; }
+HRESULT JSAPI2::OnlineServicesAPI::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ DISP_TABLE
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::OnlineServicesAPI::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::OnlineServicesAPI::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::OnlineServicesAPI::Login(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_BSTR, puArgErr);
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_FALSE);
+ const wchar_t *url = JSAPI_PARAM(pdispparams, 1).bstrVal;
+
+// if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"onlineservices", L"login", key, info, JSAPI2::api_security::ACTION_DISALLOWED) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ HWND hBrowser = info->GetHWND();
+ if (NULL != hBrowser && IsWindow(hBrowser))
+ {
+ ifc_omservice *service;
+ if (FALSE != BrowserControl_GetService(hBrowser, &service))
+ {
+ wchar_t szBuffer[4096] = {0};
+ LPCWSTR navigateUrl;
+
+ if (NULL != AGAVE_API_AUTH &&
+ 0 == AGAVE_API_AUTH->ClientToWeb(GUID_NULL, url, szBuffer, ARRAYSIZE(szBuffer)))
+ {
+ navigateUrl = szBuffer;
+ }
+ else
+ navigateUrl = url;
+
+ BrowserControl_Navigate(hBrowser, navigateUrl, TRUE);
+
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ service->Release();
+ }
+ }
+ }
+ return S_OK;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdispparams, pvarResult, puArgErr);
+HRESULT JSAPI2::OnlineServicesAPI::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ DISP_TABLE
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JSAPI2::OnlineServicesAPI::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::OnlineServicesAPI::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+
+ULONG JSAPI2::OnlineServicesAPI::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
diff --git a/Src/Plugins/Library/ml_online/jsapi2_omcom.h b/Src/Plugins/Library/ml_online/jsapi2_omcom.h
new file mode 100644
index 00000000..970ef323
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/jsapi2_omcom.h
@@ -0,0 +1,27 @@
+#pragma once
+#include <ocidl.h>
+#include "../Winamp/JSAPI_Info.h"
+
+namespace JSAPI2
+{
+ class OnlineServicesAPI : public IDispatch
+ {
+ public:
+ OnlineServicesAPI(const wchar_t *_key, JSAPI::ifc_info *info);
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+ private:
+ const wchar_t *key;
+ volatile LONG refCount;
+ JSAPI::ifc_info *info;
+
+ STDMETHOD (Login)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ };
+}
+
diff --git a/Src/Plugins/Library/ml_online/local_menu.cpp b/Src/Plugins/Library/ml_online/local_menu.cpp
new file mode 100644
index 00000000..2548ad18
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/local_menu.cpp
@@ -0,0 +1,379 @@
+#include "./main.h"
+#include "./local_menu.h"
+#include "../nu/menuHelpers.h"
+#include "./resource.h"
+#include "./api__ml_online.h"
+#include "../../General/gen_ml/menu.h"
+
+#include <windows.h>
+#include <strsafe.h>
+
+#define MENU_SERVICECONTEXT 0
+#define MENU_GALERYCONTEXT 1
+#define MENU_RATING 2
+#define MENU_VIEW 3
+#define MENU_NAVIGATION 4
+#define MENU_TOOLBAR 5
+
+#define RATING_MARKER MAKELONG(MAKEWORD('R','A'),MAKEWORD('T','E'))
+
+#define RATING_MINSPACECX 16
+
+
+
+typedef BOOL (__cdecl *MLISSKINNEDPOPUPENABLED)(void);
+typedef HANDLE (__cdecl *MLINITSKINNEDPOPUPHOOK)(HWND /*hwnd*/, HMLIMGLST /*hmlil*/, INT /*width*/, UINT /*skinStyle*/,
+ MENUCUSTOMIZEPROC /*customProc*/, ULONG_PTR /*customParam*/);
+typedef HANDLE (__cdecl *MLREMOVESKINNEDPOPUPHOOK)(HANDLE /*hPopupHook*/);
+
+#if 0
+static BOOL Menu_IsRatingStar(HMENU hMenu, INT itemId, INT *valueOut)
+{
+ WCHAR szBuffer[8] = {0};
+ INT cchBuffer = GetMenuStringW(hMenu, itemId, szBuffer, ARRAYSIZE(szBuffer), MF_BYCOMMAND);
+ if (cchBuffer < 1 || cchBuffer > 5)
+ return FALSE;
+
+ for (INT i = 1; i < cchBuffer; i++)
+ {
+ if (szBuffer[i -1] != szBuffer[i])
+ return FALSE;
+ }
+
+ if (NULL != valueOut)
+ *valueOut = cchBuffer;
+
+ return TRUE;
+}
+
+static BOOL Menu_MeasureRating(HMENU hMenu, HDC hdc, MEASUREITEMSTRUCT *pmis)
+{
+ if (NULL == hdc || !Menu_IsRatingStar(hMenu, pmis->itemID, NULL))
+ return FALSE;
+
+ RECT rect;
+ if (!MLRating_CalcRect(Plugin_GetLibrary(), NULL, 5, &rect))
+ return FALSE;
+
+ pmis->itemHeight = rect.bottom - rect.top + 6;
+
+ TEXTMETRIC tm;
+ if (GetTextMetrics(hdc, &tm) &&
+ (UINT)(tm.tmHeight + 2) > pmis->itemHeight)
+ {
+ pmis->itemHeight = tm.tmHeight + 2;
+ }
+
+ INT spaceCX = (pmis->itemHeight > RATING_MINSPACECX) ? pmis->itemHeight : RATING_MINSPACECX;
+ pmis->itemWidth = rect.right - rect.left + (2 * spaceCX) - (GetSystemMetrics(SM_CXMENUCHECK) - 1);
+
+ return TRUE;
+}
+
+static BOOL Menu_DrawRating(HMENU hMenu, HDC hdc, DRAWITEMSTRUCT *pdis)
+{
+ INT ratingValue;
+ if (NULL == hdc || !Menu_IsRatingStar(hMenu, pdis->itemID, &ratingValue))
+ return FALSE;
+
+ INT spaceCX = ((pdis->rcItem.bottom - pdis->rcItem.top) > RATING_MINSPACECX) ?
+ (pdis->rcItem.bottom - pdis->rcItem.top) :
+ RATING_MINSPACECX;
+
+ RATINGDRAWPARAMS rdp = {0};
+ rdp.cbSize = sizeof(RATINGDRAWPARAMS);
+ rdp.hdcDst = hdc;
+ rdp.rc = pdis->rcItem;
+ rdp.rc.left += spaceCX;
+ rdp.value = ratingValue;
+ rdp.maxValue = 5;
+
+ UINT menuState = GetMenuState(hMenu, pdis->itemID, MF_BYCOMMAND);
+ rdp.trackingValue = (0 == ((MF_DISABLED | MF_GRAYED) & menuState)) ? rdp.value : 0;
+
+ rdp.fStyle = RDS_LEFT | RDS_VCENTER | RDS_HOT;
+ rdp.hMLIL = NULL;
+ rdp.index = 0;
+
+ return MLRating_Draw(Plugin_GetLibrary(), &rdp);
+}
+
+static BOOL CALLBACK Menu_CustomDrawProc(INT action, HMENU hMenu, HDC hdc, LPARAM param, ULONG_PTR user)
+{
+ switch(action)
+ {
+ case MLMENU_ACTION_MEASUREITEM:
+ if (hMenu == (HMENU)user)
+ return Menu_MeasureRating(hMenu, hdc, (MEASUREITEMSTRUCT*)param);
+ break;
+ case MLMENU_ACTION_DRAWITEM:
+ if (hMenu == (HMENU)user)
+ return MLMENU_WANT_DRAWPART;
+ break;
+ case MLMENU_ACTION_DRAWBACK:
+ break;
+ case MLMENU_ACTION_DRAWICON:
+ break;
+ case MLMENU_ACTION_DRAWTEXT:
+ if (hMenu == (HMENU)user)
+ return Menu_DrawRating(hMenu, hdc, (DRAWITEMSTRUCT*)param);
+ break;
+ }
+ return FALSE;
+}
+#endif
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue)
+{
+ if (NULL == ratingMenu) return FALSE;
+
+ INT ratingList[] = { ID_RATING_VALUE_1, ID_RATING_VALUE_2, ID_RATING_VALUE_3,
+ ID_RATING_VALUE_4, ID_RATING_VALUE_5};
+ ratingValue--;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+
+ UINT type, state;
+ for (INT i = 0; i < ARRAYSIZE(ratingList); i++)
+ {
+ mii.fMask = MIIM_STATE | MIIM_FTYPE;
+ if (GetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii))
+ {
+ if (ratingValue == i)
+ {
+ type = mii.fType | MFT_RADIOCHECK;
+ state = mii.fState | MFS_CHECKED;
+ }
+ else
+ {
+ type = mii.fType & ~MFT_RADIOCHECK;
+ state = mii.fState & ~MFS_CHECKED;
+ }
+
+ mii.fMask = 0;
+ if (type != mii.fType)
+ {
+ mii.fType = type;
+ mii.fMask |= MIIM_FTYPE;
+ }
+
+ if (state != mii.fState)
+ {
+ mii.fState = state;
+ mii.fMask |= MIIM_STATE;
+ }
+
+ if (0 != mii.fMask)
+ SetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii);
+ }
+ }
+ return TRUE;
+}
+
+static HMENU Menu_FindRatingMenuRecur(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii)
+{
+ if (GetMenuInfo(hMenu, pmi) && RATING_MARKER == pmi->dwMenuData)
+ return hMenu;
+
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii) && NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingMenuRecur(pmii->hSubMenu, pmi, pmii);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ return NULL;
+}
+
+HMENU Menu_FindRatingMenu(HMENU hMenu)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_SUBMENU;
+
+ MENUINFO mi;
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+
+ return Menu_FindRatingMenuRecur(hMenu, &mi, &mii);
+}
+
+static BOOL Menu_InsertRatingMenu(HMENU hDest, INT iPos, HMENU baseMenu, INT ratingValue)
+{
+ if ( 0 == MenuHelper_CopyMenuEx(hDest, iPos, baseMenu, MENU_RATING, 1))
+ return FALSE;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_SUBMENU;
+ if (GetMenuItemInfo(hDest, iPos, TRUE, &mii))
+ {
+ if (NULL != mii.hSubMenu)
+ {
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+ mi.dwMenuData = RATING_MARKER;
+ SetMenuInfo(mii.hSubMenu, &mi);
+
+ Menu_SetRatingValue(mii.hSubMenu, ratingValue);
+ }
+ }
+ return TRUE;
+}
+
+static HMENU Menu_CreateRatingMenu(HMENU baseMenu, INT ratingValue)
+{
+ HMENU menu = GetSubMenu(baseMenu, MENU_RATING);
+ if (NULL == menu) return NULL;
+
+ menu = MenuHelper_DuplcateMenu(menu);
+ if (NULL == menu) return NULL;
+
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+ mi.dwMenuData = RATING_MARKER;
+ SetMenuInfo(menu, &mi);
+
+ Menu_SetRatingValue(menu, ratingValue);
+ return menu;
+}
+
+static HMENU Menu_GetServiceContext(HMENU baseMenu, UINT flags, BOOL fGaleryMode)
+{
+ HMENU menu = GetSubMenu(baseMenu, (FALSE == fGaleryMode) ? MENU_SERVICECONTEXT : MENU_GALERYCONTEXT);
+ if (NULL == menu) return NULL;
+
+ menu = MenuHelper_DuplcateMenu(menu);
+ if (NULL == menu) return NULL;
+
+ HMENU embedMenu;
+ INT inserted;
+ INT total = GetMenuItemCount(menu);
+
+ if (FALSE == fGaleryMode)
+ {
+ // rating
+ if (Menu_InsertRatingMenu(menu, 0, baseMenu, RATINGFROMMCF(flags)))
+ total++;
+ }
+
+ if (0 != (MCF_NAVIGATION & flags))
+ {// navigation
+ embedMenu = GetSubMenu(baseMenu, MENU_NAVIGATION);
+ if (NULL != embedMenu)
+ {
+ inserted = MenuHelper_CopyMenu(menu, 0, embedMenu);
+ if (inserted > 0 && total > 0 && MenuHelper_InsertSeparator(menu, inserted))
+ inserted++;
+ total += inserted;
+ }
+ }
+
+ if (0 != (MCF_VIEW & flags))
+ {// view
+ embedMenu = GetSubMenu(baseMenu, MENU_VIEW);
+ if (NULL != embedMenu)
+ {
+ inserted = MenuHelper_CopyMenu(menu, 0, embedMenu);
+ if (inserted > 0 && total > 0 && MenuHelper_InsertSeparator(menu, inserted))
+ {
+ inserted++;
+ }
+ total += inserted;
+ }
+
+ EnableMenuItem(menu, ID_VIEW_OPEN , MF_BYCOMMAND | ((0 == (MCF_VIEWACTIVE & flags)) ? MF_ENABLED : MF_DISABLED));
+ if ( 0 == (MCF_VIEWACTIVE & flags))
+ SetMenuDefaultItem(menu, ID_VIEW_OPEN, FALSE);
+ }
+
+ return menu;
+}
+
+static HMENU Menu_GetToolbarContext(HMENU baseMenu, UINT flags)
+{
+ HMENU hMenu = GetSubMenu(baseMenu, MENU_TOOLBAR);
+ return hMenu;
+}
+
+void Menu_ConvertRatingMenuStar(HMENU menu, UINT menu_id)
+{
+ MENUITEMINFOW mi = {sizeof(mi), MIIM_DATA | MIIM_TYPE, MFT_STRING};
+ wchar_t rateBuf[32], *rateStr = rateBuf;
+ mi.dwTypeData = rateBuf;
+ mi.cch = 32;
+ if(GetMenuItemInfoW(menu, menu_id, FALSE, &mi))
+ {
+ while(rateStr && *rateStr)
+ {
+ if(*rateStr == L'*') *rateStr = L'\u2605';
+ rateStr=CharNextW(rateStr);
+ }
+ SetMenuItemInfoW(menu, menu_id, FALSE, &mi);
+ }
+}
+
+HMENU Menu_GetMenu(INT menuKind, UINT flags)
+{
+ HMENU baseMenu = WASABI_API_LOADMENUW(IDR_CONTEXTMENU);
+ if (NULL == baseMenu)
+ return NULL;
+
+ HMENU rate_hmenu = GetSubMenu(baseMenu,2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATING_VALUE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATING_VALUE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATING_VALUE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATING_VALUE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATING_VALUE_1);
+
+ switch(menuKind)
+ {
+ case OMMENU_SERVICECONTEXT:
+ case OMMENU_GALERYCONTEXT:
+ return Menu_GetServiceContext(baseMenu, flags, (OMMENU_GALERYCONTEXT == menuKind));
+ case OMMENU_RATING:
+ return Menu_CreateRatingMenu(baseMenu, RATINGFROMMCF(flags));
+ case OMMENU_TOOLBAR:
+ return Menu_GetToolbarContext(baseMenu, flags);
+ }
+ return NULL;
+}
+
+void Menu_ReleaseMenu(HMENU hMenu, INT menuKind)
+{
+ if (NULL == hMenu)
+ return;
+
+ switch(menuKind)
+ {
+ case OMMENU_SERVICECONTEXT:
+ DestroyMenu(hMenu);
+ break;
+ case OMMENU_GALERYCONTEXT:
+ DestroyMenu(hMenu);
+ break;
+ case OMMENU_RATING:
+ DestroyMenu(hMenu);
+ break;
+ case OMMENU_TOOLBAR:
+ break;
+ }
+}
+
+INT DoTrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ return Menu_TrackPopupParam(Plugin_GetLibrary(), hMenu, fuFlags, x, y,
+ hwnd, lptpm, (ULONG_PTR)Menu_FindRatingMenu(hMenu));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/local_menu.h b/Src/Plugins/Library/ml_online/local_menu.h
new file mode 100644
index 00000000..bf4257e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/local_menu.h
@@ -0,0 +1,40 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_MENU_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_MENU_HEADER
+
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+class OmService;
+
+#define OMMENU_SERVICECONTEXT 0
+#define OMMENU_GALERYCONTEXT 1
+#define OMMENU_RATING 2
+#define OMMENU_TOOLBAR 3
+
+#define MCF_VIEW 0x00000001
+#define MCF_NAVIGATION 0x00000004
+#define MCF_VIEWACTIVE 0x00000008
+
+#define MCF_RATINGMASK 0xF0000000
+#define MCF_RATING1 0x10000000
+#define MCF_RATING2 0x20000000
+#define MCF_RATING3 0x30000000
+#define MCF_RATING4 0x40000000
+#define MCF_RATING5 0x50000000
+
+#define RATINGTOMCF(__rating) ((0x0F & (__rating)) << 24)
+#define RATINGFROMMCF(__mcf) (0x0F & ((__mcf) >> 24))
+
+HMENU Menu_GetMenu(INT menuKind, UINT flags);
+void Menu_ReleaseMenu(HMENU hMenu, INT menuKind);
+
+HMENU Menu_FindRatingMenu(HMENU hMenu);
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue);
+INT DoTrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm);
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/messageBox.cpp b/Src/Plugins/Library/ml_online/messageBox.cpp
new file mode 100644
index 00000000..03a850e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/messageBox.cpp
@@ -0,0 +1,193 @@
+#include "main.h"
+#include "./api__ml_online.h"
+#include "./resource.h"
+
+#include <windows.h>
+#include <strsafe.h>
+
+typedef struct __MESSAGEBOX
+{
+ HWND hParent;
+ LPCWSTR pszText;
+ LPCWSTR pszCaption;
+ UINT uType;
+ LPCWSTR pszCheck;
+ INT *checked;
+} MESSAGEBOX;
+
+static INT_PTR CALLBACK OmMessageBox_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+INT OmMessageBox(HWND hParent, LPCWSTR pszText, LPCWSTR pszCaption, UINT uType, LPCWSTR pszCheck, INT *checked)
+{
+ MESSAGEBOX instance;
+ instance.hParent = hParent;
+ instance.pszText = pszText;
+ instance.pszCaption = pszCaption;
+ instance.uType = uType;
+ instance.pszCheck = pszCheck;
+ instance.checked = checked;
+
+ return (INT)WASABI_API_DIALOGBOXPARAMW(IDD_MESSAGEBOX, hParent, OmMessageBox_DialogProc, (LPARAM)&instance);
+}
+
+static void OmMessgageBox_CenterWindow(HWND hwnd, HWND hCenter)
+{
+ if (NULL == hwnd || NULL == hCenter)
+ return;
+
+ RECT centerRect, windowRect;
+ if (!GetWindowRect(hwnd, &windowRect) || !GetWindowRect(hCenter, &centerRect))
+ return;
+ windowRect.left = centerRect.left + ((centerRect.right - centerRect.left) - (windowRect.right - windowRect.left))/2;
+ windowRect.top = centerRect.top + ((centerRect.bottom - centerRect.top) - (windowRect.bottom - windowRect.top))/2;
+
+ SetWindowPos(hwnd, NULL, windowRect.left, windowRect.top, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER);
+}
+
+static BOOL OmMessageBox_GetTextBox(HWND hwnd, LPCWSTR pszText, INT cchText, SIZE *sizeText)
+{
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == hdc) return FALSE;
+
+ HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+ HFONT fontOrig = (HFONT)SelectObject(hdc, font);
+
+ if (cchText < 0)
+ cchText = lstrlen(pszText);
+
+ BOOL resultOk = GetTextExtentPoint32(hdc, pszText, cchText, sizeText);
+
+ SelectObject(hdc, fontOrig);
+ ReleaseDC(hwnd, hdc);
+
+ return resultOk;
+}
+static HICON OmMessageBox_GetIcon(UINT flags)
+{
+ LPCWSTR iconName = NULL;
+ switch(0x000000F0 & flags)
+ {
+ case MB_ICONHAND: iconName = IDI_HAND; break;
+ case MB_ICONQUESTION: iconName = IDI_QUESTION; break;
+ case MB_ICONEXCLAMATION:iconName = IDI_EXCLAMATION; break;
+ case MB_ICONASTERISK: iconName = IDI_ASTERISK; break;
+ }
+ return (NULL != iconName) ? LoadIcon(NULL, iconName) : NULL;
+}
+
+static BOOL OmMessageBox_GetIconSize(UINT flags, SIZE *iconSize)
+{
+ if (NULL == iconSize) return FALSE;
+
+ iconSize->cx = 0;
+ iconSize->cy = 0;
+
+ HICON hIcon = OmMessageBox_GetIcon(flags);
+ if (NULL == hIcon)
+ return TRUE;
+
+ ICONINFO iconInfo;
+
+ if (!GetIconInfo(hIcon, &iconInfo) || FALSE == iconInfo.fIcon)
+ return FALSE;
+
+ BITMAP bm;
+ if (NULL != iconInfo.hbmColor)
+ {
+ if (FALSE == GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bm))
+ return FALSE;
+
+ iconSize->cx = bm.bmWidth;
+ iconSize->cy = bm.bmHeight;
+ }
+ else if (NULL != iconInfo.hbmMask)
+ {
+ if (FALSE == GetObject(iconInfo.hbmMask, sizeof(BITMAP), &bm))
+ return FALSE;
+
+ iconSize->cx = bm.bmWidth;
+ iconSize->cy = bm.bmHeight/2;
+ }
+
+ return TRUE;
+}
+
+static BOOL OmMessageBox_GetButtonSize(HWND hwnd, SIZE *buttonSize)
+{
+ if (NULL == buttonSize)
+ return FALSE;
+
+ if (FALSE != SendMessage(hwnd, (0x1600 + 0x0001) /*BCM_GETIDEALSIZE*/, 0, (LPARAM)buttonSize))
+ return TRUE;
+
+ return FALSE;
+}
+static INT_PTR OmMessageBox_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
+{
+ MESSAGEBOX *pmb = (MESSAGEBOX*)lParam;
+ SetWindowText(hwnd, pmb->pszCaption);
+
+ SIZE textSize;
+ SIZE maxSize;
+ MONITORINFO mi;
+ mi.cbSize = sizeof(MONITORINFO);
+
+ HMONITOR hMonitor = MonitorFromWindow(pmb->hParent, MONITOR_DEFAULTTONEAREST);
+ if (NULL != hMonitor &&
+ GetMonitorInfo(hMonitor, &mi))
+ {
+ RECT rcFrame;
+ SetRectEmpty(&rcFrame);
+ AdjustWindowRectEx(&rcFrame, GetWindowStyle(hwnd), FALSE, GetWindowStyleEx(hwnd));
+ maxSize.cx = mi.rcWork.right - mi.rcWork.left - (rcFrame.right - rcFrame.left) - 8*2;
+ maxSize.cy = mi.rcWork.bottom - mi.rcWork.top - (rcFrame.bottom - rcFrame.top) - 8*2;
+ }
+ else
+ {
+ maxSize.cx = 1200;
+ maxSize.cy = 800;
+ }
+
+ HWND hText = GetDlgItem(hwnd, IDC_TEXT);
+ if (NULL != hText)
+ {
+ if (!OmMessageBox_GetTextBox(hText, pmb->pszText, -1, &textSize))
+ ZeroMemory(&textSize, sizeof(SIZE));
+
+ SetWindowPos(hText, NULL, 0, 0, textSize.cx, textSize.cy, SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+ else
+ ZeroMemory(&textSize, sizeof(SIZE));
+
+
+
+ OmMessgageBox_CenterWindow(hwnd, pmb->hParent);
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+ return FALSE;
+}
+
+static void OmMessageBox_OnDestroy(HWND hwnd)
+{
+
+}
+
+static void OmMessageBox_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND hControl)
+{
+ switch(commandId)
+ {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, commandId);
+ break;
+ }
+}
+static INT_PTR CALLBACK OmMessageBox_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return OmMessageBox_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: OmMessageBox_OnDestroy(hwnd); break;
+ case WM_COMMAND: OmMessageBox_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ }
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_online/messageBox.h b/Src/Plugins/Library/ml_online/messageBox.h
new file mode 100644
index 00000000..4becf7f8
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/messageBox.h
@@ -0,0 +1,13 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_MESSAGEBOX_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_MESSAGEBOX_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+
+INT OmMessageBox(HWND hParent, LPCWSTR pszText, LPCWSTR pszCaption, UINT uType, LPCWSTR pszCheck, INT *checked);
+
+#endif //NULLOSFT_ONLINEMEDIA_PLUGIN_MESSAGEBOX_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/ml_online.rc b/Src/Plugins/Library/ml_online/ml_online.rc
new file mode 100644
index 00000000..133a9988
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/ml_online.rc
@@ -0,0 +1,453 @@
+// 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_MAINDIALOG DIALOGEX 0, 0, 225, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Back",IDC_BACK,"Button",BS_OWNERDRAW | WS_TABSTOP,0,81,32,11
+ CONTROL "Forward",IDC_FORWARD,"Button",BS_OWNERDRAW | WS_TABSTOP,36,81,40,11
+ CONTROL "Home",IDC_HOME,"Button",BS_OWNERDRAW | WS_TABSTOP,80,81,32,11
+ CONTROL "Stop",IDC_STOP,"Button",BS_OWNERDRAW | WS_TABSTOP,116,81,24,11
+ CONTROL "Refresh",IDC_REFRESH,"Button",BS_OWNERDRAW | WS_TABSTOP,144,81,32,11
+END
+
+IDD_OMPREF DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "'Now Playing' Service",IDC_STATIC,0,0,272,72
+ LTEXT "This allows you to specify an alternative Now Playing service to use in the 'Now Playing' node (if installed). Leave blank to reset to the default service.",IDC_STATIC,6,11,260,18
+ EDITTEXT IDC_NOWPLAYINGURL,6,33,260,13,ES_AUTOHSCROLL
+ LTEXT "Note: For now playing lookups to work when using an alternative Now Playing service, the service will need to use the available Winamp Javascript API.",IDC_STATIC,6,51,260,18,WS_DISABLED
+ GROUPBOX "Online Media",IDC_STATIC,0,76,272,146
+ GROUPBOX "General Settings",IDC_STATIC,5,87,260,30
+ CONTROL "Allow sections to auto-resize",IDC_AUTOSIZE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,100,107,10
+ GROUPBOX "Cache Preferences",IDC_STATIC,5,120,260,40
+ LTEXT "Keep pages cached for:",IDC_STATIC,11,132,78,8
+ CONTROL "Never",IDC_RADIO_NEVER,"Button",BS_AUTORADIOBUTTON | WS_GROUP,20,144,35,10
+ CONTROL "One Hour",IDC_RADIO_HOURLY,"Button",BS_AUTORADIOBUTTON,62,144,47,10
+ CONTROL "One Day",IDC_RADIO_DAILY,"Button",BS_AUTORADIOBUTTON,116,144,44,10
+ CONTROL "One Week",IDC_RADIO_WEEKLY,"Button",BS_AUTORADIOBUTTON,167,144,51,10
+ GROUPBOX "Bandwidth Control",IDC_STATIC,5,164,260,52
+ LTEXT "Maximum bandwidth for media in Kbytes",IDC_STATIC,11,175,131,10,SS_CENTERIMAGE
+ EDITTEXT IDC_RADIO_MAXBW,154,174,30,12,ES_AUTOHSCROLL
+ LTEXT "Minimum bandwidth for media in Kbytes\n(not used by some sections)",IDC_STATIC,11,190,129,16
+ EDITTEXT IDC_RADIO_MINBW,154,190,31,12,ES_AUTOHSCROLL
+END
+
+IDD_MESSAGEBOX DIALOGEX 0, 0, 186, 90
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "",IDC_TEXT,7,7,172,35
+END
+
+IDD_SETUPPAGE DIALOGEX 0, 0, 256, 158
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Choose Winamp's web services to experience premium media services online.",IDC_LABEL_HEADER,5,6,251,8
+ LISTBOX IDC_SERVICELIST,5,24,99,126,LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | LBS_NODATA | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_PLACEHOLDER,"Static",SS_BLACKRECT | NOT WS_VISIBLE,107,24,148,126
+END
+
+IDD_SETUP_SERVICEDETAILS DIALOGEX 0, 0, 148, 126
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_THUMBNAIL,"Static",SS_BITMAP,0,0,45,45
+ LTEXT "",IDC_TITLE,46,0,95,10
+ EDITTEXT 1316,1,46,140,80,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_BORDER | NOT WS_TABSTOP
+ LTEXT "line1\r\nline2",IDC_SERVICEMETA,46,10,102,26,NOT WS_VISIBLE
+END
+
+IDD_SETUP_GROUPDETAILS DIALOGEX 0, 0, 148, 126
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Group Title",IDC_TITLE,4,0,144,13
+ EDITTEXT IDC_DESCRIPTION,2,15,145,97,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_VISIBLE | NOT WS_BORDER | NOT WS_TABSTOP
+ LTEXT "to read about Winamp policies.",IDC_HELPTEXT,0,117,147,8
+END
+
+IDD_OPENURL DIALOGEX 0, 0, 220, 54
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Open Url"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Enter service list address:",IDC_STATIC,5,4,210,10
+ COMBOBOX IDC_ADDRESS,5,18,210,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ DEFPUSHBUTTON "OK",IDOK,111,36,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,165,36,50,13
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENU MENU
+BEGIN
+ POPUP "ServiceContextMenu"
+ BEGIN
+ MENUITEM SEPARATOR
+ POPUP "&Service Management"
+ BEGIN
+ MENUITEM "&New", ID_SERVICE_NEW
+ POPUP "&Import"
+ BEGIN
+ MENUITEM "&File...", ID_SERVICE_IMPORT_FILE
+ MENUITEM "&Url..", ID_SERVICE_IMPORT_URL
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Edit", ID_SERVICE_EDIT
+ MENUITEM SEPARATOR
+ MENUITEM "Open Containing &Folder", ID_SERVICE_LOCATE
+ MENUITEM "Open in E&xternal Editor", ID_SERVICE_EDITEXTERNAL
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Re&move", ID_SERVICE_UNSUBSCRIBE
+ MENUITEM "Reset Permi&ssions", ID_SERVICE_RESETPOLICY
+ END
+ POPUP "GaleryContextMenu"
+ BEGIN
+ POPUP "&Service Management"
+ BEGIN
+ MENUITEM "&New", ID_SERVICE_NEW
+ MENUITEM SEPARATOR
+ MENUITEM "Import &File...", ID_SERVICE_IMPORT_FILE
+ MENUITEM "Import &Url..", ID_SERVICE_IMPORT_URL
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Preferences...", ID_PLUGIN_PREFERENCES
+ MENUITEM "&omBrowser Options...", ID_OMBROWSER_OPTIONS
+ MENUITEM SEPARATOR
+ MENUITEM "&Help", ID_PLUGIN_HELP
+ END
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATING_VALUE_5
+ MENUITEM "****", ID_RATING_VALUE_4
+ MENUITEM "***", ID_RATING_VALUE_3
+ MENUITEM "**", ID_RATING_VALUE_2
+ MENUITEM "*", ID_RATING_VALUE_1
+ END
+ POPUP "View"
+ BEGIN
+ MENUITEM "&Open", ID_VIEW_OPEN
+ MENUITEM "Open in &New Window", ID_VIEW_OPENPOPUP
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "Back", ID_NAVIGATION_BACK
+ MENUITEM "Forward", ID_NAVIGATION_FORWARD
+ MENUITEM "Refresh", ID_NAVIGATION_REFRESH
+ MENUITEM "Stop", ID_NAVIGATION_STOP
+ END
+ POPUP "Toolbar"
+ BEGIN
+ MENUITEM "&Top Dock", ID_TOOLBAR_DOCKTOP
+ MENUITEM "&Bottom Dock", ID_TOOLBAR_DOCKBOTTOM
+ MENUITEM SEPARATOR
+ MENUITEM "Auto-&Hide", ID_TOOLBAR_AUTOHIDE
+ MENUITEM "Enable &Tab Stop", ID_TOOLBAR_TABSTOP
+ END
+END
+
+IDR_SETUPMENU MENU
+BEGIN
+ POPUP "GroupContext"
+ BEGIN
+ MENUITEM "Expand", ID_GROUP_TOGGLE
+ MENUITEM SEPARATOR
+ MENUITEM "&Select All", ID_GROUP_SELECTALL
+ MENUITEM "&Unselect All", ID_GROUP_UNSELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "&Reload", ID_GROUP_RELOAD
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_MESSAGEBOX, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 83
+ END
+
+ IDD_SETUPPAGE, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ VERTGUIDE, 104
+ TOPMARGIN, 6
+ BOTTOMMARGIN, 150
+ HORZGUIDE, 24
+ END
+
+ IDD_SETUP_SERVICEDETAILS, DIALOG
+ BEGIN
+ RIGHTMARGIN, 142
+ VERTGUIDE, 46
+ HORZGUIDE, 10
+ HORZGUIDE, 46
+ END
+
+ IDD_OPENURL, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 215
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 49
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_BROWSERACCEL ACCELERATORS
+BEGIN
+ VK_F5, ID_NAVIGATION_REFRESH, VIRTKEY, NOINVERT
+ "R", ID_NAVIGATION_REFRESH, VIRTKEY, CONTROL, NOINVERT
+ VK_F11, ID_WINDOW_FULLSCREEN, VIRTKEY, NOINVERT
+ VK_F4, ID_WINDOW_CLOSE, VIRTKEY, CONTROL, NOINVERT
+ VK_F5, ID_NAVIGATION_REFRESH_COMPLETELY, VIRTKEY, CONTROL, NOINVERT
+ "R", ID_NAVIGATION_REFRESH_COMPLETELY, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// HTML
+//
+
+IDR_HTML_EDITOR HTML "resources\\pages\\serviceEditor.htm"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_ONLINE_SERVICES "Online Services"
+ IDS_NO_INTERNET_CONNECTION
+ "The media library feature you are attempting to use requires an internet connection. Please make sure you are connected to the internet and try again."
+ IDS_SORRY "Sorry"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Online Services v%d.%02d"
+ 65535 "{D006C700-557E-43c7-A580-B4C50C56957A}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEFAULT_SERVICENAME "Unknown name"
+ IDS_MORE "More..."
+ IDS_HOME "Home"
+ IDS_HOME_DESCRIPTION "Navigate to the main service page"
+ IDS_BACK "Back"
+ IDS_BACK_DESCRIPTION "Go back one page"
+ IDS_FORWARD "Forward"
+ IDS_FORWARD_DESCRIPTION "Go forward one page"
+ IDS_REFRESH "Refresh"
+ IDS_REFRESH_DESCRIPTION "Reload current page"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_STOP "Stop"
+ IDS_STOP_DESCRIPTION "Stop loading this page"
+ IDS_SEPARATOR "Separator"
+ IDS_SPACE "Space"
+ IDS_FLEXSPACE "Flexible Space"
+ IDS_MORE_DESCRIPTION "Show more items"
+ IDS_RATED "Rating:"
+ IDS_PLEASE_WAIT "Please wait..."
+ IDS_SERVICE_CHECKINGVERSION "Checking service version"
+ IDS_SERVICE_GETINFO "Get Info..."
+ IDS_SERVICE_GETINFO_DESCRIPTION "Get service info"
+ IDS_SERVICE_REPORT "Report..."
+ IDS_SERVICE_REPORT_DESCRIPTION "Report service"
+ IDS_SERVICE_UNSUBSCRIBE "Remove"
+ IDS_SERVICE_UNSUBSCRIBE_DESCRIPTION "Remove service"
+ IDS_RATING_0 "not rated"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RATING_1 "1 star"
+ IDS_RATING_2 "2 stars"
+ IDS_RATING_3 "3 stars"
+ IDS_RATING_4 "4 stars"
+ IDS_RATING_5 "5 stars"
+ IDS_RATING_CHANGETO "Set to:"
+ IDS_RATING_CURRENT "Rated:"
+ IDS_SETUPPAGE_TITLE "Choose Online Services"
+ IDS_SETUP_EMPTYGROUP "( empty )"
+ IDS_SETUP_LOADINGGROUP "Loading..."
+ IDS_SERVICEGROUP_FEATURED "Featured"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_SERVICEGROUP_KNOWN "Already Subscribed"
+ IDS_SETUP_GROUPLOADFAILED "Error loading data"
+ IDS_SERVICE_BYAUTHOR " by: "
+ IDS_SERVICE_LASTUPDATED " updated: "
+ IDS_SERVICEGROUP_FEATUREDLONG "Featured Services"
+ IDS_SERVICEGROUP_KNOWNLONG "Already Subscribed Services"
+ IDS_SERVICEGROUP_FEATURED_DESC
+ "This group contains some great services that we handpicked especially for you - including Winamp Charts & Add-ons. Also, if at any time you would like to try some other services, feel free to discover them by selecting the Online Services page in the Media Library."
+ IDS_SERVICEGROUP_KNOWN_DESC
+ "Services that you have already subscribed to."
+ IDS_MESSAGEBOX_UNSUBSCRIBE
+ "Are you sure you want to remove %s from your Online Services?"
+ IDS_MESSAGEBOX_UNSUBSCRIBE_CAPTION "Confirm Service Remove"
+ IDS_MESSAGEBOX_RESETTODEFAULT
+ "Are you sure you want to remove your current Online Services and restore the default Online Services?"
+ IDS_MESSAGEBOX_RESETTODEFAULT_CAPTION
+ "Confirm Restore to Default Services"
+ IDS_DOWNLOADSERVICE_JOB "Loading featured services"
+ IDS_SAVESERVICE_JOB "Saving services"
+ IDS_CACHEICONS_JOB "Caching service icons"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_REGISTERINGSERVICE_JOB "Registering services"
+ IDS_MESSAGEBOX_NAMECHANGED_CAPTION "Service Name Changed"
+ IDS_MESSAGEBOX_NAMECHANGED "The service %s has been renamed to %s."
+ IDS_MESSAGEBOX_POLICYRESET_CAPTION "Confirm Permissions Reset"
+ IDS_MESSAGEBOX_POLICYRESET
+ "Are you sure you want to reset permissions for %s service?"
+ IDS_COLLAPSE "Collapse"
+ IDS_EXPAND "Expand"
+ IDS_SECURE_CONNECTION "Show Page Identification"
+ IDS_SCRIPT_ERROR "Show Script Errors"
+ IDS_SCRIPT_ERROR_DESCRIPTION "Page executed with some errors"
+ IDS_ENCRYPTION_MIXED "Mixed security"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ENCRYPTION_40BIT "40-bit security"
+ IDS_ENCRYPTION_56BIT "56-bit security"
+ IDS_ENCRYPTION_FORTEZZA "Fortezza security"
+ IDS_ENCRYPTION_128BIT "128-bit security"
+ IDS_CONNECTION_UNSECURE "Connection Not Encrypted"
+ IDS_CONNECTION_ENCRYPTED "Connection Encrypted"
+ IDS_NAVIGATING "Navigating..."
+ IDS_MESSAGEBOX_FORCEREMOVE
+ "Service %s was cancelled and will be removed from your Online Services."
+ IDS_MESSAGEBOX_FORCEREMOVE_CAPTION "Service Cancelled"
+ IDS_HISTORY "Recent pages"
+ IDS_HISTORY_DESCRIPTION "Recent pages"
+ IDS_CURRENT_PAGE "Current Page"
+ IDS_CLICKHERE "Click here"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_MESSAGEBOX_DISCOVERBUSY
+ "Service discovery in progress. Please wait till completion and try again."
+ IDS_MESSAGEBOX_DISCOVERBUSY_CAPTION "Operation In Progress"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_USERSERVICE_NAME "My Service"
+ IDS_FILEFILTER_ALL "All Files"
+ IDS_FILEFILTER_ALLKNOWN "All Supported Files"
+ IDS_IMPORT_FILES "Import Service..."
+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_online/ml_online.sln b/Src/Plugins/Library/ml_online/ml_online.sln
new file mode 100644
index 00000000..733c80a3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/ml_online.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_online", "ml_online.vcxproj", "{E40EADDB-F488-43CE-A451-B0D805C09A94}"
+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
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Debug|Win32.Build.0 = Debug|Win32
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Debug|x64.ActiveCfg = Debug|x64
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Debug|x64.Build.0 = Debug|x64
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Release|Win32.ActiveCfg = Release|Win32
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Release|Win32.Build.0 = Release|Win32
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Release|x64.ActiveCfg = Release|x64
+ {E40EADDB-F488-43CE-A451-B0D805C09A94}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {87AC7BBF-61AD-484A-A3A1-FA39CE18F1E7}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_online/ml_online.vcxproj b/Src/Plugins/Library/ml_online/ml_online.vcxproj
new file mode 100644
index 00000000..8475fcd7
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/ml_online.vcxproj
@@ -0,0 +1,395 @@
+<?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>{E40EADDB-F488-43CE-A451-B0D805C09A94}</ProjectGuid>
+ <RootNamespace>ml_online</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)'=='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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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)'=='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|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;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_ONLINE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4091;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <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;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_ONLINE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4091;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_ONLINE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4091;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.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>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_ONLINE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4091;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;comctl32.lib;rpcrt4.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>
+ </Link>
+ <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>
+ <ResourceCompile Include="ml_online.rc" />
+ <ResourceCompile Include="png.rc" />
+ <ResourceCompile Include="testPages.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="resources\pages\serviceEditor.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\ConfigCOM.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menuHelpers.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\nu\trace.cpp" />
+ <ClCompile Include="..\..\..\nu\windowsTheme.cpp" />
+ <ClCompile Include="..\..\..\Winamp\JSAPI_CallbackParameters.cpp" />
+ <ClCompile Include="browserEvent.cpp" />
+ <ClCompile Include="commands.cpp" />
+ <ClCompile Include="common.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="external.cpp" />
+ <ClCompile Include="forceUrl.cpp" />
+ <ClCompile Include="handler.cpp" />
+ <ClCompile Include="importFile.cpp" />
+ <ClCompile Include="importUrl.cpp" />
+ <ClCompile Include="JnetCOM.cpp" />
+ <ClCompile Include="JSAPI2_Creator.cpp" />
+ <ClCompile Include="jsapi2_omcom.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="messageBox.cpp" />
+ <ClCompile Include="navigation.cpp" />
+ <ClCompile Include="OMCOM.cpp" />
+ <ClCompile Include="Preferences.cpp" />
+ <ClCompile Include="serviceHelper.cpp" />
+ <ClCompile Include="serviceHost.cpp" />
+ <ClCompile Include="Setup\setup.cpp" />
+ <ClCompile Include="Setup\setupDetails.cpp" />
+ <ClCompile Include="Setup\setupDetailsGroup.cpp" />
+ <ClCompile Include="Setup\setupGroup.cpp" />
+ <ClCompile Include="Setup\setupGroupFilter.cpp" />
+ <ClCompile Include="Setup\setupGroupList.cpp" />
+ <ClCompile Include="Setup\setupImage.cpp" />
+ <ClCompile Include="Setup\setupListbox.cpp" />
+ <ClCompile Include="Setup\setupListboxLabel.cpp" />
+ <ClCompile Include="Setup\setupLog.cpp" />
+ <ClCompile Include="Setup\setupPage.cpp" />
+ <ClCompile Include="Setup\setupPageWnd.cpp" />
+ <ClCompile Include="Setup\setupRecord.cpp" />
+ <ClCompile Include="Setup\setupServicePanel.cpp" />
+ <ClCompile Include="wasabi.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\jnetlib\asyncdns.h" />
+ <ClInclude Include="..\..\..\jnetlib\connection.h" />
+ <ClInclude Include="..\..\..\jnetlib\httpget.h" />
+ <ClInclude Include="..\..\..\jnetlib\listen.h" />
+ <ClInclude Include="..\..\..\jnetlib\netinc.h" />
+ <ClInclude Include="..\..\..\jnetlib\util.h" />
+ <ClInclude Include="..\..\..\nu\ConfigCOM.h" />
+ <ClInclude Include="browserEvent.h" />
+ <ClInclude Include="BufferCache.h" />
+ <ClInclude Include="commands.h" />
+ <ClInclude Include="common.h" />
+ <ClInclude Include="config.h" />
+ <ClInclude Include="external.h" />
+ <ClInclude Include="forceUrl.h" />
+ <ClInclude Include="handler.h" />
+ <ClInclude Include="import.h" />
+ <ClInclude Include="JnetCOM.h" />
+ <ClInclude Include="JSAPI2_Creator.h" />
+ <ClInclude Include="jsapi2_omcom.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="messageBox.h" />
+ <ClInclude Include="navigation.h" />
+ <ClInclude Include="OMCOM.h" />
+ <ClInclude Include="Preferences.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="serviceHelper.h" />
+ <ClInclude Include="serviceHost.h" />
+ <ClInclude Include="Setup\setupDetails.h" />
+ <ClInclude Include="Setup\setupGroup.h" />
+ <ClInclude Include="Setup\SetupGroupFilter.h" />
+ <ClInclude Include="Setup\setupGroupList.h" />
+ <ClInclude Include="Setup\setupImage.h" />
+ <ClInclude Include="Setup\setupListbox.h" />
+ <ClInclude Include="Setup\setupListboxLabel.h" />
+ <ClInclude Include="Setup\setupLog.h" />
+ <ClInclude Include="Setup\setupPage.h" />
+ <ClInclude Include="Setup\setupRecord.h" />
+ <ClInclude Include="Setup\setupServicePanel.h" />
+ <ClInclude Include="api__ml_online.h" />
+ </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_online/ml_online.vcxproj.filters b/Src/Plugins/Library/ml_online/ml_online.vcxproj.filters
new file mode 100644
index 00000000..6963e519
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/ml_online.vcxproj.filters
@@ -0,0 +1,301 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="browserEvent.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="commands.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="common.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="external.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="forceUrl.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="handler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="importFile.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="importUrl.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JnetCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="jsapi2_omcom.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Creator.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="messageBox.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="navigation.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="OMCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Preferences.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="serviceHelper.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="serviceHost.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setup.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupDetails.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupDetailsGroup.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupGroup.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupGroupFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupGroupList.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wasabi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupServicePanel.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupRecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupPageWnd.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupPage.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupLog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupListboxLabel.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupListbox.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Setup\setupImage.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ConfigCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\JSAPI_CallbackParameters.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menuHelpers.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\trace.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\windowsTheme.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="browserEvent.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="BufferCache.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="commands.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="external.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="forceUrl.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="handler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="import.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JnetCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Creator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="jsapi2_omcom.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="messageBox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="navigation.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="OMCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Preferences.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="serviceHelper.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_online.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupServicePanel.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupRecord.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupPage.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupLog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupListboxLabel.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupListbox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupImage.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupGroupList.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\SetupGroupFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupGroup.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Setup\setupDetails.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="serviceHost.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\jnetlib\listen.h">
+ <Filter>Header Files\jnetlibws</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\jnetlib\asyncdns.h">
+ <Filter>Header Files\jnetlibws</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\jnetlib\connection.h">
+ <Filter>Header Files\jnetlibws</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\jnetlib\httpget.h">
+ <Filter>Header Files\jnetlibws</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ConfigCOM.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\jnetlib\netinc.h">
+ <Filter>Header Files\jnetlibws</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\jnetlib\util.h">
+ <Filter>Header Files\jnetlibws</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_online.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="png.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="testPages.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{e3d16f42-4b26-4321-936a-0243e6c4f69c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{cd0589d8-5b12-49c6-9677-6d79580e8c60}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{92b2e12e-b041-4908-b10a-e7156ed3fc62}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="HTM Files">
+ <UniqueIdentifier>{4b199e78-6958-4855-8ac9-9d3e16a24d84}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\jnetlibws">
+ <UniqueIdentifier>{888befc6-d04c-4686-907e-b74a8a1742f8}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{981def8f-a0e5-4e28-a401-8dcf96712819}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{b7e447b3-b0b4-48d9-9da1-a65fb3f61161}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="resources\pages\serviceEditor.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/navigation.cpp b/Src/Plugins/Library/ml_online/navigation.cpp
new file mode 100644
index 00000000..72019329
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/navigation.cpp
@@ -0,0 +1,1472 @@
+#include "main.h"
+#include "./navigation.h"
+#include "./resource.h"
+#include "./api__ml_online.h"
+#include "./local_menu.h"
+#include "./commands.h"
+#include "./config.h"
+
+#include "../omBrowser/browserView.h"
+#include "../winamp/wa_ipc.h"
+
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+#include "./browserEvent.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+#include <storageIni.h>
+#include <ifc_omserviceenum.h>
+#include <ifc_mlnavigationhelper.h>
+#include <ifc_omserviceeventmngr.h>
+#include <ifc_ombrowserwndmngr.h>
+#include <ifc_ombrowserwndenum.h>
+#include <ifc_ombrowsereventmngr.h>
+
+#include "../../General/gen_ml/menu.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+
+#include <vector>
+#include "../nu/sort.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <algorithm>
+
+#define NAVITEM_PREFIX L"om_svc_"
+#define E_NAVITEM_UNKNOWN E_NOINTERFACE
+
+typedef struct __NAVASYNCPARAM
+{
+ Navigation *instance;
+ NavigationCallback callback;
+ ULONG_PTR param;
+}NAVASYNCPARAM;
+
+
+
+static BOOL Navigation_CheckInvariantName(LPCWSTR pszInvarian)
+{
+ INT cchInvariant = (NULL != pszInvarian) ? lstrlen(pszInvarian) : 0;
+ INT cchPrefix = ARRAYSIZE(NAVITEM_PREFIX) - 1;
+ return (cchInvariant > cchPrefix &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, 0, NAVITEM_PREFIX, cchPrefix, pszInvarian, cchPrefix));
+}
+
+
+Navigation::Navigation()
+ : ref(1), cookie(0), hRoot(NULL), hLibrary(NULL)
+{
+}
+
+Navigation::~Navigation()
+{
+}
+
+HRESULT Navigation::CreateInstance(Navigation **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ HRESULT hr;
+
+ Navigation *navigation = new Navigation();
+ if (NULL != navigation)
+ {
+ hr = navigation->Initialize();
+ if (FAILED(hr))
+ {
+ navigation->Release();
+ navigation = NULL;
+ }
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ *instance = navigation;
+ return hr;
+}
+
+size_t Navigation::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t Navigation::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int Navigation::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_MlNavigationCallback))
+ *object = static_cast<ifc_mlnavigationcallback*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT Navigation::Initialize()
+{
+ hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ if (0 == cookie)
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->RegisterCallback(this, &cookie);
+ navHelper->Release();
+ }
+ }
+
+ ifc_omservice *service;
+
+ MLNavCtrl_BeginUpdate(hLibrary, NUF_LOCK_TOP);
+
+ // TODO make thie configurable?
+ if (SUCCEEDED(ServiceHelper_Create(ROOTSERVICE_ID, MAKEINTRESOURCE(IDS_ONLINE_SERVICES),
+ NULL, L"http://client.winamp.com/services", SVCF_SPECIAL | SVCF_SUBSCRIBED, 1, FALSE, &service)))
+ {
+ hRoot = CreateItemInt(NULL, service);
+ service->Release();
+ }
+
+ if (NULL == hRoot)
+ {
+ MLNavCtrl_EndUpdate(hLibrary);
+ return E_FAIL;
+ }
+
+
+ ifc_omserviceenum *enumerator;
+ if (SUCCEEDED(ServiceHelper_Load(&enumerator)))
+ {
+ ifc_omservice *service;
+ std::vector<ifc_omservice*> serviceList;
+ while (S_OK == enumerator->Next(1, &service, NULL))
+ {
+ if (S_OK == ServiceHelper_IsSubscribed(service))
+ {
+ serviceList.push_back(service);
+ }
+ else
+ service->Release();
+ }
+ enumerator->Release();
+
+ size_t count = serviceList.size();
+ Order(serviceList);
+ for(size_t i =0; i < count; i++)
+ {
+ service = serviceList[i];
+ CreateItemInt(hRoot, service);
+ service->Release();
+ }
+ }
+
+ MLNavCtrl_EndUpdate(hLibrary);
+
+ return S_OK;
+}
+
+HRESULT Navigation::Finish()
+{
+ if (0 != cookie)
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->UnregisterCallback(cookie);
+ navHelper->Release();
+ }
+ }
+
+ if (NULL != OMBROWSERMNGR)
+ {
+ OMBROWSERMNGR->Finish();
+ }
+
+ return S_OK;
+}
+
+HRESULT Navigation::SaveOrder()
+{
+ if (NULL == hRoot || NULL == hLibrary)
+ return E_UNEXPECTED;
+
+ LPSTR buffer = NULL;
+ INT count = MLNavItem_GetChildrenCount(hLibrary, hRoot);
+ if (count > 0)
+ {
+ size_t bufferMax = 11 * count;
+ buffer = Plugin_MallocAnsiString(bufferMax);
+ if (NULL == buffer) return E_OUTOFMEMORY;
+ *buffer = '\0';
+
+ LPSTR cursor = buffer;
+ size_t remaining = bufferMax;
+
+ NAVITEM item;
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_PARAM;
+ item.hItem = MLNavItem_GetChild(hLibrary, hRoot);
+ while (NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service)
+ {
+ UINT serviceFlags;
+ if (SUCCEEDED(service->GetFlags(&serviceFlags)) &&
+ 0 == (SVCF_SPECIAL & serviceFlags))
+ {
+ if (cursor == buffer ||
+ SUCCEEDED(StringCchCopyExA(cursor, remaining, ";", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE)))
+ {
+ StringCchPrintfExA(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, "%d", service->GetId());
+ }
+ }
+ }
+ }
+ item.hItem = MLNavItem_GetNext(hLibrary, item.hItem);
+ }
+
+ }
+
+ Config_WriteStr("Navigation", "order", buffer);
+ Plugin_FreeAnsiString(buffer);
+
+ return S_OK;
+}
+
+//static int __fastcall Navigation_OrderComparer(const void *elem1, const void *elem2, const void *context)
+//{
+// std::vector<UINT> *orderList = (std::vector<UINT>*)context;
+//
+// UINT serviceId;
+// size_t index1, index2;
+// size_t count = orderList->size();
+//
+// serviceId = (*(ifc_omservice**)elem1)->GetId();
+// for (index1 = 0; index1 < count && serviceId != orderList->at(index1); index1++);
+//
+// serviceId = (*(ifc_omservice**)elem2)->GetId();
+// for (index2 = 0; index2 < count && serviceId != orderList->at(index2); index2++);
+//
+// return (INT)(index1 - index2);
+//}
+class Navigation_OrderComparer
+{
+public:
+ Navigation_OrderComparer(const void* ctx)
+ : context(ctx)
+ {
+ }
+
+ bool operator()(const void* elem1, const void* elem2)
+ {
+ std::vector<UINT>* orderList = (std::vector<UINT>*)context;
+
+ UINT serviceId;
+ size_t index1, index2;
+ size_t count = orderList->size();
+
+ serviceId = ((ifc_omservice*)elem1)->GetId();
+ for (index1 = 0; index1 < count && serviceId != orderList->at(index1); index1++);
+
+ serviceId = ((ifc_omservice*)elem2)->GetId();
+ for (index2 = 0; index2 < count && serviceId != orderList->at(index2); index2++);
+
+ return (INT)(index1 - index2) < 0;
+ }
+
+
+private:
+ const void* context;
+};
+
+HRESULT Navigation::Order(std::vector<ifc_omservice*> &list)
+{
+ size_t listSize = list.size();
+
+ if (listSize < 2)
+ return S_FALSE;
+
+ //if (NULL == list) return E_INVALIDARG;
+
+ size_t bufferMax = 16384;
+ LPSTR buffer = Plugin_MallocAnsiString(bufferMax);
+ if (NULL == buffer) return E_OUTOFMEMORY;
+
+ UINT len = Config_ReadStr("Navigation", "order", NULL, buffer, (UINT)bufferMax);
+ std::vector<UINT> orderList;
+
+ LPCSTR end = buffer + len;
+ LPCSTR block = buffer;
+ LPCSTR cursor = block;
+ for(;;)
+ {
+ if (cursor == end || ';' == *cursor)
+ {
+ if (block != cursor)
+ {
+ INT serviceId;
+ if (FALSE != StrToIntExA(block, STIF_DEFAULT, &serviceId))
+ orderList.push_back(serviceId);
+ }
+
+ if (cursor == end) break;
+ cursor++;
+ block = cursor;
+ }
+ cursor++;
+ }
+
+
+ if (0 != orderList.size())
+ {
+ //nu::qsort(list, listSize, sizeof(ifc_omservice*), &orderList, Navigation_OrderComparer);
+ std::sort(list.begin(), list.end(), Navigation_OrderComparer(&orderList));
+ }
+
+ Plugin_FreeAnsiString(buffer);
+ return S_OK;
+}
+
+typedef struct __IMAGEAPCPARAM
+{
+ LPWSTR name;
+ INT index;
+} IMAGEAPCPARAM;
+
+static void CALLBACK Navigtaion_ImageChangedApc(Navigation *instance, ULONG_PTR param)
+{
+ IMAGEAPCPARAM *image = (IMAGEAPCPARAM*)param;
+ if (NULL == image) return;
+
+ instance->ImageChanged(image->name, image->index);
+
+ Plugin_FreeString(image->name);
+ free(image);
+}
+
+
+void Navigation::ImageChanged(LPCWSTR pszName, INT index)
+{
+ if (NULL == hRoot || NULL == hLibrary || NULL == pszName)
+ return;
+
+
+ DWORD libraryTID = GetWindowThreadProcessId(hLibrary, NULL);
+ DWORD currentTID = GetCurrentThreadId();
+ if (libraryTID != currentTID)
+ {
+ if (NULL != OMUTILITY)
+ {
+ IMAGEAPCPARAM *param = (IMAGEAPCPARAM*)calloc(1, sizeof(IMAGEAPCPARAM));
+ if (NULL != param)
+ {
+ param->name = Plugin_CopyString(pszName);
+ param->index = index;
+ if (FAILED(PostMainThreadCallback(Navigtaion_ImageChangedApc, (ULONG_PTR)param)))
+ {
+ free(param);
+ }
+ }
+ }
+ return;
+ }
+
+ WCHAR szBuffer[2048] = {0};
+ NAVITEM item = {0};
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
+ item.pszInvariant = szBuffer;
+ item.cchInvariantMax = ARRAYSIZE(szBuffer);
+
+ item.hItem = hRoot;
+
+ while (NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item) &&
+ FALSE != Navigation_CheckInvariantName(item.pszInvariant))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service &&
+ SUCCEEDED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer))) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, szBuffer, -1, pszName, -1))
+ {
+
+ item.iImage = index;
+ item.iSelectedImage = index;
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(hLibrary, &item);
+ return;
+ }
+ }
+
+ item.hItem = (HNAVITEM)SENDMLIPC(hLibrary,
+ (item.hItem == hRoot) ? ML_IPC_NAVITEM_GETCHILD : ML_IPC_NAVITEM_GETNEXT,
+ (WPARAM)item.hItem);
+
+ }
+}
+
+
+
+BOOL Navigation::ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result)
+{
+ if (msg < ML_MSG_TREE_BEGIN || msg > ML_MSG_TREE_END)
+ return FALSE;
+
+ HRESULT hr;
+
+ switch(msg)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ {
+ HWND hView;
+ hr = OnCreateView(GetMessageItem(msg, param1), (HWND)param2, &hView);
+ *result = (SUCCEEDED(hr)) ? (INT_PTR)hView : NULL;
+ }
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ hr = OnContextMenu(GetMessageItem(msg, param1), (HWND)param2, MAKEPOINTS(param3));
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONDELETE:
+ hr = OnDeleteItem(GetMessageItem(msg, param1));
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONENDTITLEEDIT:
+ hr = OnEndTitleEdit(GetMessageItem(msg, param1), (LPCWSTR)param2);
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_TREE_ONKEYDOWN:
+ hr = OnKeyDown(GetMessageItem(msg, param1), (NMTVKEYDOWN*)param2);
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONDESTROY:
+ OnControlDestroy();
+ *result = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+HNAVITEM Navigation::GetActive(ifc_omservice **serviceOut)
+{
+ ifc_omservice *service;
+ HNAVITEM hActive = (NULL != hLibrary) ? MLNavCtrl_GetSelection(hLibrary) : NULL;
+ if (NULL == hActive || FAILED(GetService(hActive, &service)))
+ {
+ hActive = NULL;
+ service = NULL;
+ }
+
+ if (NULL != serviceOut)
+ *serviceOut = service;
+ else if (NULL != service)
+ service->Release();
+
+ return hActive;
+}
+
+HWND Navigation::GetActiveView(ifc_omservice **serviceOut)
+{
+ HWND hView = (NULL != hLibrary) ? ((HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0)) : NULL;
+ if (NULL != hView)
+ {
+ WCHAR szBuffer[128] = {0};
+ if (!GetClassName(hView, szBuffer, ARRAYSIZE(szBuffer)) ||
+ CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, szBuffer, -1,
+ L"Nullsoft_omBrowserView", -1))
+ {
+ hView = NULL;
+ }
+ }
+
+ ifc_omservice *service;
+ if (NULL == hView || FALSE == BrowserView_GetService(hView, &service))
+ {
+ hView = NULL;
+ service = NULL;
+ }
+
+ if (NULL != serviceOut)
+ *serviceOut = service;
+ else if (NULL != service)
+ service->Release();
+
+ return hView;
+}
+
+HRESULT Navigation::SelectItem(HNAVITEM hItem, LPCWSTR pszUrl)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+
+ ifc_omservice *service;
+ HRESULT hr = GetService(hItem, &service);
+ if (FAILED(hr)) return hr;
+
+ hr = SelectItemInt(hItem, service->GetId(), pszUrl);
+ service->Release();
+
+ return hr;
+}
+
+HRESULT Navigation::DeleteItem(HNAVITEM hItem)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ ifc_omservice *service;
+ if (FAILED(GetService(hItem, &service)))
+ return E_FAIL;
+
+ HRESULT hr;
+ UINT serviceFlags;
+ if (FAILED(service->GetFlags(&serviceFlags))) serviceFlags = 0;
+ if (0 == (SVCF_SPECIAL & serviceFlags))
+ {
+ MLNavCtrl_BeginUpdate(hLibrary, 0);
+ HNAVITEM hSelection = MLNavCtrl_GetSelection(hLibrary);
+ if (hSelection == hItem)
+ {
+ HNAVITEM hNext = MLNavItem_GetNext(hLibrary, hItem);
+ if (NULL == hNext)
+ hNext = MLNavItem_GetPrevious(hLibrary, hItem);
+
+ if (NULL != hNext)
+ {
+ MLNavItem_Select(hLibrary, hNext);
+ }
+ }
+
+ BOOL result = MLNavCtrl_DeleteItem(hLibrary, hItem);
+ hr = (FALSE != result) ? S_OK : E_FAIL;
+
+ MLNavCtrl_EndUpdate(hLibrary);
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ service->Release();
+
+ return hr;
+}
+
+HRESULT Navigation::DeleteAll()
+{
+ if (NULL == hRoot || NULL == hLibrary) return E_UNEXPECTED;
+
+ std::vector<HNAVITEM> itemList;
+ HNAVITEM hItem = MLNavItem_GetChild(hLibrary, hRoot);
+ while (NULL != hItem)
+ {
+ itemList.push_back(hItem);
+ hItem = MLNavItem_GetNext(hLibrary, hItem);
+ }
+
+ MLNavCtrl_BeginUpdate(hLibrary, 0);
+
+ NAVITEM item;
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_PARAM;
+
+ size_t index = itemList.size();
+ while(index--)
+ {
+ item.hItem = itemList[index];
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service)
+ {
+ service->AddRef();
+
+ UINT serviceFlags;
+ if (SUCCEEDED(service->GetFlags(&serviceFlags)) &&
+ 0 == (SVCF_SPECIAL & serviceFlags) &&
+ FALSE != MLNavCtrl_DeleteItem(hLibrary, item.hItem))
+ {
+ }
+
+ service->Release();
+ }
+ }
+ }
+
+ MLNavCtrl_EndUpdate(hLibrary);
+ return S_OK;
+}
+HRESULT Navigation::InitializeBrowser()
+{
+ if (NULL == OMBROWSERMNGR)
+ return E_UNEXPECTED;
+
+ HWND hWinamp = Plugin_GetWinamp();
+
+ HRESULT hr = OMBROWSERMNGR->Initialize(NULL, hWinamp);
+ if (SUCCEEDED(hr))
+ {
+ if (S_OK == hr)
+ {
+ ifc_ombrowsereventmngr *eventManager;
+ if (SUCCEEDED(OMBROWSERMNGR->QueryInterface(IFC_OmBrowserEventManager, (void**)&eventManager)))
+ {
+ BrowserEvent *eventHandler;
+ if (SUCCEEDED(BrowserEvent::CreateInstance(&eventHandler)))
+ {
+ eventManager->RegisterHandler(eventHandler);
+ eventHandler->Release();
+ }
+ eventManager->Release();
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT Navigation::CreatePopup(HNAVITEM hItem, HWND *hwnd)
+{
+ if (NULL == hwnd) return E_POINTER;
+ *hwnd = NULL;
+
+ if (NULL == hLibrary) return E_UNEXPECTED;
+ if (NULL == hItem) return E_INVALIDARG;
+
+ HRESULT hr;
+
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ HWND hWinamp = Plugin_GetWinamp();
+
+ hr = InitializeBrowser();
+ if (SUCCEEDED(hr))
+ {
+ RECT rect;
+ HWND hFrame = (HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == hFrame) hFrame = hLibrary;
+ if (NULL == hFrame || FALSE == GetWindowRect(hFrame, &rect))
+ {
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ rect.left += 16;
+ rect.top += 16;
+
+ hr = OMBROWSERMNGR->CreatePopup(service, rect.left, rect.top,
+ rect.right - rect.left, rect.bottom - rect.top, hWinamp, NULL, 0, hwnd);
+ }
+ }
+ service->Release();
+ }
+ return hr;
+}
+
+static void CALLBACK Navigation_AsyncCallback(ULONG_PTR data)
+{
+ NAVASYNCPARAM *async = (NAVASYNCPARAM*)data;
+ if (NULL == async) return;
+
+ async->callback(async->instance, async->param);
+ if (NULL != async->instance)
+ async->instance->Release();
+ free(async);
+}
+
+HRESULT Navigation::PostMainThreadCallback(NavigationCallback callback, ULONG_PTR param)
+{
+ if (NULL == callback)
+ return E_INVALIDARG;
+
+ NAVASYNCPARAM *async = (NAVASYNCPARAM*)calloc(1, sizeof(NAVASYNCPARAM));
+ if (NULL == async) return E_OUTOFMEMORY;
+
+ async->instance = this;
+ async->callback = callback;
+ async->param = param;
+ this->AddRef();
+
+ HRESULT hr;
+ if (NULL == OMUTILITY)
+ hr = E_FAIL;
+ else
+ {
+ hr = OMUTILITY->PostMainThreadCallback(Navigation_AsyncCallback, (ULONG_PTR)async);
+ if (FAILED(hr))
+ {
+ Release();
+ free(async);
+ }
+ }
+
+ return hr;
+}
+
+static void CALLBACK Navigation_CreateItemAsyncCallback(Navigation *instance, ULONG_PTR param)
+{
+ ifc_omservice *service= (ifc_omservice*)param;
+
+ if (NULL != service)
+ {
+ if (NULL != instance)
+ instance->CreateItem(service);
+
+ service->Release();
+ }
+}
+
+HRESULT Navigation::CreateItemAsync(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;;
+
+ service->AddRef();
+
+ HRESULT hr = PostMainThreadCallback(Navigation_CreateItemAsyncCallback, (ULONG_PTR)service);
+ if (FAILED(hr))
+ service->Release();
+
+ return hr;
+}
+
+
+HRESULT Navigation::GetService(HNAVITEM hItem, ifc_omservice **service)
+{
+ WCHAR szBuffer[64] = {0};
+
+ if (NULL == service) return E_POINTER;
+ *service = NULL;
+
+ if (NULL == hLibrary || NULL == hItem)
+ return E_INVALIDARG;
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(NAVITEM);
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT;
+
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+
+ if (FALSE == Navigation_CheckInvariantName(szBuffer))
+ return E_NAVITEM_UNKNOWN;
+
+ *service = (ifc_omservice*)itemInfo.lParam;
+ (*service)->AddRef();
+ return S_OK;
+}
+
+static void CALLBACK Navigtaion_UpdateServiceApc(Dispatchable *object, ULONG_PTR param1, ULONG_PTR param2)
+{
+ Navigation *navigation = (Navigation*)object;
+ if (NULL != navigation)
+ {
+ ifc_omservice *service = (ifc_omservice*)param1;
+ navigation->UpdateService(service, (UINT)param2);
+ if (NULL != service) service->Release();
+ }
+}
+
+HRESULT Navigation::UpdateService(ifc_omservice *service, UINT modifiedFlags)
+{
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ DWORD libraryTID = GetWindowThreadProcessId(hLibrary, NULL);
+ DWORD currentTID = GetCurrentThreadId();
+ if (libraryTID != currentTID)
+ {
+ if (NULL != OMUTILITY)
+ {
+ service->AddRef();
+ if (FAILED(OMUTILITY->PostMainThreadCallback2(Navigtaion_UpdateServiceApc, this, (ULONG_PTR)service, (ULONG_PTR)modifiedFlags)))
+ service->Release();
+ }
+ return E_PENDING;
+ }
+
+ HNAVITEM hItem = FindService(service->GetId(), NULL);
+ if (NULL == hItem)
+ return E_FAIL;
+
+ if (0 != (ifc_omserviceeditor::modifiedFlags & modifiedFlags) &&
+ S_FALSE == ServiceHelper_IsSubscribed(service))
+ {
+ DeleteItem(hItem);
+ return S_OK;
+ }
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(NAVITEM);
+ itemInfo.hItem = hItem;
+
+ itemInfo.mask = NIMF_IMAGE;
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ itemInfo.iImage= -1;
+
+ itemInfo.mask = 0;
+
+ WCHAR szName[512] = {0};
+ if (0 != (ifc_omserviceeditor::modifiedName & modifiedFlags) &&
+ SUCCEEDED(service->GetName(szName, ARRAYSIZE(szName))))
+ {
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = szName;
+ }
+
+
+ if (0 != (ifc_omserviceeditor::modifiedIcon & modifiedFlags))
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ INT iImage;
+ WCHAR szIcon[1024] = {0};
+ if (FAILED(service->GetIcon(szIcon, ARRAYSIZE(szIcon))) ||
+ FAILED(navHelper->QueryIndex(szIcon, &iImage, NULL)))
+ {
+ iImage = -1;
+ }
+
+ if (itemInfo.iImage != iImage)
+ {
+ itemInfo.mask |= NIMF_IMAGE | NIMF_IMAGESEL;
+ itemInfo.iImage = iImage;
+ itemInfo.iSelectedImage = iImage;
+ }
+
+ navHelper->Release();
+ }
+ }
+
+ if (0 != itemInfo.mask)
+ {
+ if (FALSE == MLNavItem_SetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+ }
+
+ NAVITEMINAVLIDATE invalidate;
+ invalidate.hItem = hItem;
+ invalidate.fErase = FALSE;
+ invalidate.prc = NULL;
+ MLNavItem_Invalidate(hLibrary, &invalidate);
+
+ return S_OK;
+}
+
+HNAVITEM Navigation::FindService(UINT serviceId, ifc_omservice **serviceOut)
+{
+ if (NULL == hRoot || NULL == hLibrary)
+ {
+ if (NULL != serviceOut) *serviceOut = NULL;
+ return NULL;
+ }
+
+ WCHAR szBuffer[128] = {0};
+ NAVITEM item = {0};
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
+ item.pszInvariant = szBuffer;
+ item.cchInvariantMax = ARRAYSIZE(szBuffer);
+
+ item.hItem = hRoot;
+
+ while (NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item) &&
+ FALSE != Navigation_CheckInvariantName(item.pszInvariant))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service && serviceId == service->GetId())
+ {
+ if (NULL != serviceOut)
+ {
+ *serviceOut = service;
+ service->AddRef();
+ }
+ return item.hItem;
+ }
+ }
+
+ item.hItem = (HNAVITEM)SENDMLIPC(hLibrary,
+ (item.hItem == hRoot) ? ML_IPC_NAVITEM_GETCHILD : ML_IPC_NAVITEM_GETNEXT,
+ (WPARAM)item.hItem);
+
+ }
+
+ if (NULL != serviceOut) *serviceOut = NULL;
+ return NULL;
+}
+
+HRESULT Navigation::ShowService(UINT serviceId, LPCWSTR pszUrl)
+{
+ ifc_omservice *service;
+ HNAVITEM hItem = FindService(serviceId, &service);
+ if (NULL == hItem) return E_FAIL;
+
+ HRESULT hr = SelectItemInt(hItem, serviceId, pszUrl);
+ service->Release();
+
+ return hr;
+}
+
+HNAVITEM Navigation::CreateItem(ifc_omservice *service, int altMode)
+{
+ if (NULL == hLibrary || NULL == hRoot) return NULL;
+ return CreateItemInt(hRoot, service, altMode);
+}
+
+HRESULT Navigation::GenerateServiceName(LPWSTR pszBuffer, INT cchBufferMax)
+{
+ if (NULL == pszBuffer) return E_POINTER;
+ *pszBuffer = L'\0';
+
+ if (NULL == hLibrary || NULL == hRoot) return E_UNEXPECTED;
+
+ if (FAILED(Plugin_CopyResString(pszBuffer, cchBufferMax, MAKEINTRESOURCE(IDS_USERSERVICE_NAME))))
+ return E_UNEXPECTED;
+
+ INT cchName = lstrlen(pszBuffer);
+ LPWSTR pszFormat = pszBuffer + cchName;
+ INT cchFormatMax = cchBufferMax - cchName;
+
+ WCHAR szText[512] = {0};
+ NAVITEM item = {0};
+
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_TEXT;
+ item.pszText = szText;
+ item.cchTextMax = ARRAYSIZE(szText);
+
+ BOOL fFound = TRUE;
+
+ for(INT index = 1; FALSE != fFound; index++)
+ {
+ fFound = FALSE;
+ if (FAILED(StringCchPrintf(pszFormat, cchFormatMax, L" %d", index)))
+ {
+ pszFormat = L'\0';
+ return E_FAIL;
+ }
+
+ item.hItem = MLNavItem_GetChild(hLibrary, hRoot);
+ while(NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item) &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item.pszText, -1, pszBuffer, -1))
+ {
+ fFound = TRUE;
+ break;
+ }
+ item.hItem = MLNavItem_GetNext(hLibrary, item.hItem);
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Navigation::CreateUserService(HNAVITEM *itemOut)
+{
+ HRESULT hr;
+
+ if (NULL != itemOut)
+ *itemOut = NULL;
+
+ if (NULL == hRoot) return E_FAIL;
+
+ INT serviceId = 710;
+ while(NULL != FindService(serviceId, NULL)) serviceId++;
+
+ WCHAR szName[256] = {0};
+ if (FAILED(GenerateServiceName(szName, ARRAYSIZE(szName))))
+ return E_FAIL;
+
+ ifc_omservice *service;
+ hr = ServiceHelper_Create(serviceId, szName, NULL, L"about:blank", SVCF_SUBSCRIBED | SVCF_PREAUTHORIZED, 2, TRUE, &service);
+
+ if (SUCCEEDED(hr))
+ {
+ HNAVITEM hItem = CreateItem(service, 1);
+ if (NULL == hItem)
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ if (NULL != itemOut)
+ *itemOut = hItem;
+ }
+
+ service->Release();
+ }
+
+ return hr;
+}
+
+typedef struct __ICONPATCHREC
+{
+ LPCWSTR iconName;
+ LPCWSTR resourceName;
+} ICONPATCHREC;
+
+const static ICONPATCHREC szIconPatch[] =
+{
+ //{ L"11000", MAKEINTRESOURCEW(IDR_ICON_AOL)},
+ { L"11001", MAKEINTRESOURCEW(IDR_ICON_SHOUTCASTRADIO)},
+ /*{ L"11002", MAKEINTRESOURCEW(IDR_ICON_SHOUTCASTTV)},
+ { L"11003", MAKEINTRESOURCEW(IDR_ICON_WINAMPMUSIC)},
+ { L"11004", MAKEINTRESOURCEW(IDR_ICON_SINGINGFISH)},
+ { L"11005", MAKEINTRESOURCEW(IDR_ICON_MUSICNOW)},
+ { L"11006", MAKEINTRESOURCEW(IDR_ICON_AOL_GAMES)},
+ { L"11007", MAKEINTRESOURCEW(IDR_ICON_IN2TV)},
+ { L"11008", MAKEINTRESOURCEW(IDR_ICON_WINAMREMOTE)},*/
+};
+
+HRESULT Navigation::PatchIconName(LPWSTR pszIcon, UINT cchMaxIconLen, ifc_omservice *service)
+{
+ if (NULL == pszIcon) return E_INVALIDARG;
+ if (L'\0' == *pszIcon) return S_FALSE;
+
+ for(INT i = 0; i < ARRAYSIZE(szIconPatch); i++)
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, szIconPatch[i].iconName, -1, pszIcon, -1))
+ {
+ if (IS_INTRESOURCE(szIconPatch[i].resourceName))
+ {
+ return Plugin_MakeResourcePath(pszIcon, cchMaxIconLen, RT_RCDATA, szIconPatch[i].resourceName, RESPATH_COMPACT);
+ }
+ return StringCchCopy(pszIcon, cchMaxIconLen, szIconPatch[i].resourceName);
+ }
+ }
+
+ if (FALSE == PathIsURL(pszIcon) && FALSE != PathIsRelative(pszIcon))
+ {
+ WCHAR szTemp[2048] = {0};
+ HRESULT hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pszIcon);
+ if (SUCCEEDED(hr))
+ {
+ ServiceHost *serviceHost;
+ hr = ServiceHost::GetCachedInstance(&serviceHost);
+ if (SUCCEEDED(hr))
+ {
+ WCHAR szBase[MAX_PATH] = {0};
+ if (SUCCEEDED(serviceHost->GetBasePath(service, szBase, ARRAYSIZE(szBase))))
+ {
+ if (L'\0' != szBase[0] && FALSE != PathIsRelative(szBase))
+ {
+ LPCWSTR pszUser = (NULL != WASABI_API_APP) ? WASABI_API_APP->path_getUserSettingsPath() : NULL;
+ if (NULL != pszUser)
+ {
+ StringCchCopy(pszIcon, cchMaxIconLen, pszUser);
+ }
+ PathAppend(pszIcon, szBase);
+ }
+ else
+ {
+ StringCchCopy(pszIcon, cchMaxIconLen, szBase);
+ }
+
+ PathAppend(pszIcon, szTemp);
+ }
+ serviceHost->Release();
+ }
+ }
+ if (FAILED(hr))
+ return hr;
+ }
+
+ return S_FALSE;
+}
+
+HNAVITEM Navigation::CreateItemInt(HNAVITEM hParent, ifc_omservice *service, int altMode)
+{
+ if (!altMode && (S_OK != ServiceHelper_IsSubscribed(service)))
+ return NULL;
+
+ WCHAR szName[256] = {0}, szInvariant[64] = {0};
+ if (FAILED(service->GetName(szName, ARRAYSIZE(szName))))
+ return NULL;
+
+ if (L'\0' == szName[0])
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEFAULT_SERVICENAME, szName, ARRAYSIZE(szName));
+
+ if (FAILED(StringCchPrintf(szInvariant, ARRAYSIZE(szInvariant), NAVITEM_PREFIX L"%u", service->GetId())))
+ return NULL;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.hInsertAfter = NULL;
+ nis.hParent = hParent;
+
+ INT iIcon = -1;
+ ifc_mlnavigationhelper *navHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ WCHAR szIcon[2048] = {0};
+ if (FAILED(service->GetIcon(szIcon, ARRAYSIZE(szIcon))) ||
+ FAILED(PatchIconName(szIcon, ARRAYSIZE(szIcon), service)) ||
+ FAILED(navHelper->QueryIndex(szIcon, &iIcon, NULL)))
+ {
+ iIcon = -1;
+ }
+ navHelper->Release();
+ }
+
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT | NIMF_PARAM | NIMF_IMAGE | NIMF_IMAGESEL;
+
+ nis.item.id = 0;
+ nis.item.pszText = szName;
+ nis.item.pszInvariant = szInvariant;
+ nis.item.lParam = (LPARAM)service;
+
+ nis.item.style = 0;
+ UINT serviceFlags;
+ if (FAILED(service->GetFlags(&serviceFlags)))
+ serviceFlags = 0;
+
+ if (0 != (SVCF_SPECIAL & serviceFlags))
+ {
+ nis.item.style |= (NIS_HASCHILDREN | NIS_ALLOWCHILDMOVE);
+ iIcon = -1;
+ }
+
+ nis.item.styleMask = nis.item.style;
+
+ nis.item.iImage = iIcon;
+ nis.item.iSelectedImage = iIcon;
+
+
+ HNAVITEM hItem = MLNavCtrl_InsertItem(hLibrary, &nis);
+ if (NULL != hItem)
+ {
+ ServiceHost *serviceHost;
+ if (SUCCEEDED(ServiceHost::GetCachedInstance(&serviceHost)))
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->RegisterHandler(serviceHost);
+ eventManager->Release();
+ }
+
+ serviceHost->Release();
+ }
+
+ service->AddRef();
+ }
+
+ return hItem;
+}
+
+HRESULT Navigation::SelectItemInt(HNAVITEM hItem, UINT serviceId, LPCWSTR pszUrl)
+{
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ if (NULL != pszUrl && L'\0' != *pszUrl)
+ {
+ HRESULT hr = forceUrl.Set(serviceId, pszUrl);
+ if (FAILED(hr)) return hr;
+ }
+ else
+ {
+ forceUrl.Remove(serviceId);
+ }
+
+ if (FALSE == MLNavItem_Select(hLibrary, hItem))
+ {
+ forceUrl.Remove(serviceId);
+ return E_FAIL;
+ }
+
+ return S_OK;
+
+}
+HNAVITEM Navigation::GetMessageItem(INT msg, INT_PTR param1)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(hLibrary, param1) :
+ (HNAVITEM)param1;
+}
+
+HRESULT Navigation::OnCreateView(HNAVITEM hItem, HWND hParent, HWND *hView)
+{
+ if (NULL == hView) return E_POINTER;
+ *hView = NULL;
+
+ if (NULL == hLibrary) return E_UNEXPECTED;
+ if (NULL == hItem || NULL == hParent) return E_INVALIDARG;
+
+ HRESULT hr;
+
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ hr = InitializeBrowser();
+ if (SUCCEEDED(hr))
+ {
+ LPWSTR pszUrl;
+ if (S_OK != forceUrl.Peek(service->GetId(), &pszUrl))
+ pszUrl = NULL;
+
+ hr = OMBROWSERMNGR->CreateView(service, hParent, pszUrl, 0, hView);
+ forceUrl.FreeString(pszUrl);
+ }
+
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::OnContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts)
+{
+ if (NULL == hItem || NULL == hHost)
+ return E_INVALIDARG;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ HRESULT hr;
+ ifc_omservice *service, *activeService;
+ hr = GetService(hItem, &service);
+ if (FAILED(hr)) return hr;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(hLibrary, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HWND activeView = GetActiveView(&activeService);
+ if (NULL == activeView) activeService = NULL;
+
+ INT menuKind = (hItem == hRoot) ? OMMENU_GALERYCONTEXT : OMMENU_SERVICECONTEXT;
+
+ UINT menuFlags = MCF_VIEW;
+ if (NULL != activeService && activeService->GetId() == service->GetId())
+ menuFlags |= MCF_VIEWACTIVE;
+
+ UINT rating;
+ if (OMMENU_SERVICECONTEXT == menuKind && SUCCEEDED(service->GetRating(&rating)))
+ menuFlags |= RATINGTOMCF(rating);
+
+ HMENU hMenu = Menu_GetMenu(menuKind, menuFlags);
+ if (NULL != hMenu)
+ {
+ INT commandId = DoTrackPopup(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ pt.x, pt.y, hHost, NULL);
+
+ Menu_ReleaseMenu(hMenu, menuKind);
+
+ if (0 != commandId && FALSE != Command_ProcessService(activeView, service, commandId, NULL))
+ commandId = 0;
+
+ if (0 != commandId && NULL != activeView && FALSE != Command_ProcessView(activeView, commandId, NULL))
+ commandId = 0;
+
+ if (0 != commandId && FALSE != Command_ProcessGeneral(commandId, NULL))
+ commandId = 0;
+ }
+
+ if (NULL != activeService)
+ activeService->Release();
+
+ service->Release();
+
+ return hr;
+}
+
+HRESULT Navigation::OnEndTitleEdit(HNAVITEM hItem, LPCWSTR pszNewTitle)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ HRESULT hr;
+
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != pszNewTitle)
+ {
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ hr = editor->SetName(pszNewTitle, FALSE);
+ editor->Release();
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ ServiceHelper_Save(service);
+
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::OnDeleteItem(HNAVITEM hItem)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ WCHAR szBuffer[2048] = {0};
+ NAVITEM itemInfo = {0};
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT | NIMF_IMAGE;
+
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+ if (FALSE == Navigation_CheckInvariantName(szBuffer))
+ return E_NAVITEM_UNKNOWN;
+
+ ifc_omservice *service = (ifc_omservice*)itemInfo.lParam;
+
+ if (NULL != service)
+ {
+ if (SUCCEEDED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->ReleaseIndex(szBuffer);
+ navHelper->Release();
+ }
+ }
+
+ ifc_ombrowserwndmngr *windowManager;
+ if (NULL != OMBROWSERMNGR && SUCCEEDED(OMBROWSERMNGR->QueryInterface(IFC_OmBrowserWindowManager, (void**)&windowManager)))
+ {
+ UINT serviceId = service->GetId();
+
+ ifc_ombrowserwndenum *windowEnum;
+ if (SUCCEEDED(windowManager->Enumerate(NULL, &serviceId, &windowEnum)))
+ {
+ HWND hwnd;
+ while (S_OK == windowEnum->Next(1, &hwnd, NULL))
+ {
+ DestroyWindow(hwnd);
+ }
+ windowEnum->Release();
+ }
+
+ windowManager->Release();
+ }
+
+ ServiceHost *serviceHost;
+ if (SUCCEEDED(ServiceHost::GetCachedInstance(&serviceHost)))
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->UnregisterHandler(serviceHost);
+ eventManager->Release();
+ }
+ serviceHost->Release();
+ }
+
+ itemInfo.mask = NIMF_PARAM;
+ itemInfo.lParam = 0L;
+ MLNavItem_SetInfo(hLibrary, &itemInfo);
+
+ service->Release();
+ }
+
+
+
+ return S_OK;
+}
+
+HRESULT Navigation::OnKeyDown(HNAVITEM hItem, NMTVKEYDOWN *pnmkd)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ ifc_omservice *service;
+ HRESULT hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ switch(pnmkd->wVKey)
+ {
+ case VK_DELETE:
+ {
+ BOOL fSuccess;
+ ifc_omservice *activeService;
+ HWND activeView = GetActiveView(&activeService);
+ if (IsWindow(activeView))
+ {
+ Command_ProcessService(activeView, service, ID_SERVICE_UNSUBSCRIBE, &fSuccess);
+ }
+ }
+ break;
+ }
+
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::OnControlDestroy()
+{
+ SaveOrder();
+ return S_OK;
+}
+
+#define CBCLASS Navigation
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+VCB(API_IMAGECHANGED, ImageChanged)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/navigation.h b/Src/Plugins/Library/ml_online/navigation.h
new file mode 100644
index 00000000..c25682df
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/navigation.h
@@ -0,0 +1,96 @@
+#ifndef NULLOSFT_ONLINEMEDIA_PLUGIN_NAVIGATION_HEADER
+#define NULLOSFT_ONLINEMEDIA_PLUGIN_NAVIGATION_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <ifc_mlnavigationcallback.h>
+#include "./forceUrl.h"
+#include <vector>
+
+class ifc_omservice;
+class forceUrl;
+typedef LPVOID HNAVITEM;
+
+#define ROOTSERVICE_ID 777
+
+typedef void (CALLBACK *NavigationCallback)(Navigation* /*instance*/, ULONG_PTR /*param*/);
+
+class Navigation : public ifc_mlnavigationcallback
+{
+protected:
+ Navigation();
+ ~Navigation();
+
+public:
+ static HRESULT CreateInstance(Navigation **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_mlnavigationcallback */
+ void ImageChanged(LPCWSTR pszName, INT index);
+
+public:
+ HRESULT Finish();
+
+ BOOL ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result);
+
+ HNAVITEM GetActive(ifc_omservice **serviceOut);
+ HWND GetActiveView(ifc_omservice **serviceOut);
+
+ HRESULT SelectItem(HNAVITEM hItem, LPCWSTR pszUrl);
+ HRESULT DeleteItem(HNAVITEM hItem);
+ HRESULT DeleteAll();
+
+ HRESULT GenerateServiceName(LPWSTR pszBuffer, INT cchBufferMax);
+
+ HRESULT CreatePopup(HNAVITEM hItem, HWND *hwnd);
+
+ HRESULT GetService(HNAVITEM hItem, ifc_omservice **service);
+ HRESULT UpdateService(ifc_omservice *service, UINT modifiedFlags);
+ HNAVITEM FindService(UINT serviceId, ifc_omservice **serviceOut);
+ HRESULT ShowService(UINT serviceId, LPCWSTR pszUrl);
+
+ HNAVITEM CreateItem(ifc_omservice *service, int altMode = 0);
+ HRESULT CreateItemAsync(ifc_omservice *service);
+ HRESULT CreateUserService(HNAVITEM *itemOut);
+
+protected:
+ HRESULT Initialize();
+ HRESULT InitializeBrowser();
+ HRESULT SaveOrder();
+ HRESULT Order(std::vector<ifc_omservice *> &list);
+
+ HNAVITEM GetMessageItem(INT msg, INT_PTR param1);
+ HNAVITEM CreateItemInt(HNAVITEM hParent, ifc_omservice *service, int altMode = 0);
+ HRESULT SelectItemInt(HNAVITEM hItem, UINT serviceId, LPCWSTR pszUrl);
+
+ HRESULT PatchIconName(LPWSTR pszIcon, UINT cchMaxIconLen, ifc_omservice *service);
+
+ HRESULT OnCreateView(HNAVITEM hItem, HWND hParent, HWND *hView);
+ HRESULT OnContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts);
+ HRESULT OnEndTitleEdit(HNAVITEM hItem, LPCWSTR pszNewTitle);
+ HRESULT OnDeleteItem(HNAVITEM hItem);
+ HRESULT OnKeyDown(HNAVITEM hItem, NMTVKEYDOWN *pnmkd);
+ HRESULT OnControlDestroy();
+
+ HRESULT PostMainThreadCallback(NavigationCallback callback, ULONG_PTR param);
+
+protected:
+ size_t ref;
+ HNAVITEM hRoot;
+ HWND hLibrary;
+ UINT cookie;
+ ForceUrl forceUrl;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif // NULLOSFT_ONLINEMEDIA_PLUGIN_NAVIGATION_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/png.rc b/Src/Plugins/Library/ml_online/png.rc
new file mode 100644
index 00000000..a1468d05
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/png.rc
@@ -0,0 +1,27 @@
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Data
+//
+IDR_SERVICE64X64_IMAGE RCDATA
+".\\resources\\service64x64.png"
+IDR_ICON_DEFAULT RCDATA
+".\\resources\\iconDefault.png"
+//IDR_ICON_AOL RCDATA
+//".\\resources\\iconAol.png"
+//IDR_ICON_AOL_GAMES RCDATA
+//".\\resources\\iconAolGames.png"
+//IDR_ICON_IN2TV RCDATA
+//".\\resources\\iconIn2Tv.png"
+//IDR_ICON_MUSICNOW RCDATA
+//".\\resources\\iconMusicNow.png"
+IDR_ICON_SHOUTCASTRADIO RCDATA
+".\\resources\\iconShoutcastRadio.png"
+//IDR_ICON_SHOUTCASTTV RCDATA
+//".\\resources\\iconShoutcastTv.png"
+//IDR_ICON_SINGINGFISH RCDATA
+//".\\resources\\iconSingingfish.png"
+//IDR_ICON_WINAMPMUSIC RCDATA
+//".\\resources\\iconWaMusic.png"
+//IDR_ICON_WINAMREMOTE RCDATA
+//".\\resources\\iconWaRemote.png"
diff --git a/Src/Plugins/Library/ml_online/resource.h b/Src/Plugins/Library/ml_online/resource.h
new file mode 100644
index 00000000..db9fbbde
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resource.h
@@ -0,0 +1,206 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_online.rc
+//
+#define IDS_NULLSOFT_ONLINE_SERVICES 0
+#define IDS_ONLINE_SERVICES 1
+#define IDOK 1
+#define IDS_NO_INTERNET_CONNECTION 2
+#define IDCANCEL 2
+#define IDS_SORRY 3
+#define IDR_CONTEXTMENU 4
+#define IDD_MAINDIALOG 101
+#define IDD_OMPREF 102
+#define IDD_OPENURL 103
+#define IDS_USERSERVICE_NAME 104
+#define IDS_FILEFILTER_ALL 105
+#define IDS_FILEFILTER_ALLKNOWN 106
+#define IDS_IMPORT_FILES 109
+#define IDS_DEFAULT_SERVICENAME 148
+#define IDS_MORE 151
+#define IDS_HOME 152
+#define IDS_HOME_DESCRIPTION 153
+#define IDS_BACK 154
+#define IDS_BACK_DESCRIPTION 155
+#define IDS_FORWARD 156
+#define IDS_FORWARD_DESCRIPTION 157
+#define IDS_REFRESH 158
+#define IDS_REFRESH_DESCRIPTION 159
+#define IDS_STOP 160
+#define IDS_STOP_DESCRIPTION 161
+#define IDS_SEPARATOR 162
+#define IDS_SPACE 163
+#define IDS_FLEXSPACE 164
+#define IDS_MORE_DESCRIPTION 165
+#define IDS_RATED 166
+#define IDS_PLEASE_WAIT 167
+#define IDS_SERVICE_CHECKINGVERSION 168
+#define IDS_SERVICE_GETINFO 169
+#define IDS_SERVICE_GETINFO_DESCRIPTION 170
+#define IDS_SERVICE_REPORT 171
+#define IDS_SERVICE_REPORT_DESCRIPTION 172
+#define IDS_SERVICE_UNSUBSCRIBE 173
+#define IDS_SERVICE_UNSUBSCRIBE_DESCRIPTION 174
+#define IDS_RATING_0 175
+#define IDS_RATING_1 176
+#define IDS_RATING_2 177
+#define IDS_RATING_3 178
+#define IDS_RATING_4 179
+#define IDS_RATING_5 180
+#define IDS_RATING_CHANGETO 181
+#define IDS_RATING_CURRENT 183
+#define IDD_MESSAGEBOX 186
+#define IDD_SETUPPAGE 187
+#define IDS_SETUPPAGE_TITLE 188
+#define IDS_SETUP_EMPTYGROUP 189
+#define IDS_SETUP_LOADINGGROUP 190
+#define IDS_SERVICEGROUP_FEATURED 191
+#define IDS_SERVICEGROUP_KNOWN 192
+#define IDD_SETUP_SERVICEDETAILS 192
+#define IDS_SETUP_GROUPLOADFAILED 193
+#define IDS_SERVICE_BYAUTHOR 194
+#define IDS_SERVICE_LASTUPDATED 195
+#define IDD_SETUP_GROUPDETAILS 196
+#define IDS_SERVICEGROUP_FEATUREDLONG 197
+#define IDS_SERVICEGROUP_KNOWNLONG 198
+#define IDS_SERVICEGROUP_FEATURED_DESC 199
+#define IDS_SERVICEGROUP_KNOWN_DESC 200
+#define IDS_MESSAGEBOX_UNSUBSCRIBE 201
+#define IDS_MESSAGEBOX_UNSUBSCRIBE_CAPTION 202
+#define IDS_MESSAGEBOX_RESETTODEFAULT 203
+#define IDS_MESSAGEBOX_RESETTODEFAULT_CAPTION 204
+#define IDS_DOWNLOADSERVICE_JOB 205
+#define IDS_SAVESERVICE_JOB 206
+#define IDS_CACHEICONS_JOB 207
+#define IDS_REGISTERINGSERVICE_JOB 210
+#define IDS_MESSAGEBOX_NAMECHANGED_CAPTION 212
+#define IDS_MESSAGEBOX_NAMECHANGED 213
+#define IDR_SETUPMENU 213
+#define IDS_MESSAGEBOX_POLICYRESET_CAPTION 214
+#define IDS_MESSAGEBOX_POLICYRESET 215
+#define IDS_COLLAPSE 216
+#define IDS_EXPAND 217
+#define IDS_SECURE_CONNECTION 219
+#define IDS_SCRIPT_ERROR 220
+#define IDS_SCRIPT_ERROR_DESCRIPTION 221
+#define IDS_ENCRYPTION_UNSECURE 222
+#define IDS_ENCRYPTION_MIXED 223
+#define IDS_ENCRYPTION_40BIT 224
+#define IDS_ENCRYPTION_56BIT 225
+#define IDS_ENCRYPTION_FORTEZZA 226
+#define IDS_ENCRYPTION_128BIT 227
+#define IDS_CONNECTION_UNSECURE 229
+#define IDS_CONNECTION_ENCRYPTED 230
+#define IDR_HTML1 230
+#define IDS_NAVIGATING 231
+#define IDS_MESSAGEBOX_FORCEREMOVE 232
+#define IDS_MESSAGEBOX_FORCEREMOVE_CAPTION 233
+#define IDS_HISTORY 234
+#define IDS_HISTORY_DESCRIPTION 235
+#define IDS_CURRENT_PAGE 236
+#define IDR_BROWSERACCEL 237
+#define IDS_READMORE 238
+#define IDS_CLICKHERE 239
+#define IDS_MESSAGEBOX_DISCOVERBUSY 240
+#define IDS_MESSAGEBOX_DISCOVERBUSY_CAPTION 241
+#define IDR_HTML_EDITOR 243
+#define IDC_ADDRESS 1000
+#define IDC_REFRESH 1007
+#define IDC_RADIO_DAILY 1035
+#define IDC_RADIO_WEEKLY 1036
+#define IDC_RADIO_NEVER 1037
+#define IDC_AUTOSIZE 1052
+#define IDC_RADIO_HOURLY 1060
+#define IDC_NOWPLAYINGURL 1069
+#define IDC_RADIO_MAXBW 1071
+#define IDC_RADIO_MINBW 1072
+#define IDC_BACK 1310
+#define IDC_FORWARD 1311
+#define IDC_TEXT 1311
+#define IDC_HOME 1312
+#define IDC_SERVICELIST 1312
+#define IDC_STOP 1313
+#define IDC_LABEL_HEADER 1313
+#define IDC_PLACEHOLDER 1315
+#define IDC_DESCRIPTION 1316
+#define IDC_TITLE 1317
+#define IDC_THUMBNAIL 1318
+#define IDC_SERVICEMETA 1319
+#define IDC_STATUS 1321
+#define IDC_BUTTON1 1322
+#define IDC_HELPTEXT 1323
+#define IDR_CURTAINPROGRESS_IMAGE 20000
+#define IDR_TOOLBARLARGE_IMAGE 20001
+#define IDR_TOOLBARPROGRESS_IMAGE 20002
+#define IDR_SERVICE64X64_IMAGE 20003
+#define IDR_ICON_DEFAULT 20010
+#define IDR_ICON_AOL 20011
+#define IDR_ICON_AOL_GAMES 20012
+#define IDR_ICON_IN2TV 20013
+#define IDR_ICON_MUSICNOW 20014
+#define IDR_ICON_SHOUTCASTRADIO 20015
+#define IDR_ICON_SHOUTCASTTV 20016
+#define IDR_ICON_SINGINGFISH 20017
+#define IDR_ICON_WINAMPMUSIC 20018
+#define IDR_ICON_WINAMREMOTE 20019
+#define IDR_MENUARROW_IMAGE 20020
+#define ID_RATING_VALUE_5 40106
+#define ID_RATING_VALUE_4 40107
+#define ID_RATING_VALUE_3 40108
+#define ID_RATING_VALUE_2 40109
+#define ID_RATING_VALUE_1 40110
+#define ID_SERVICE_REPORT 40112
+#define ID_SERVICE_UNSUBSCRIBE 40113
+#define ID_SERVICE_GETINFO 40114
+#define ID_GALERY_OPENVIEW 40119
+#define ID_PLUGIN_PREFERENCES 40123
+#define ID_VIEW_OPEN 40127
+#define ID_NAVIGATION_HOME 40134
+#define ID_NAVIGATION_BACK 40135
+#define ID_NAVIGATION_FORWARD 40136
+#define ID_NAVIGATION_STOP 40138
+#define ID_SERVICEMANAGER_RESET 40141
+#define ID_NAVIGATION_REFRESH 40142
+#define ID_NAVIGATION_HISTORY 40143
+#define ID_TOOLBAR_DOCKTOP 40147
+#define ID_TOOLBAR_DOCKBOTTOM 40148
+#define ID_TOOLBAR_AUTOHIDE 40149
+#define ID_TOOLBAR_TABSTOP 40151
+#define ID_SERVICE_RESETPOLICY 40154
+#define ID_GROUP_COLLAPSE 40161
+#define ID_GROUP_SELECTALL 40162
+#define ID_GROUP_UNSELECTALL 40163
+#define ID_GROUP_TOGGLE 40164
+#define ID_GROUP_RELOAD 40166
+#define ID_BROWSER_SECURECONNECTION 40167
+#define ID_BROWSER_SCRIPTERROR 40168
+#define ID_WINDOW_FULLSCREEN 40169
+#define ID_WINDOW_CLOSE 40170
+#define ID_NAVIGATION_REFRESH_COMPLETELY 40171
+#define ID_VIEW_OPENINNEWWINDOW 40174
+#define ID_VIEW_OPENPOPUP 40175
+#define ID_GALERYCONTEXTMENU_HELP 40176
+#define ID_PLUGIN_HELP 40177
+#define ID_SERVICE_NEW 40178
+#define ID_SERVICE_IMPORT_FILE 40179
+#define ID_SERVICE_IMPORT_URL 40180
+#define ID_SERVICE_EDIT 40181
+#define ID_SERVICE_RELOAD 40182
+#define ID_SERVICE_RESETPERMISSIONS 40183
+#define ID_SERVICE_DELETE 40184
+#define ID_SERVICE_DELETEALL 40185
+#define ID_SERVICE_LOCATE 40186
+#define ID_SERVICE_EDITEXTERNAL 40187
+#define ID_OMBROWSER_OPTIONS 40188
+#define IDS_PLUGIN_NAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 244
+#define _APS_NEXT_COMMAND_VALUE 40189
+#define _APS_NEXT_CONTROL_VALUE 1324
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_online/resources/iconAol.png b/Src/Plugins/Library/ml_online/resources/iconAol.png
new file mode 100644
index 00000000..c48a9cda
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconAol.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconAolGames.png b/Src/Plugins/Library/ml_online/resources/iconAolGames.png
new file mode 100644
index 00000000..d5dce983
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconAolGames.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconDefault.png b/Src/Plugins/Library/ml_online/resources/iconDefault.png
new file mode 100644
index 00000000..6a2d99e6
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconDefault.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconIn2Tv.png b/Src/Plugins/Library/ml_online/resources/iconIn2Tv.png
new file mode 100644
index 00000000..5df2581b
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconIn2Tv.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconMusicNow.png b/Src/Plugins/Library/ml_online/resources/iconMusicNow.png
new file mode 100644
index 00000000..ca645c9e
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconMusicNow.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconShoutcastRadio.png b/Src/Plugins/Library/ml_online/resources/iconShoutcastRadio.png
new file mode 100644
index 00000000..72ea4648
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconShoutcastRadio.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconShoutcastTv.png b/Src/Plugins/Library/ml_online/resources/iconShoutcastTv.png
new file mode 100644
index 00000000..85fcfbca
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconShoutcastTv.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconSingingfish.png b/Src/Plugins/Library/ml_online/resources/iconSingingfish.png
new file mode 100644
index 00000000..a3b7c57f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconSingingfish.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconWaMusic.png b/Src/Plugins/Library/ml_online/resources/iconWaMusic.png
new file mode 100644
index 00000000..32ea227a
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconWaMusic.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/iconWaRemote.png b/Src/Plugins/Library/ml_online/resources/iconWaRemote.png
new file mode 100644
index 00000000..70143fe1
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/iconWaRemote.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/resources/pages/serviceEditor.htm b/Src/Plugins/Library/ml_online/resources/pages/serviceEditor.htm
new file mode 100644
index 00000000..ea7c3bf2
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/pages/serviceEditor.htm
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Online Services Editor</title>
+ <meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema"/>
+ <meta content="True" name="vs_snapToGrid"/>
+ <meta content="False" name="vs_showGrid"/>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
+ <script src="webdev.js" type="text/javascript"></script>
+ <style type="text/css">#Checkbox1 {width: 321px; top: 160px; left: 64px;}</style>
+</head>
+<body style="overflow-y: auto; overflow-x: auto; overflow: visible; FONT-FAMILY: MS Shell Dlg 2;" onload="WebDevEditor_Init();">
+ <h2 align="center">Online Services Editor</h2>
+ <div align="left" nowrap>
+ <div id="DIV1" style="border: 1px solid gray; clear: both; DISPLAY: block; left: 50%; float: none; visibility: visible; margin-left: -204px; overflow: visible; width: 408px; color: black; position: absolute; height: 150px; background-color: dimgray; top: 126px;" align="center" nowrap ms_positioning="GridLayout">
+ <div style="z-index: 110; left: 16px; position: absolute; top: 14px">Id:</div>
+ <input id="scvedt_edt_id" style="z-index: 101; left: 64px; position: absolute; top: 14px" readonly maxlength="16" size="9" tabindex="1"/>
+ <div style="z-index: 102; left: 16px; position: absolute; top: 46px">Name:</div>
+ <input id="scvedt_edt_name" style="z-index: 103; left: 64px; position: absolute; top: 46px; width: 322px;" maxlength="512" size="48" tabindex="2"/>
+ <div style="z-index: 104; left: 16px; position: absolute; top: 78px">Url:</div>
+ <input id="scvedt_edt_url" style="z-index: 105; left: 64px; position: absolute; top: 78px; width: 321px;" maxlength="2048" size="48" name="Text1" tabindex="3"/>
+ <div style="z-index: 106; left: 16px; position: absolute; top: 110px">Icon:</div>
+ <input id="scvedt_edt_icon" style="z-index: 107; left: 64px; position: absolute; top: 110px; width: 321px;" maxlength="512" size="48" name="Text2" tabindex="4"/>
+ <div align = "left" style="z-index: 108; position: absolute; width: 320px; left: 25px;" >
+ <input id="svcedt_btn_save" style="z-index: 109; left: 200px; width: 88px; position: absolute; top: 180px; height: 24px; right: 118px;" onclick="WebDevEditor_Save();" type="submit" value="Save" name="buttonOk" tabindex="6"/>
+ <input id="svcedt_btn_close" style="z-index: 110; left: 297px; width: 88px; position: absolute; top: 180px; height: 24px" onclick="WebDevEditor_Close();" type="button" value="Cancel" name="buttonCancel" tabindex="7"/>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/resources/pages/webdev.js b/Src/Plugins/Library/ml_online/resources/pages/webdev.js
new file mode 100644
index 00000000..96eb0ab9
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/pages/webdev.js
@@ -0,0 +1,95 @@
+function GetUrlParam(name)
+{
+ name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
+ var regexS = "[\\?&]"+name+"=([^&#]*)";
+ var regex = new RegExp( regexS );
+ var results = regex.exec( window.location.href );
+ if( results == null )
+ return "";
+ else
+ return results[1];
+}
+
+function WebDev_OpenService(serviceId, forceUrl)
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else if (false == window.external.WebDev.serviceOpen(serviceId, forceUrl))
+ alert("Unable to open service");
+}
+function WebDev_OpenDocumentation()
+{
+ WebDev_OpenService(701, null);
+}
+
+function WebDev_OpenJSAPI2Test()
+{
+ WebDev_OpenService(702, null);
+}
+
+function WebDev_CreateService()
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else if (false == window.external.WebDev.serviceCreate())
+ alert("Unable to create service");
+}
+
+function WebDevEditor_Init(url)
+{
+ var serviceId = parseInt(GetUrlParam("serviceId"), 10);
+
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else
+ {
+ var info = window.external.WebDev.serviceGetInfo(serviceId);
+ if (null == info)
+ {
+ alert("Unable to get service information");
+ }
+ else
+ {
+ document.getElementById("scvedt_edt_id").value = info.id;
+ document.getElementById("scvedt_edt_name").value = info.name;
+ document.getElementById("scvedt_edt_url").value = info.url;
+ document.getElementById("scvedt_edt_icon").value = info.icon;
+ }
+ }
+}
+
+function WebDevEditor_Save()
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else
+ {
+ var serviceId = parseInt(document.getElementById("scvedt_edt_id").value, 10);
+ if (0 != serviceId)
+ {
+ var serviceName = document.getElementById("scvedt_edt_name").value;
+ var serviceUrl = document.getElementById("scvedt_edt_url").value;
+ var serviceIcon = document.getElementById("scvedt_edt_icon").value;
+ if (false == window.external.WebDev.serviceSetInfo(serviceId, serviceName, serviceIcon, serviceUrl, true))
+ {
+ alert("Unable to set service info");
+ }
+ if (false == window.external.WebDev.serviceOpen(serviceId, 1))
+ {
+ alert("Unable to navigate");
+ }
+ }
+ }
+}
+
+function WebDevEditor_Close()
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else
+ {
+ var serviceId = parseInt(document.getElementById("scvedt_edt_id").value, 10);
+ if (0 == serviceId || false == window.external.WebDev.serviceOpen(serviceId, 1))
+ alert("Unable to navigate");
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/resources/service64x64.png b/Src/Plugins/Library/ml_online/resources/service64x64.png
new file mode 100644
index 00000000..8aaea002
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/resources/service64x64.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_online/serviceHelper.cpp b/Src/Plugins/Library/ml_online/serviceHelper.cpp
new file mode 100644
index 00000000..e2f24bea
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/serviceHelper.cpp
@@ -0,0 +1,1232 @@
+#include "main.h"
+#include "./serviceHelper.h"
+#include "./navigation.h"
+#include "./api__ml_online.h"
+#include "./resource.h"
+#include "./serviceHost.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omfilestorage.h>
+#include <ifc_omwebstorage.h>
+#include <ifc_omserviceeditor.h>
+#include <ifc_omserviceeventmngr.h>
+#include <ifc_omservicecommand.h>
+#include <ifc_omserviceenum.h>
+#include <ifc_omxmlserviceenum.h>
+#include <ifc_omservicecopier.h>
+#include <ifc_mlnavigationhelper.h>
+#include <browserUiCommon.h>
+
+#include <vector>
+
+#include <wininet.h>
+#include <strsafe.h>
+
+typedef std::vector<ifc_omstorageasync*> AsyncList;
+
+struct SERVICEHELPER
+{
+ SERVICEHELPER()
+ : discoverAsync(NULL)
+ {}
+
+ ifc_omstorageasync *discoverAsync;
+ AsyncList versionChecks;
+ CRITICAL_SECTION lock;
+};
+
+static SERVICEHELPER *serviceHelper = NULL;
+
+static void ServiceHelper_Lock()
+{
+ if (NULL != serviceHelper)
+ EnterCriticalSection(&serviceHelper->lock);
+}
+
+static void ServiceHelper_Unlock()
+{
+ if (NULL != serviceHelper)
+ LeaveCriticalSection(&serviceHelper->lock);
+}
+
+static void CALLBACK ServiceHelper_Uninitialize()
+{
+ if (NULL == serviceHelper)
+ return;
+
+ ServiceHelper_Lock();
+
+ ifc_omstorage *storage = NULL;;
+ if (SUCCEEDED(ServiceHelper_QueryWebStorage(&storage)))
+ {
+ if (NULL != serviceHelper->discoverAsync)
+ {
+ storage->RequestAbort(serviceHelper->discoverAsync, TRUE);
+ storage->EndLoad(serviceHelper->discoverAsync, NULL);
+ serviceHelper->discoverAsync->Release();
+ serviceHelper->discoverAsync = NULL;
+ }
+
+ size_t index = serviceHelper->versionChecks.size();
+ while(index--)
+ {
+ ifc_omstorageasync *async = serviceHelper->versionChecks[index];
+ storage->RequestAbort(async, TRUE);
+ storage->EndLoad(async, NULL);
+ }
+ serviceHelper->versionChecks.clear();
+
+ storage->Release();
+ }
+
+ ServiceHelper_Unlock();
+ DeleteCriticalSection(&serviceHelper->lock);
+
+ //free(serviceHelper);
+ delete serviceHelper;
+ serviceHelper = NULL;
+}
+
+HRESULT ServiceHelper_Initialize()
+{
+ if (NULL != serviceHelper)
+ return S_FALSE;
+
+ //serviceHelper = (SERVICEHELPER*)calloc(1, sizeof(SERVICEHELPER));
+ serviceHelper = new SERVICEHELPER();
+
+ if (NULL == serviceHelper)
+ return E_OUTOFMEMORY;
+ InitializeCriticalSection(&serviceHelper->lock);
+
+ Plugin_RegisterUnloadCallback(ServiceHelper_Uninitialize);
+
+ return S_OK;
+}
+
+HRESULT ServiceHelper_QueryStorage(ifc_omstorage **storage)
+{
+ if (NULL == storage) return E_POINTER;
+
+ if (NULL == OMSERVICEMNGR)
+ {
+ *storage = NULL;
+ return E_UNEXPECTED;
+ }
+
+ return OMSERVICEMNGR->QueryStorage(&SUID_OmStorageIni, storage);
+}
+
+HRESULT ServiceHelper_QueryWebStorage(ifc_omstorage **storage)
+{
+ if (NULL == storage) return E_POINTER;
+
+ if (NULL == OMSERVICEMNGR)
+ {
+ *storage = NULL;
+ return E_UNEXPECTED;
+ }
+
+ return OMSERVICEMNGR->QueryStorage(&SUID_OmStorageUrl, storage);
+}
+
+HRESULT ServiceHelper_Create(UINT serviceId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, UINT flags, UINT generation, BOOL fSave, ifc_omservice **serviceOut)
+{
+ if (NULL == serviceOut)
+ return E_POINTER;
+
+ *serviceOut = NULL;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ HRESULT hr = OMSERVICEMNGR->CreateService(serviceId, serviceHost, serviceOut);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceeditor *editor;
+ if (SUCCEEDED((*serviceOut)->QueryInterface(IFC_OmServiceEditor, (void**)&editor)))
+ {
+ WCHAR szBuffer[4096] = {0};
+ editor->BeginUpdate();
+ if (NULL != pszName && IS_INTRESOURCE(pszName))
+ {
+ if (NULL != WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszName, szBuffer, ARRAYSIZE(szBuffer)))
+ editor->SetName(szBuffer, FALSE);
+ }
+ else
+ editor->SetName(pszName, FALSE);
+
+ if (NULL != pszIcon && IS_INTRESOURCE(pszIcon))
+ {
+ if (SUCCEEDED(Plugin_MakeResourcePath(szBuffer, ARRAYSIZE(szBuffer), RT_RCDATA, pszIcon, RESPATH_COMPACT)))
+ editor->SetIcon(szBuffer, FALSE);
+ }
+ else
+ editor->SetIcon(pszIcon, FALSE);
+
+ if (NULL != pszUrl && IS_INTRESOURCE(pszUrl))
+ {
+ if (SUCCEEDED(Plugin_MakeResourcePath(szBuffer, ARRAYSIZE(szBuffer), RT_HTML, pszUrl, RESPATH_TARGETIE | RESPATH_COMPACT)))
+ editor->SetUrl(szBuffer, FALSE);
+ }
+ else
+ editor->SetUrl(pszUrl, FALSE);
+
+ editor->SetGeneration(generation);
+
+ editor->SetFlags(flags, 0xFFFFFFFF);
+
+ if (FALSE != fSave)
+ {
+ hr = ServiceHelper_Save(*serviceOut);
+ if (FAILED(hr))
+ {
+ (*serviceOut)->Release();
+ *serviceOut = NULL;
+ }
+ }
+ else
+ {
+ editor->SetModified(0, (UINT)-1);
+ }
+ editor->EndUpdate();
+ editor->Release();
+ }
+ }
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+
+ return hr;
+}
+
+HRESULT ServiceHelper_Save(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ if (S_OK == ServiceHelper_IsSpecial(service))
+ return S_FALSE;
+
+ HRESULT hr;
+ ifc_omstorage *storage;
+ hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+
+ UINT serviceFlags;
+ if (SUCCEEDED(service->GetFlags(&serviceFlags)) && 0 != (SVCF_AUTOUPGRADE & serviceFlags))
+ {
+ hr = storage->Save(&service, 1, ifc_omstorage::saveClearModified, NULL);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceeditor *editor;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEditor, (void**)&editor)))
+ {
+ editor->BeginUpdate();
+ editor->SetFlags(0, SVCF_AUTOUPGRADE);
+ editor->SetModified(0, ifc_omserviceeditor::modifiedFlags);
+ editor->EndUpdate();
+ editor->Release();
+ }
+ }
+ }
+ else
+ {
+ hr = storage->Save(&service, 1, ifc_omstorage::saveClearModified | ifc_omstorage::saveModifiedOnly, NULL);
+ }
+
+ storage->Release();
+ }
+ return hr;
+}
+
+HRESULT ServiceHelper_Delete(ifc_omservice *service, UINT flags)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ if (S_OK == ServiceHelper_IsSpecial(service))
+ return S_FALSE;
+
+ HRESULT hr;
+
+ if (S_OK == ServiceHelper_IsSubscribed(service))
+ {
+ hr = ServiceHelper_Subscribe(service, FALSE, ((SHF_NOTIFY | SHF_VERBAL) & flags));
+ if (S_OK != hr) return hr;
+ }
+
+ ifc_omstorage *storage = NULL;
+ hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr) && storage)
+ {
+ hr = storage->Delete(&service, 1, NULL);
+ storage->Release();
+ }
+ return hr;
+}
+
+HRESULT ServiceHelper_SetFlags(ifc_omservice *service, UINT flags, UINT flagsMask)
+{
+ if (NULL ==service)
+ return E_INVALIDARG;
+
+ UINT serviceFlags;
+ HRESULT hr = service->GetFlags(&serviceFlags);
+ if (FAILED(hr)) return hr;
+
+ if ((flags & flagsMask) == (serviceFlags & flagsMask))
+ return S_FALSE;
+
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ editor->SetFlags(flags, flagsMask);
+ editor->Release();
+ }
+
+ return hr;
+}
+HRESULT ServiceHelper_IsSpecial(ifc_omservice *service)
+{
+ if (NULL == service) return E_INVALIDARG;
+
+ UINT flags;
+ HRESULT hr = service->GetFlags(&flags);
+ if (FAILED(hr)) return hr;
+
+ return (0 != (SVCF_SPECIAL & flags)) ? S_OK : S_FALSE;
+}
+
+HRESULT ServiceHelper_IsSubscribed(ifc_omservice *service)
+{
+ if (NULL == service) return E_INVALIDARG;
+
+ //return S_OK;
+
+ UINT flags;
+ HRESULT hr = service->GetFlags(&flags);
+ if (FAILED(hr)) return hr;
+
+ return (0 != (SVCF_SUBSCRIBED & flags)) ? S_OK : S_FALSE;
+}
+
+HRESULT ServiceHelper_IsModified(ifc_omservice *service)
+{
+ if (NULL == service) return E_INVALIDARG;
+
+ HRESULT hr;
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ UINT modified;
+ hr = editor->GetModified(&modified);
+ if (SUCCEEDED(hr))
+ {
+ hr = (0 == modified) ? S_FALSE : S_OK;
+ }
+ editor->Release();
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_MarkModified(ifc_omservice *service, UINT modifiedFlag, UINT modifiedMask)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ HRESULT hr;
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ hr = editor->SetModified(modifiedFlag, modifiedMask);
+ editor->Release();
+ }
+ return hr;
+}
+HRESULT ServiceHelper_Load(ifc_omserviceenum **enumerator)
+{
+ if (NULL == enumerator)
+ return E_POINTER;
+
+ HRESULT hr;
+ ifc_omstorage *storage;
+ hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ hr = storage->Load(L"*.ini", serviceHost, enumerator);
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+
+ storage->Release();
+ }
+
+ if (FAILED(hr))
+ *enumerator = NULL;
+
+ return hr;
+}
+
+HRESULT ServiceHelper_UpdateIcon(ifc_omserviceeditor *editor, LPCWSTR pszImage)
+{
+ WCHAR szBuffer[8192];
+ szBuffer[0] = L'\0';
+
+ if (NULL == editor)
+ return E_INVALIDARG;
+
+ ifc_omservice *service;
+ if (SUCCEEDED(editor->QueryInterface(IFC_OmService, (void**)&service)))
+ {
+ if (FAILED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer))))
+ szBuffer[0] = L'\0';
+ service->Release();
+ }
+
+ HRESULT hr = editor->SetIcon(pszImage, FALSE);
+ if (FAILED(hr) || S_FALSE == hr) return hr;
+
+ if (L'\0' != szBuffer)
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->ReleaseIndex(szBuffer);
+ navHelper->Release();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ServiceHelper_Find(UINT serviceId, ifc_omservice **serviceOut)
+{
+ if (NULL == serviceOut) return E_POINTER;
+ *serviceOut = NULL;
+
+ if (0 == serviceId)
+ return E_INVALIDARG;
+
+
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ HNAVITEM hItem = navigation->FindService(serviceId, serviceOut);
+ navigation->Release();
+ if (NULL != hItem)
+ return S_OK;
+ }
+
+ ifc_omserviceenum *enumerator;
+ HRESULT hr = ServiceHelper_Load(&enumerator);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ hr = S_FALSE;
+ while (S_OK == enumerator->Next(1, &service, NULL))
+ {
+ if (service->GetId() == serviceId)
+ {
+ *serviceOut = service;
+ hr = S_OK;
+ break;
+ }
+ service->Release();
+ }
+ enumerator->Release();
+ }
+ return hr;
+}
+
+HRESULT ServiceHelper_SetRating(ifc_omservice *service, UINT rating, UINT flags)
+{
+ if (NULL == service)
+ return E_POINTER;
+
+ ifc_omserviceeditor *editor;
+ HRESULT hr;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ hr = editor->SetRating(rating);
+ if (S_OK == hr)
+ {
+ if (0 != (SHF_NOTIFY & flags))
+ {
+ // send notification
+
+ WCHAR szUrl[2048] = {0}, szClient[128] = {0};
+ if (NULL == OMBROWSERMNGR || FAILED(OMBROWSERMNGR->GetClientId(szClient, ARRAYSIZE(szClient))))
+ szClient[0] = L'\0';
+
+ hr = StringCchPrintf(szUrl, ARRAYSIZE(szUrl),
+ L"http://services.winamp.com/svc/rating?svc_id=%u&unique_id=%s&rating=%d",
+ service->GetId(), szClient, rating*2);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = ServiceHelper_PostNotificationUrl(szUrl);
+ }
+
+ }
+
+ if (0 != (SHF_SAVE & flags))
+ ServiceHelper_Save(service);
+
+ }
+ editor->Release();
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_PostNotificationUrl(LPCWSTR pszUrl)
+{
+ HRESULT hr;
+ ifc_omstorage *storage;
+ hr = ServiceHelper_QueryWebStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorageasync *async;
+ hr = storage->BeginLoad(pszUrl, NULL, NULL, NULL, &async);
+ if(SUCCEEDED(hr))
+ {
+ async->Release();
+ }
+ storage->Release();
+ }
+
+ return hr;
+}
+HRESULT ServiceHelper_ResetPermissions(ifc_omservice *service, UINT flags)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (0 != (SHF_VERBAL & flags))
+ {
+ WCHAR szText[1024] = {0}, szFormat[512] = {0}, szName[128] = {0};
+
+ service->GetName(szName, ARRAYSIZE(szName));
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGEBOX_POLICYRESET, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szText, ARRAYSIZE(szText), szFormat, szName);
+
+ if (IDNO == Plugin_MessageBox(szText, MAKEINTRESOURCE(IDS_MESSAGEBOX_POLICYRESET_CAPTION),
+ MB_ICONQUESTION | MB_YESNO))
+ {
+ return S_FALSE;
+ }
+ }
+
+ if (NULL == AGAVE_API_JSAPI2_SECURITY)
+ return E_UNEXPECTED;
+
+ WCHAR szBuffer[64] = {0};
+ if (FAILED(StringCchPrintfW(szBuffer, ARRAYSIZE(szBuffer), L"%u", service->GetId())))
+ return E_FAIL;
+
+ AGAVE_API_JSAPI2_SECURITY->ResetAuthorization(szBuffer);
+ return S_OK;
+}
+
+HRESULT ServiceHelper_IsPreAuthorized(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ UINT serviceFlags;
+ HRESULT hr = service->GetFlags(&serviceFlags);
+ if (SUCCEEDED(hr))
+ {
+ hr = (0 != (SVCF_PREAUTHORIZED & serviceFlags)) ? S_OK : S_FALSE;
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_Subscribe(ifc_omservice *service, BOOL subscribe, UINT flags)
+{
+ if (NULL == service)
+ return E_POINTER;
+
+ if ((TRUE == subscribe) == (S_OK == ServiceHelper_IsSubscribed(service)))
+ return S_FALSE;
+
+ HRESULT hr;
+
+ if (FALSE == subscribe && 0 != (SHF_VERBAL & flags))
+ {
+ WCHAR szText[1024] = {0}, szFormat[512] = {0}, szName[128] = {0};
+ if (FAILED(service->GetName(szName, ARRAYSIZE(szName))))
+ szName[0] = L'\0';
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGEBOX_UNSUBSCRIBE, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szText,ARRAYSIZE(szText), szFormat, szName);
+
+ if (IDNO == Plugin_MessageBox(szText, MAKEINTRESOURCE(IDS_MESSAGEBOX_UNSUBSCRIBE_CAPTION),
+ MB_ICONQUESTION | MB_YESNO))
+ {
+ return S_FALSE;
+ }
+ }
+
+ ifc_omserviceeditor *editor = NULL;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr) && editor)
+ {
+ hr = editor->SetFlags((FALSE == subscribe) ? 0 : SVCF_SUBSCRIBED, SVCF_SUBSCRIBED);
+ if (S_OK == hr)
+ {
+ if (0 != (SHF_SAVE & flags))
+ ServiceHelper_Save(service);
+ }
+ editor->Release();
+
+ if (FALSE == subscribe)
+ ServiceHelper_Delete(service, 0);
+ }
+
+ if (S_OK == hr)
+ {
+ if (NULL == AGAVE_API_JSAPI2_SECURITY)
+ {
+ WCHAR szBuffer[64] = {0};
+ if (SUCCEEDED(StringCchPrintfW(szBuffer, ARRAYSIZE(szBuffer), L"%u", service->GetId())))
+ {
+ AGAVE_API_JSAPI2_SECURITY->ResetAuthorization(szBuffer);
+ }
+ }
+
+ if (0 != (SHF_NOTIFY & flags))
+ {
+ LPWSTR pszUrl;
+ UINT serviceId = service->GetId();
+ LPCWSTR action = (FALSE != subscribe) ? L"add" : L"remove";;
+ if (NULL != action && SUCCEEDED(Plugin_BuildActionUrl(&pszUrl, action, &serviceId, 1)))
+ {
+ hr = ServiceHelper_PostNotificationUrl(pszUrl);
+ Plugin_FreeString(pszUrl);
+ }
+ }
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_ResetSubscription(UINT flags)
+{
+ if (S_OK == ServiceHelper_IsDiscovering())
+ {
+ if (0 != (SHF_VERBAL & flags))
+ {
+ Plugin_MessageBox(MAKEINTRESOURCE(IDS_MESSAGEBOX_DISCOVERBUSY),
+ MAKEINTRESOURCE(IDS_MESSAGEBOX_DISCOVERBUSY_CAPTION),
+ MB_ICONASTERISK | MB_OK);
+ }
+
+ return E_PENDING;
+ }
+
+ if (0 != (SHF_VERBAL & flags))
+ {
+ if (IDNO == Plugin_MessageBox(MAKEINTRESOURCE(IDS_MESSAGEBOX_RESETTODEFAULT),
+ MAKEINTRESOURCE(IDS_MESSAGEBOX_RESETTODEFAULT_CAPTION),
+ MB_ICONQUESTION | MB_YESNO))
+ {
+ return S_FALSE;
+ }
+ }
+
+ HRESULT hr;
+
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ navigation->DeleteAll();
+ navigation->Release();
+ }
+
+ ifc_omstorage *storage;
+ hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceenum *enumerator;
+ hr = ServiceHelper_Load(&enumerator);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ std::vector<UINT> removeList;
+ while(S_OK == enumerator->Next(1, &service, NULL))
+ {
+ if (S_OK == ServiceHelper_IsSubscribed(service))
+ removeList.push_back(service->GetId());
+
+ ServiceHelper_ResetPermissions(service, 0);
+ storage->Delete(&service, 1, NULL);
+ service->Release();
+ }
+ enumerator->Release();
+
+ if (0 != removeList.size())
+ {
+ LPWSTR pszUrl;
+ if (SUCCEEDED(Plugin_BuildActionUrl(&pszUrl, L"remove", removeList.data(), removeList.size())))
+ {
+ ServiceHelper_PostNotificationUrl(pszUrl);
+ Plugin_FreeString(pszUrl);
+ }
+ }
+ }
+ storage->Release();
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // discover new services
+ hr = ServiceHelper_BeginDiscover(L"http://services.winamp.com/svc/default.php");
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_IsDiscovering()
+{
+ HRESULT hr;
+ ServiceHelper_Lock();
+
+ if (NULL == serviceHelper)
+ {
+ hr = E_UNEXPECTED;
+ }
+ else
+ {
+ hr = (NULL != serviceHelper->discoverAsync) ? S_OK : S_FALSE;
+ }
+
+ ServiceHelper_Unlock();
+
+ return hr;
+}
+
+static void CALLBACK ServiceHelper_DiscoverComplete(ifc_omstorageasync *result)
+{
+ if (NULL == result)
+ return;
+
+ SERVICEHELPER *helper;
+ if (FAILED(result->GetData((void**)&helper)) || NULL == helper)
+ return;
+
+ ServiceHelper_Lock();
+
+ ifc_omstorage *webStorage;
+ HRESULT hr = ServiceHelper_QueryWebStorage(&webStorage);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceenum *serviceEnum;
+ if (SUCCEEDED(webStorage->EndLoad(result, &serviceEnum)))
+ {
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ navigation = NULL;
+
+ ifc_omstorage *localStorage;
+ if (SUCCEEDED(ServiceHelper_QueryStorage(&localStorage)))
+ {
+ std::vector<UINT> registerList;
+
+ ifc_omservice *service;
+ while(S_OK == serviceEnum->Next(1, &service, NULL))
+ {
+ ifc_omserviceeditor *editor;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEditor, (void**)&editor)))
+ {
+ if (SUCCEEDED(editor->SetFlags(SVCF_SUBSCRIBED, SVCF_SUBSCRIBED)))
+ registerList.push_back(service->GetId());
+
+ editor->Release();
+ }
+
+ if (SUCCEEDED(localStorage->Save(&service, 1, ifc_omstorage::saveClearModified, NULL)))
+ {
+ navigation->CreateItemAsync(service);
+ }
+
+ service->Release();
+ }
+
+ if (0 != registerList.size())
+ {
+ LPWSTR pszUrl;
+ if (SUCCEEDED(Plugin_BuildActionUrl(&pszUrl, L"add", registerList.data(), registerList.size())))
+ {
+ ServiceHelper_PostNotificationUrl(pszUrl);
+ Plugin_FreeString(pszUrl);
+ }
+ }
+ }
+
+ if (NULL != navigation)
+ navigation->Release();
+
+ serviceEnum->Release();
+ }
+ webStorage->Release();
+ }
+
+
+ if (serviceHelper->discoverAsync == result)
+ {
+ serviceHelper->discoverAsync->Release();
+ serviceHelper->discoverAsync = NULL;
+ }
+
+ ServiceHelper_Unlock();
+}
+
+HRESULT ServiceHelper_BeginDiscover(LPCWSTR address)
+{
+ ifc_omstorage *storage;
+ HRESULT hr = ServiceHelper_QueryWebStorage(&storage);
+ if (FAILED(hr)) return hr;
+
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ ServiceHelper_Lock();
+
+ if (NULL == serviceHelper->discoverAsync)
+ hr = storage->BeginLoad(address, serviceHost, ServiceHelper_DiscoverComplete, serviceHelper, &serviceHelper->discoverAsync);
+ else
+ hr = E_PENDING;
+
+ ServiceHelper_Unlock();
+
+ storage->Release();
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+
+ return hr;
+}
+HRESULT ServiceHelper_GetDetailsUrl(LPWSTR pszBuffer, UINT cchBufferMax, ifc_omservice *service, BOOL fLite)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ if (0 == service)
+ return E_INVALIDARG;
+
+ HRESULT hr;
+ size_t remaining = cchBufferMax;
+ LPWSTR cursor = pszBuffer;
+
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"http://services.winamp.com/svc/details?svc_id=%u", service->GetId());
+
+ WCHAR szClient[128] = {0};
+ if (SUCCEEDED(hr) &&
+ NULL != OMBROWSERMNGR &&
+ SUCCEEDED(OMBROWSERMNGR->GetClientId(szClient, ARRAYSIZE(szClient))) &&
+ L'\0' != szClient[0])
+ {
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"&unique_id=%s", szClient);
+ }
+
+ if (FALSE != fLite && SUCCEEDED(hr))
+ {
+ hr = StringCchCopyEx(cursor, remaining, L"&detail=lite", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ return hr;
+}
+static void CALLBACK ServiceHelper_VersionCheckComplete(ifc_omstorageasync *result)
+{
+ if (NULL == result)
+ return;
+
+ ifc_omservice *target;
+ if (FAILED(result->GetData((void**)&target)) || NULL == target)
+ return;
+
+ ServiceHelper_Lock();
+
+ size_t index = serviceHelper->versionChecks.size();
+ while(index--)
+ {
+ if (result == serviceHelper->versionChecks[index])
+ {
+ auto it = serviceHelper->versionChecks.begin() + index;
+ if (it < serviceHelper->versionChecks.end())
+ {
+ it = serviceHelper->versionChecks.erase(it);
+ }
+ break;
+ }
+ }
+
+ ServiceHelper_Unlock();
+
+ UINT originalFlags;
+ if (FAILED(target->GetFlags(&originalFlags)))
+ originalFlags = 0;
+
+ ifc_omserviceeditor *editor;
+ if (SUCCEEDED(target->QueryInterface(IFC_OmServiceEditor, (void**)&editor)))
+ editor->BeginUpdate();
+ else
+ editor = NULL;
+
+
+ ifc_omstorage *storage;
+ HRESULT hr = ServiceHelper_QueryWebStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceenum *serviceEnum;
+ if (SUCCEEDED(storage->EndLoad(result, &serviceEnum)))
+ {
+ ifc_omservice *source = NULL;
+ while(S_OK == serviceEnum->Next(1, &source, NULL))
+ {
+ if (source->GetId() == target->GetId())
+ break;
+
+ source->Release();
+ source = NULL;
+ }
+
+ ifc_omxmlserviceenum *xmlServiceEnum;
+ if (SUCCEEDED(serviceEnum->QueryInterface(IFC_OmXmlServiceEnum, (void**)&xmlServiceEnum)))
+ {
+ UINT statusCode;
+ if (SUCCEEDED(xmlServiceEnum->GetStatusCode(&statusCode)))
+ {
+ switch(statusCode)
+ {
+ case 410: // force remove
+ if (NULL != target)
+ {
+ WCHAR szText[1024] = {0}, szFormat[512] = {0}, szName[128] = {0};
+ target->GetName(szName, ARRAYSIZE(szName));
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGEBOX_FORCEREMOVE, szFormat, ARRAYSIZE(szFormat));
+ StringCchPrintf(szText,ARRAYSIZE(szText), szFormat, szName);
+ Plugin_MessageBox(szText, MAKEINTRESOURCE(IDS_MESSAGEBOX_FORCEREMOVE_CAPTION), MB_ICONINFORMATION | MB_OK);
+ ServiceHelper_Delete(target, 0);
+ target->Release();
+ target = NULL;
+ }
+ break;
+
+ case 200:
+ if (NULL != source)
+ {
+ ifc_omservicecopier *copier;
+ if (SUCCEEDED(source->QueryInterface(IFC_OmServiceCopier, (void**)&copier)))
+ {
+ UINT targetFlags, sourceFlags;
+ if (FAILED(target->GetFlags(&targetFlags))) targetFlags = 0;
+ if (SUCCEEDED(source->GetFlags(&sourceFlags)))
+ targetFlags |= sourceFlags;
+
+ copier->CopyTo(target, NULL);
+ copier->Release();
+
+ editor->SetFlags(targetFlags, targetFlags);
+ }
+
+ }
+ break;
+ }
+ }
+ xmlServiceEnum->Release();
+ }
+
+ if (NULL != source)
+ source->Release();
+
+ serviceEnum->Release();
+ }
+ storage->Release();
+ }
+
+ if (NULL != target && NULL != AGAVE_API_JSAPI2_SECURITY)
+ {
+ WCHAR szBuffer[64] = {0};
+ if (SUCCEEDED(StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), L"%u", target->GetId())))
+ {
+ UINT flags = 0;
+ if (SUCCEEDED(target->GetFlags(&flags)))
+ {
+ bool bypassEnabled = (0 != (SVCF_PREAUTHORIZED & flags));
+ AGAVE_API_JSAPI2_SECURITY->SetBypass(szBuffer, bypassEnabled);
+ }
+ }
+ }
+
+ if (NULL != editor)
+ {
+ editor->SetFlags(SVCF_VALIDATED, SVCF_VALIDATED | SVCF_VERSIONCHECK);
+
+ if (NULL != target)
+ {
+ UINT targetFlags;
+ if (FAILED(target->GetFlags(&targetFlags)))
+ targetFlags = 0;
+
+ if ((targetFlags & ~ifc_omservice::RuntimeFlagsMask) == (originalFlags & ~ifc_omservice::RuntimeFlagsMask))
+ editor->SetModified(0, ifc_omserviceeditor::modifiedFlags);
+ }
+ editor->EndUpdate();
+ editor->Release();
+ }
+
+ if (NULL != target)
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(target->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->Signal_CommandStateChange(&CMDGROUP_SERVICE, SVCCOMMAND_BLOCKNAV);
+ eventManager->Release();
+ }
+
+ ServiceHelper_Save(target);
+ target->Release();
+ }
+
+}
+
+HRESULT ServiceHelper_BeginVersionCheck(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == serviceHelper)
+ return E_UNEXPECTED;
+
+ HRESULT hr = S_OK;
+ ServiceHelper_Lock();
+ size_t index = serviceHelper->versionChecks.size();
+ while(index--)
+ {
+ ifc_omstorageasync *async = serviceHelper->versionChecks[index];
+ UINT serviceId;
+ if (SUCCEEDED(async->GetData((void**)&serviceId)))
+ {
+ if (serviceId == service->GetId())
+ {
+ hr = S_FALSE;
+ }
+ }
+ }
+
+ if (S_OK == hr)
+ {
+ ifc_omstorage *storage;
+ HRESULT hr = ServiceHelper_QueryWebStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ WCHAR szAddress[2048] = {0};
+ hr = ServiceHelper_GetDetailsUrl(szAddress, ARRAYSIZE(szAddress), service, TRUE);
+ if (SUCCEEDED(hr))
+ {
+ ServiceHost *serviceHost;
+ if (FAILED(ServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ ifc_omserviceeditor *editor;
+ if (FAILED(service->QueryInterface(IFC_OmServiceEditor, (void**)&editor)))
+ editor = NULL;
+ else
+ {
+ editor->BeginUpdate();
+ editor->SetFlags(SVCF_VERSIONCHECK, SVCF_VERSIONCHECK);
+ }
+ service->AddRef();
+ serviceHelper->versionChecks.push_back(NULL);
+ //ifc_omstorageasync **pAsync = serviceHelper->versionChecks.end() - 1;
+ ifc_omstorageasync** pAsync = (serviceHelper->versionChecks.size() ?
+ &serviceHelper->versionChecks.at(serviceHelper->versionChecks.size() - 1) : 0);
+ hr = storage->BeginLoad(szAddress, serviceHost, ServiceHelper_VersionCheckComplete, service, pAsync);
+ if (FAILED(hr))
+ {
+ service->Release();
+ serviceHelper->versionChecks.pop_back();
+ if (NULL != editor)
+ editor->SetFlags(0, SVCF_VERSIONCHECK);
+ }
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+
+ if (NULL != editor)
+ {
+ editor->EndUpdate();
+ editor->Release();
+ }
+ }
+ storage->Release();
+ }
+
+ }
+
+ ServiceHelper_Unlock();
+
+ return hr;
+}
+static void CALLBACK ServiceHelper_UpdateOperationInfoApc(ULONG_PTR param)
+{
+ HWND hBrowser = (HWND)param;
+ if (NULL != hBrowser && IsWindow(hBrowser))
+ {
+ ServiceHelper_UpdateOperationInfo(hBrowser);
+ }
+}
+
+HRESULT ServiceHelper_UpdateOperationInfo(HWND hBrowser)
+{
+ if (NULL == hBrowser)
+ return E_INVALIDARG;
+
+ DWORD currentTID = GetCurrentThreadId();
+ DWORD winampTID = GetWindowThreadProcessId(Plugin_GetWinamp(), NULL);
+ if (NULL != winampTID && winampTID != currentTID)
+ {
+ HRESULT hr;
+ if (NULL != OMUTILITY)
+ hr = OMUTILITY->PostMainThreadCallback(ServiceHelper_UpdateOperationInfoApc, (ULONG_PTR)hBrowser);
+ else
+ hr = E_FAIL;
+ return hr;
+ }
+
+ ifc_omservice *service;
+ if (FALSE == BrowserControl_GetService(hBrowser, &service))
+ return E_FAIL;
+
+ UINT flags;
+ if (SUCCEEDED(service->GetFlags(&flags)))
+ {
+ WCHAR szText[128] = {0}, szTitle[128] = {0};
+ OPERATIONINFO operation = {0};
+ operation.cbSize = sizeof(operation);
+ operation.mask = NBCOM_FLAGS;
+
+ if (0 == (SVCF_VERSIONCHECK & flags))
+ {
+ operation.flags = NBCOF_HIDEWIDGET;
+ }
+ else
+ {
+ operation.mask |= (NBCOM_TITLE | NBCOM_TEXT);
+ operation.flags = NBCOF_SHOWWIDGET;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_SERVICE_CHECKINGVERSION, szTitle, ARRAYSIZE(szTitle));
+ operation.title = szTitle;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PLEASE_WAIT, szText, ARRAYSIZE(szText));
+ operation.text = szText;
+ }
+ BrowserControl_ShowOperation(hBrowser, &operation);
+ }
+ service->Release();
+
+ return S_OK;
+}
+static void CALLBACK ServiceHelper_ShowWindowTimer(UINT_PTR eventId, DWORD elapsedMs, ULONG_PTR data)
+{
+ Plugin_KillTimer(eventId);
+ HWND hTarget = (HWND)data;
+
+ if (hTarget == Plugin_GetLibrary())
+ PostMessage(hTarget, WM_ML_IPC, 0, ML_IPC_ENSURE_VISIBLE);
+ else
+ ShowWindow(hTarget, SW_SHOWNORMAL);
+}
+
+HRESULT ServiceHelper_ShowService(UINT serviceId, UINT showMode)
+{
+ HRESULT hr;
+
+ BOOL serviceFound = FALSE;
+ ifc_omservice *service;
+ if (0 != serviceId && ROOTSERVICE_ID != serviceId &&
+ S_OK == ServiceHelper_Find(serviceId, &service) &&
+ NULL != service)
+ {
+ if (S_OK == ServiceHelper_IsSubscribed(service))
+ serviceFound = TRUE;
+
+ service->Release();
+ }
+
+ WCHAR szBuffer[INTERNET_MAX_URL_LENGTH] = {0};
+ LPCWSTR pszUrl = NULL;
+
+ if (FALSE == serviceFound)
+ {
+ if (0 != serviceId && ROOTSERVICE_ID != serviceId &&
+ SUCCEEDED(StringCchPrintfW(szBuffer, ARRAYSIZE(szBuffer),
+ L"http://client.winamp.com/service/detail/gallery/%u", serviceId)))
+ {
+ pszUrl = szBuffer;
+ }
+ serviceId = ROOTSERVICE_ID;
+ }
+
+ Navigation *navigation;
+ hr = Plugin_GetNavigation(&navigation);
+ if (FAILED(hr)) return hr;
+
+ if (SHOWMODE_POPUP == showMode)
+ {
+ showMode = SHOWMODE_ENSUREVISIBLE;
+ if (FALSE != serviceFound)
+ {
+ HNAVITEM hItem = navigation->FindService(serviceId, NULL);
+ if (NULL != hItem)
+ {
+ HWND hPopup;
+ hr = navigation->CreatePopup(hItem, &hPopup);
+ if (SUCCEEDED(hr))
+ {
+ Plugin_SetTimer(100, ServiceHelper_ShowWindowTimer, (ULONG_PTR)hPopup);
+ showMode = SHOWMODE_POPUP;
+ }
+ }
+ }
+ }
+
+ if (SHOWMODE_POPUP != showMode)
+ {
+ hr = navigation->ShowService(serviceId, pszUrl);
+ if (SUCCEEDED(hr))
+ {
+ if (SHOWMODE_ENSUREVISIBLE == showMode)
+ Plugin_SetTimer(100, ServiceHelper_ShowWindowTimer, (ULONG_PTR)Plugin_GetLibrary());
+ }
+ }
+
+ navigation->Release();
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/serviceHelper.h b/Src/Plugins/Library/ml_online/serviceHelper.h
new file mode 100644
index 00000000..fe09a685
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/serviceHelper.h
@@ -0,0 +1,57 @@
+#ifndef NULLSOFT_ONLINEMEDIA_PLUGIN_SERVICE_HELPER_HEADER
+#define NULLSOFT_ONLINEMEDIA_PLUGIN_SERVICE_HELPER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ifc_omservice;
+class ifc_omserviceenum;
+class ifc_omserviceeditor;
+class ifc_omstorage;
+
+#include <ifc_omstorageasync.h>
+
+#define SHF_VERBAL 0x00000001
+#define SHF_NOTIFY 0x00000002
+#define SHF_SAVE 0x00000004
+
+HRESULT ServiceHelper_Initialize();
+
+HRESULT ServiceHelper_QueryStorage(ifc_omstorage **storage);
+HRESULT ServiceHelper_QueryWebStorage(ifc_omstorage **storage);
+HRESULT ServiceHelper_Load(ifc_omserviceenum **enumerator);
+
+HRESULT ServiceHelper_Create(UINT serviceId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, UINT flags, UINT generation, BOOL fSave, ifc_omservice **serviceOut);
+HRESULT ServiceHelper_Save(ifc_omservice *service);
+HRESULT ServiceHelper_Delete(ifc_omservice *service, UINT flags);
+HRESULT ServiceHelper_UpdateIcon(ifc_omserviceeditor *editor, LPCWSTR pszImage);
+HRESULT ServiceHelper_Find(UINT serviceId, ifc_omservice **serviceOut);
+
+HRESULT ServiceHelper_SetFlags(ifc_omservice *service, UINT flags, UINT flagsMask);
+HRESULT ServiceHelper_IsSpecial(ifc_omservice *service);
+HRESULT ServiceHelper_IsPreAuthorized(ifc_omservice *service);
+HRESULT ServiceHelper_IsSubscribed(ifc_omservice *service);
+HRESULT ServiceHelper_IsModified(ifc_omservice *service);
+HRESULT ServiceHelper_MarkModified(ifc_omservice *service, UINT modifiedFlag, UINT modifiedMask);
+
+HRESULT ServiceHelper_SetRating(ifc_omservice *service, UINT rating, UINT flags);
+HRESULT ServiceHelper_Subscribe(ifc_omservice *service, BOOL subscribe, UINT flags);
+HRESULT ServiceHelper_ResetPermissions(ifc_omservice *service, UINT flags);
+HRESULT ServiceHelper_ResetSubscription(UINT flags);
+HRESULT ServiceHelper_BeginDiscover(LPCWSTR address);
+HRESULT ServiceHelper_IsDiscovering();
+HRESULT ServiceHelper_BeginVersionCheck(ifc_omservice *service);
+
+HRESULT ServiceHelper_GetDetailsUrl(LPWSTR pszBuffer, UINT cchBufferMax, ifc_omservice *service, BOOL fLite);
+HRESULT ServiceHelper_PostNotificationUrl(LPCWSTR pszUrl);
+HRESULT ServiceHelper_UpdateOperationInfo(HWND hBrowser);
+
+#define SHOWMODE_NORMAL 0
+#define SHOWMODE_ENSUREVISIBLE 1
+#define SHOWMODE_POPUP 2
+HRESULT ServiceHelper_ShowService(UINT serviceId, UINT showMode);
+
+#endif //NULLSOFT_ONLINEMEDIA_PLUGIN_SERVICE_HELPER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/serviceHost.cpp b/Src/Plugins/Library/ml_online/serviceHost.cpp
new file mode 100644
index 00000000..e907e345
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/serviceHost.cpp
@@ -0,0 +1,394 @@
+#include "main.h"
+#include "./serviceHost.h"
+#include "./api__ml_online.h"
+#include "./resource.h"
+#include "./external.h"
+#include "./navigation.h"
+#include "./commands.h"
+#include "./serviceHelper.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+#include <ifc_omservicecommand.h>
+
+#include <ifc_omstoragehelper.h>
+#include <ifc_omstoragehandlerenum.h>
+#include <ifc_omfilestorage.h>
+
+#include "../winamp/IWasabiDispatchable.h"
+#include "../winamp/JSAPI_Info.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+#define IS_INVALIDISPATCH(__disp) (((IDispatch *)1) == (__disp) || NULL == (__disp))
+
+static ServiceHost *cachedInstance = NULL;
+
+static void CALLBACK StorageHandler_ReadAuth(ifc_omservice *service, LPCWSTR pszKey, LPCWSTR pszValue)
+{
+ INT iVal;
+ UINT flags = (NULL != pszValue &&
+ FALSE != StrToIntEx(pszValue, STIF_SUPPORT_HEX, &iVal) &&
+ 0 != iVal) ?
+ SVCF_USECLIENTOWEB : 0;
+
+ ServiceHelper_SetFlags(service, flags, SVCF_USECLIENTOWEB);
+}
+
+
+static void CALLBACK StorageHandler_ReadBypass(ifc_omservice *service, LPCWSTR pszKey, LPCWSTR pszValue)
+{
+ INT iVal;
+ UINT flags = (NULL != pszValue &&
+ FALSE != StrToIntEx(pszValue, STIF_SUPPORT_HEX, &iVal) &&
+ 0 != iVal) ?
+ SVCF_PREAUTHORIZED : 0;
+
+ ServiceHelper_SetFlags(service, flags, SVCF_PREAUTHORIZED);
+
+}
+
+static void CALLBACK StorageHandler_ReadSubscribed(ifc_omservice *service, LPCWSTR pszKey, LPCWSTR pszValue)
+{
+ INT iVal;
+ UINT flags = (NULL != pszValue &&
+ FALSE != StrToIntEx(pszValue, STIF_SUPPORT_HEX, &iVal) &&
+ 0 != iVal) ?
+ SVCF_SUBSCRIBED : 0;
+
+ flags |= SVCF_AUTOUPGRADE;
+ ServiceHelper_SetFlags(service, flags, SVCF_SUBSCRIBED | SVCF_AUTOUPGRADE);
+
+}
+
+static const ifc_omstoragehelper::TemplateRecord szStorageExtXml[] =
+{
+ { L"auth", StorageHandler_ReadAuth },
+ { L"bypass", StorageHandler_ReadBypass },
+};
+
+static const ifc_omstoragehelper::TemplateRecord szStorageExtIni[] =
+{
+ { L"subscribed", StorageHandler_ReadSubscribed },
+};
+
+ServiceHost::ServiceHost()
+ : ref(1), storageExtXml(NULL), storageExtIni(NULL)
+{
+
+}
+
+ServiceHost::~ServiceHost()
+{
+ if (NULL != storageExtXml)
+ storageExtXml->Release();
+
+ if (NULL != storageExtIni)
+ storageExtIni->Release();
+}
+
+HRESULT ServiceHost::CreateInstance(ServiceHost **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new ServiceHost();
+ if (NULL == instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+HRESULT ServiceHost::GetCachedInstance(ServiceHost **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+
+ if (NULL == cachedInstance)
+ {
+ HRESULT hr = CreateInstance(&cachedInstance);
+ if (FAILED(hr))
+ {
+ *instance = NULL;
+ return hr;
+ }
+ }
+
+ cachedInstance->AddRef();
+ *instance = cachedInstance;
+ return S_OK;
+}
+
+HRESULT ServiceHost::ReleseCache()
+{
+ if (NULL == cachedInstance)
+ return S_FALSE;
+
+ ServiceHost *t = cachedInstance;
+ cachedInstance = NULL;
+ t->Release();
+
+ return S_OK;
+}
+
+size_t ServiceHost::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t ServiceHost::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int ServiceHost::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_OmServiceHost))
+ *object = static_cast<ifc_omservicehost*>(this);
+ else if (IsEqualIID(interface_guid, IFC_OmServiceEvent))
+ *object = static_cast<ifc_omserviceevent*>(this);
+ else if (IsEqualIID(interface_guid, IFC_OmStorageExt))
+ *object = static_cast<ifc_omstorageext*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT ServiceHost::GetExternal(ifc_omservice *service, IDispatch **ppDispatch)
+{
+ if (NULL == ppDispatch)
+ return E_POINTER;
+
+ if (NULL != *ppDispatch)
+ {
+ // try to connect our external
+ IWasabiDispatchable *pWasabi;
+ if (SUCCEEDED((*ppDispatch)->QueryInterface(IID_IWasabiDispatchable, (void**)&pWasabi)))
+ {
+ JSAPI::ifc_info *pInfo;
+ if (SUCCEEDED(pWasabi->QueryDispatchable(JSAPI::IID_JSAPI_ifc_info, (Dispatchable**)&pInfo)))
+ {
+ ExternalDispatch *pExternal;
+ if (SUCCEEDED(ExternalDispatch::CreateInstance(&pExternal)))
+ {
+ pInfo->AddAPI(pExternal->GetName(), pExternal);
+ pExternal->Release();
+ }
+ pInfo->Release();
+ }
+ pWasabi->Release();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ServiceHost::GetBasePath(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ return StringCchCopy(pszBuffer, cchBufferMax, L".\\Plugins\\ml\\omServices");
+}
+
+HRESULT ServiceHost::GetDefaultName(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ return StringCchPrintf(pszBuffer, cchBufferMax, L"omService_{%010u}.ini", service->GetId());
+}
+
+HRESULT ServiceHost::QueryCommandState(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId)
+{
+ if (NULL == service || NULL == commandGroup)
+ return E_NOTIMPL;
+
+ if (IsEqualGUID(*commandGroup, CMDGROUP_SERVICE))
+ {
+ switch(commandId)
+ {
+ case SVCCOMMAND_SHOWINFO:
+ case SVCCOMMAND_REPORT:
+ case SVCCOMMAND_UNSUBSCRIBE:
+ case SVCCOMMAND_RATE:
+ if (S_FALSE == ServiceHelper_IsSpecial(service))
+ {
+ return CMDSTATE_ENABLED;
+ }
+ return CMDSTATE_UNKNOWN;
+
+ case SVCCOMMAND_BLOCKNAV:
+ {
+ UINT flags;
+ if (FAILED(service->GetFlags(&flags)))
+ flags = 0;
+
+ HRESULT state = (0 == (SVCF_VERSIONCHECK & flags)) ?
+ CMDSTATE_DISABLED : CMDSTATE_ENABLED;
+
+ ServiceHelper_UpdateOperationInfo(hBrowser);
+ return state;
+ }
+ break;
+ }
+ }
+ return E_NOTIMPL;
+}
+
+HRESULT ServiceHost::ExecuteCommand(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId, ULONG_PTR commandArg)
+{
+ if (IsEqualGUID(CMDGROUP_SERVICE, *commandGroup))
+ {
+ if (S_OK != ServiceHelper_IsSpecial(service))
+ {
+ switch(commandId)
+ {
+ case SVCCOMMAND_SHOWINFO: Command_ShowServiceInfo(service); return S_OK;
+ case SVCCOMMAND_REPORT: Command_ReportService(service); return S_OK;
+ case SVCCOMMAND_UNSUBSCRIBE: Command_UnsubscribeService(service); return S_OK;
+ case SVCCOMMAND_RATE: Command_SetServiceRating(service, (UINT)commandArg); return S_OK;
+ }
+ }
+ }
+
+ return E_NOTIMPL;
+}
+
+void ServiceHost::ServiceChange(ifc_omservice *service, UINT nModified)
+{
+ if (NULL == service) return;
+
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ navigation->UpdateService(service, nModified);
+ navigation->Release();
+ }
+
+}
+
+HRESULT ServiceHost::EnumerateStorageExt(const GUID *storageId, ifc_omstoragehandlerenum **enumerator)
+{
+ if (NULL == storageId)
+ return E_INVALIDARG;
+
+ if (IsEqualGUID(SUID_OmStorageXml, *storageId))
+ {
+ if (NULL == storageExtXml)
+ {
+ ifc_omstoragehelper *storageHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetStorageHelper(&storageHelper)))
+ {
+ if (FAILED(storageHelper->CreateEnumerator(szStorageExtXml, ARRAYSIZE(szStorageExtXml), &storageExtXml)))
+ storageExtXml = NULL;
+ storageHelper->Release();
+
+ }
+
+ if (NULL == storageExtXml)
+ return E_FAIL;
+ }
+
+ *enumerator = storageExtXml;
+ storageExtXml->AddRef();
+ return S_OK;
+ }
+ else if (IsEqualGUID(SUID_OmStorageIni, *storageId))
+ {
+ if (NULL == storageExtIni)
+ {
+ ifc_omstoragehelper *storageHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetStorageHelper(&storageHelper)))
+ {
+ if (FAILED(storageHelper->CreateEnumerator(szStorageExtIni, ARRAYSIZE(szStorageExtIni), &storageExtIni)))
+ storageExtIni = NULL;
+ storageHelper->Release();
+
+ }
+
+ if (NULL == storageExtIni)
+ return E_FAIL;
+ }
+
+ *enumerator = storageExtIni;
+ storageExtIni->AddRef();
+ return S_OK;
+
+ }
+ return E_NOTIMPL;
+}
+
+HRESULT ServiceHost::GetUrl(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ UINT flags;
+ if (NULL != service && SUCCEEDED(service->GetFlags(&flags)) &&
+ 0 != (SVCF_USECLIENTOWEB & flags) && NULL != AGAVE_API_AUTH)
+ {
+ LPWSTR pszUrl = Plugin_CopyString(pszBuffer);
+ if (NULL == pszUrl) return E_OUTOFMEMORY;
+
+ HRESULT hr(E_NOTIMPL);
+
+ if (0 == AGAVE_API_AUTH->ClientToWeb(GUID_NULL, pszUrl, pszBuffer, cchBufferMax))
+ hr = S_OK;
+
+ Plugin_FreeString(pszUrl);
+ return hr;
+ }
+
+ return E_NOTIMPL;
+}
+
+#define CBCLASS ServiceHost
+START_MULTIPATCH;
+ START_PATCH(MPIID_OMSVCHOST)
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, ADDREF, AddRef);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, RELEASE, Release);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, QUERYINTERFACE, QueryInterface);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETEXTERNAL, GetExternal);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETBASEPATH, GetBasePath);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETDEFAULTNAME, GetDefaultName);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_QUERYCOMMANDSTATE, QueryCommandState);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_EXECUTECOMMAND, ExecuteCommand);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETURL, GetUrl);
+
+ NEXT_PATCH(MPIID_OMSVCEVENT)
+ M_CB(MPIID_OMSVCEVENT, ifc_omserviceevent, ADDREF, AddRef);
+ M_CB(MPIID_OMSVCEVENT, ifc_omserviceevent, RELEASE, Release);
+ M_CB(MPIID_OMSVCEVENT, ifc_omserviceevent, QUERYINTERFACE, QueryInterface);
+ M_VCB(MPIID_OMSVCEVENT, ifc_omserviceevent, API_SERVICECHANGE, ServiceChange);
+
+ NEXT_PATCH(MPIID_OMSTRGEXT)
+ M_CB(MPIID_OMSTRGEXT, ifc_omstorageext, ADDREF, AddRef);
+ M_CB(MPIID_OMSTRGEXT, ifc_omstorageext, RELEASE, Release);
+ M_CB(MPIID_OMSTRGEXT, ifc_omstorageext, QUERYINTERFACE, QueryInterface);
+ M_CB(MPIID_OMSTRGEXT, ifc_omstorageext, API_ENUMERATE, EnumerateStorageExt);
+
+
+ END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_online/serviceHost.h b/Src/Plugins/Library/ml_online/serviceHost.h
new file mode 100644
index 00000000..1e292b9f
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/serviceHost.h
@@ -0,0 +1,75 @@
+#ifndef NULLSOFT_ONLINEMEDIA_PLUGIN_SERVICE_HOST_HEADER
+#define NULLSOFT_ONLINEMEDIA_PLUGIN_SERVICE_HOST_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <ifc_omservicehost.h>
+#include <ifc_omserviceevent.h>
+#include <ifc_omstorageext.h>
+#include <bfc/multipatch.h>
+
+#define SVCF_SUBSCRIBED 0x00000001
+
+// runtime flags
+#define SVCF_SPECIAL 0x00010000
+#define SVCF_USECLIENTOWEB 0x00020000
+#define SVCF_VALIDATED 0x00040000
+#define SVCF_VERSIONCHECK 0x00080000
+#define SVCF_PREAUTHORIZED 0x00100000
+#define SVCF_AUTOUPGRADE 0x00200000
+
+#define MPIID_OMSVCHOST 10
+#define MPIID_OMSVCEVENT 20
+#define MPIID_OMSTRGEXT 30
+
+class ifc_omstoragehandlerenum;
+
+class ServiceHost : public MultiPatch<MPIID_OMSVCHOST, ifc_omservicehost>,
+ public MultiPatch<MPIID_OMSVCEVENT, ifc_omserviceevent>,
+ public MultiPatch<MPIID_OMSTRGEXT, ifc_omstorageext>
+{
+
+protected:
+ ServiceHost();
+ ~ServiceHost();
+
+public:
+ static HRESULT CreateInstance(ServiceHost **instance);
+ static HRESULT GetCachedInstance(ServiceHost **instance);
+ static HRESULT ReleseCache();
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_omservicehost */
+ HRESULT GetExternal(ifc_omservice *service, IDispatch **ppDispatch);
+ HRESULT GetBasePath(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT GetDefaultName(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT QueryCommandState(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId);
+ HRESULT ExecuteCommand(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId, ULONG_PTR commandArg);
+ HRESULT GetUrl(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax);
+
+ /* ifc_omsvceventhandler */
+ void ServiceChange(ifc_omservice *service, UINT nModified);
+
+ /* ifc_omstorageext */
+ HRESULT EnumerateStorageExt(const GUID *storageId, ifc_omstoragehandlerenum **enumerator);
+
+protected:
+ ULONG ref;
+ ifc_omstoragehandlerenum *storageExtXml;
+ ifc_omstoragehandlerenum *storageExtIni;
+
+protected:
+ RECVS_MULTIPATCH;
+};
+
+
+
+
+#endif //NULLSOFT_ONLINEMEDIA_PLUGIN_SERVICE_HOST_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/testPages.rc b/Src/Plugins/Library/ml_online/testPages.rc
new file mode 100644
index 00000000..9ea955e3
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/testPages.rc
@@ -0,0 +1,7 @@
+#include "./resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// HTML
+//
+
+WEBDEV.JS HTML ".\\resources\\pages\\webdev.js" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_online/version.rc2 b/Src/Plugins/Library/ml_online/version.rc2
new file mode 100644
index 00000000..dfc40599
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,0,3,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", "2,0,3,0"
+ VALUE "InternalName", "Nullsoft Online Services"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_online.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_online/wasabi.cpp b/Src/Plugins/Library/ml_online/wasabi.cpp
new file mode 100644
index 00000000..e6edf6ae
--- /dev/null
+++ b/Src/Plugins/Library/ml_online/wasabi.cpp
@@ -0,0 +1,144 @@
+#include "main.h"
+#include "./api__ml_online.h"
+#include <api/service/waservicefactory.h>
+#include "../nu/Singleton.h"
+#include "handler.h"
+#include "JSAPI2_Creator.h"
+
+static ULONG wasabiRef = 0;
+
+api_service *WASABI_API_SVC = NULL;
+api_application *WASABI_API_APP = NULL;
+api_config *AGAVE_API_CONFIG = NULL;
+api_language *WASABI_API_LNG = NULL;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = NULL;
+api_memmgr *WASABI_API_MEMMNGR = NULL;
+svc_imageLoader *pngLoaderApi = NULL;
+obj_ombrowser *browserManager = NULL;
+ifc_omservicemanager *serviceManager = NULL;
+ifc_omutility *omUtility = NULL;
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = NULL;
+api_auth *AGAVE_API_AUTH = 0;
+static JSAPI2Factory jsapi2Factory;
+HINSTANCE WASABI_API_LNG_HINST = NULL;
+HINSTANCE WASABI_API_ORIG_HINST = NULL;
+static BOOL fDefaultsLoaded = FALSE;
+static OnlineServicesURIHandler uri_handler;
+static SingletonServiceFactory<svc_urihandler, OnlineServicesURIHandler> uri_handler_factory;
+
+EXTERN_C const GUID pngLoaderGUID =
+{ 0x5e04fb28, 0x53f5, 0x4032, { 0xbd, 0x29, 0x3, 0x2b, 0x87, 0xec, 0x37, 0x25 } };
+
+void *Wasabi_QueryInterface(REFGUID interfaceGuid)
+{
+ waServiceFactory *serviceFactory = WASABI_API_SVC->service_getServiceByGuid(interfaceGuid);
+ return (NULL != serviceFactory) ? serviceFactory->getInterface() : NULL;
+}
+
+void Wasabi_ReleaseInterface(REFGUID interfaceGuid, void *pInstance)
+{
+ if (NULL == pInstance) return;
+ waServiceFactory *serviceFactory = WASABI_API_SVC->service_getServiceByGuid(interfaceGuid);
+ if (NULL != serviceFactory) serviceFactory->releaseInterface(pInstance);
+}
+
+
+HRESULT WasabiApi_Initialize(HINSTANCE hInstance, api_service *serviceApi)
+{
+ if (NULL != WASABI_API_SVC)
+ return S_FALSE;
+
+ fDefaultsLoaded = FALSE;
+
+ WASABI_API_SVC = serviceApi;
+ if ((api_service *)1 == WASABI_API_SVC)
+ WASABI_API_SVC = NULL;
+
+ if (NULL == WASABI_API_SVC)
+ return E_FAIL;
+
+ WASABI_API_ORIG_HINST = hInstance;
+ WASABI_API_LNG_HINST = WASABI_API_ORIG_HINST;
+
+ WasabiApi_AddRef();
+ return S_OK;
+}
+
+HRESULT WasabiApi_LoadDefaults()
+{
+ if (NULL == WASABI_API_SVC)
+ return E_UNEXPECTED;
+
+ if (FALSE != fDefaultsLoaded)
+ return S_FALSE;
+
+ WASABI_API_APP = QueryWasabiInterface(api_application, applicationApiServiceGuid);
+ WASABI_API_LNG = QueryWasabiInterface(api_language, languageApiGUID);
+ WASABI_API_EXPLORERFINDFILE = QueryWasabiInterface(api_explorerfindfile, ExplorerFindFileApiGUID);
+ AGAVE_API_CONFIG = QueryWasabiInterface(api_config, AgaveConfigGUID);
+ AGAVE_API_JSAPI2_SECURITY = QueryWasabiInterface(JSAPI2::api_security, JSAPI2::api_securityGUID);
+ OMBROWSERMNGR = QueryWasabiInterface(obj_ombrowser, OBJ_OmBrowser);
+ OMSERVICEMNGR = QueryWasabiInterface(ifc_omservicemanager, IFC_OmServiceManager);
+ OMUTILITY = QueryWasabiInterface(ifc_omutility, IFC_OmUtility);
+ AGAVE_API_AUTH = QueryWasabiInterface(api_auth, AuthApiGUID);
+
+ if (NULL != WASABI_API_LNG)
+ WASABI_API_LNG_HINST = WASABI_API_LNG->StartLanguageSupport(WASABI_API_ORIG_HINST, MlOnlineLangGUID);
+
+ WASABI_API_SVC->service_register(&jsapi2Factory);
+ uri_handler_factory.Register(WASABI_API_SVC, &uri_handler);
+
+ fDefaultsLoaded = TRUE;
+ return S_OK;
+}
+
+static void WasabiApi_Uninitialize()
+{
+ if (NULL != WASABI_API_SVC)
+ {
+ ReleaseWasabiInterface(applicationApiServiceGuid, WASABI_API_APP);
+ ReleaseWasabiInterface(languageApiGUID, WASABI_API_LNG);
+ ReleaseWasabiInterface(ExplorerFindFileApiGUID, WASABI_API_EXPLORERFINDFILE);
+ ReleaseWasabiInterface(AgaveConfigGUID, AGAVE_API_CONFIG);
+ ReleaseWasabiInterface(memMgrApiServiceGuid, WASABI_API_MEMMNGR);
+ ReleaseWasabiInterface(pngLoaderGUID, WASABI_API_PNGLOADER);
+ ReleaseWasabiInterface(JSAPI2::api_securityGUID, AGAVE_API_JSAPI2_SECURITY);
+ ReleaseWasabiInterface(AuthApiGUID, AGAVE_API_AUTH);
+ ReleaseWasabiInterface(OBJ_OmBrowser, OMBROWSERMNGR);
+ ReleaseWasabiInterface(IFC_OmServiceManager, OMSERVICEMNGR);
+ ReleaseWasabiInterface(IFC_OmUtility, OMUTILITY);
+ WASABI_API_SVC->service_deregister(&jsapi2Factory);
+ uri_handler_factory.Deregister(WASABI_API_SVC);
+ }
+
+ WASABI_API_SVC = NULL;
+ WASABI_API_APP = NULL;
+ WASABI_API_LNG = NULL;
+ WASABI_API_EXPLORERFINDFILE = NULL;
+ AGAVE_API_CONFIG = NULL;
+ AGAVE_API_JSAPI2_SECURITY = NULL;
+ AGAVE_API_AUTH = NULL;
+ OMBROWSERMNGR = NULL;
+ OMSERVICEMNGR = NULL;
+ OMUTILITY = NULL;
+
+ fDefaultsLoaded = FALSE;
+}
+
+ULONG WasabiApi_AddRef()
+{
+ return InterlockedIncrement((LONG*)&wasabiRef);
+}
+
+ULONG WasabiApi_Release()
+{
+ if (0 == wasabiRef)
+ return wasabiRef;
+
+ LONG r = InterlockedDecrement((LONG*)&wasabiRef);
+ if (0 == r)
+ {
+ WasabiApi_Uninitialize();
+ }
+ return r;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/AMNWatcher.cpp b/Src/Plugins/Library/ml_playlists/AMNWatcher.cpp
new file mode 100644
index 00000000..c57951eb
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/AMNWatcher.cpp
@@ -0,0 +1,241 @@
+#include "main.h"
+
+#include "./amnwatcher.h"
+#include <shlobj.h>
+#include <strsafe.h>
+
+#include "playlists.h"
+#include "PlaylistManager.h"
+#include "PlaylistView.h"
+
+waServiceFactory *AMNWatcher::watcherFactory = NULL;
+
+BOOL IsMusicNowWantUs(void)
+{
+ HKEY key;
+ DWORD value = 0, length = sizeof(DWORD);
+ if (ERROR_SUCCESS == RegOpenKeyExW( HKEY_CURRENT_USER, L"Software\\AOL Music Now\\UserPreferences", 0, KEY_QUERY_VALUE, &key ))
+ {
+ long retCode = RegQueryValueExW(key, L"IntegrateWithWinamp", NULL, NULL, (LPBYTE) & value, &length);
+ if (ERROR_SUCCESS != retCode) value = TRUE;
+ RegCloseKey(key);
+ }
+ return value;
+}
+
+BOOL GetMusicNowPlaylistPath(LPWSTR *pathNew, LPWSTR *pathOld) // CAUTION!!! this function will allocate memory for the string using malloc
+{
+ // first check rgistry
+ HKEY key;
+ *pathNew = NULL;
+ *pathOld = NULL;
+
+ if (ERROR_SUCCESS == RegOpenKeyExW( HKEY_CURRENT_USER, L"Software\\AOL Music Now\\UserPreferences", 0, KEY_QUERY_VALUE, &key ))
+ {
+ const wchar_t *rPath = L"PlaylistDirectory";
+ DWORD length;
+ if (ERROR_SUCCESS == RegQueryValueExW(key, rPath, NULL, NULL, NULL, &length)) // try registry ( for future)
+ {
+ *pathNew = (LPWSTR)malloc(length);
+ if (ERROR_SUCCESS != RegQueryValueExW(key, rPath, NULL, NULL, (LPBYTE)*pathNew, &length)) { free(*pathNew); *pathNew = NULL; }
+ }
+ RegCloseKey(key);
+
+ if (*pathNew && !PathFileExists(*pathNew)) { free(*pathNew); *pathNew = NULL; } //check that path is actualy exist
+
+ LPITEMIDLIST pidl;
+ if (S_OK == SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &pidl))
+ {
+ wchar_t path[MAX_PATH*4] = {0};
+ if(SHGetPathFromIDListW(pidl, path))
+ {
+ const wchar_t *suffix = L"Music Now Playlists";
+ int cchLen = lstrlenW(path) + lstrlenW(suffix) + 32;
+ if (!*pathNew) // no luck - use deafult's
+ {
+ *pathNew = (LPWSTR)malloc(cchLen*sizeof(wchar_t));
+ StringCchPrintfW(*pathNew, cchLen, L"%s\\%s", path, suffix);
+ if (!PathFileExists(*pathNew)) {free(*pathNew); *pathNew = NULL; }// i give up - can't find anything
+ }
+ // now let's try old path
+ *pathOld = (LPWSTR)malloc(cchLen*sizeof(wchar_t));
+ StringCchPrintfW(*pathOld, cchLen, L"%s\\AOL %s", path, suffix);
+ if (!PathFileExists(*pathOld)) { free(*pathOld); *pathOld = NULL; }
+ }
+ CoTaskMemFree(pidl);
+ }
+ }
+ return (NULL != *pathNew) || (NULL != *pathOld);
+}
+
+AMNWatcher::AMNWatcher(void) : watcherOld(NULL), watcherNew(NULL), dirty(FALSE)
+{}
+
+AMNWatcher::~AMNWatcher(void)
+{
+ //Destroy(); // have to just let it leak. you should call Destroy() manually
+}
+
+int AMNWatcher::Init(api_service *service, const wchar_t *trackPath)
+{
+ BOOL retCode = FALSE;
+ if (!IsMusicNowWantUs()) return retCode;
+ if (watcherFactory) return retCode;
+
+ LPWSTR pathNew, pathOld;
+ if (!GetMusicNowPlaylistPath(&pathNew, &pathOld)) return retCode;
+ int cchLen = lstrlenW(pathNew);
+ if (cchLen > 1 && pathNew[cchLen -1] == L'\\') pathNew[cchLen -1] = 0x00;
+ cchLen = lstrlenW(pathOld);
+ if (cchLen > 1 && pathOld[cchLen -1] == L'\\') pathOld[cchLen -1] = 0x00;
+
+ watcherFactory = service->service_getServiceByGuid(watcherGUID);
+ if (watcherFactory)
+ {
+ if (pathNew)
+ {
+ watcherNew = (api_watcher*)watcherFactory->getInterface();
+ watcherNew->Create( L"WAMN_PLS_NEW", pathNew, TRUE, WATCHER_TRACKMODE_CENTRAL, OnWatcherNotify);
+ }
+ if (pathOld)
+ {
+ watcherOld = (api_watcher*)watcherFactory->getInterface();
+ watcherOld->Create( L"WAMN_PLS_OLD", pathOld, TRUE, WATCHER_TRACKMODE_CENTRAL, OnWatcherNotify);
+ }
+ for (int i = 0; i < 2; i++)
+ {
+ api_watcher *watcher = (i) ? watcherOld : watcherNew;
+ if (!watcher) continue;
+
+ watcher->SetExtensionFilter(L"WPL", WATCHER_FILTERTYPE_INCLUDE);
+ watcher->SetUserData(this);
+ watcher->SetCallBack(OnWatcherNotify);
+ watcher->SetTrackingPath(trackPath);
+ watcher->Start();
+ watcher->ForceScan(FALSE, SCANPRIORITY_IDLE);
+ }
+ }
+ free(pathNew);
+ free(pathOld);
+ return TRUE;
+}
+
+void AMNWatcher::Destroy(void)
+{
+ if (watcherNew)
+ {
+ watcherNew->Stop();
+ watcherFactory->releaseInterface(watcherNew);
+ watcherNew = NULL;
+ }
+ if (watcherOld)
+ {
+ watcherOld->Stop();
+ watcherFactory->releaseInterface(watcherOld);
+ watcherOld = NULL;
+ }
+}
+
+int AMNWatcher::OnWatcherNotify(api_watcher *sender, UINT message, LONG_PTR param, void* userdata)
+{
+ UNREFERENCED_PARAMETER(sender);
+
+ AMNWatcher *watcher = (AMNWatcher*)userdata;
+ if (WATCHER_MSG_FILE == message)
+ {
+ WATCHERCHANGEINFO *info = (WATCHERCHANGEINFO*)param;
+
+ // ignore some names
+ if (info->cchFile >= 21) // can be "AOL Music Now - Auido.wpl" or "AOL Music Now - Video.wpl"
+ {
+ const wchar_t *testname = info->file + info->cchFile - 21;
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NULL, testname, 9, L"Music Now", 9)) return 1;
+ }
+ else if ( 17 == info->cchFile && CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NULL, info->file, 13, L"AOL Music Now", 13)) return 1;
+
+ wchar_t fullName[MAX_PATH*4];
+ PathCombineW(fullName, info->path, info->file);
+
+ size_t i(0);
+
+ switch (info->state)
+ {
+ case WATCHER_FILESTATE_ADDED:
+ case WATCHER_FILESTATE_CHANGED:
+ {
+ if (!info->file)
+ break;
+ wchar_t *title = _wcsdup(info->file);
+ unsigned int len = lstrlen(title);
+ while (len && title[len] != '.') len--;
+ if (len != 0) title[len] = 0x00;
+ while (i != playlists.size() && lstrcmpW(fullName, playlists.at(i).filename)) i++;
+ if ( i == playlists.size())
+ {
+ AddPlaylist(title, fullName, TRUE, playlistManager->CountItems(fullName));
+ watcher->dirty = TRUE;
+ }
+ else
+ {
+ int length = playlistManager->GetLengthMilliseconds(fullName);
+ int numItems = (int)playlistManager->CountItems(fullName);
+ if (playlists.at(i).length != length || playlists.at(i).numItems != numItems)
+ {
+ StringCchCopy(playlists.at(i).title, 1024, title);
+ playlists.at(i).length = length;
+ playlists.at(i).numItems = numItems;
+
+ HWND hwnd = (HWND) SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_GETCURRENTVIEW);
+ if (hwnd)
+ {
+ wchar_t* title = (wchar_t*)GetPropW(hwnd, L"TITLE");
+ if (title && 0 == lstrcmp(title, playlists[i].title)) SendMessage(hwnd, WM_PLAYLIST_RELOAD, 0, 0);
+ }
+ watcher->dirty = TRUE;
+ }
+
+ }
+ free(title);
+ }
+ break;
+ case WATCHER_FILESTATE_REMOVED:
+ {
+ for ( i = 0; i < playlists.size(); i++)
+ {
+ if (0 == lstrcmpW(fullName, playlists.at(i).filename))
+ {
+ HWND hwnd = (HWND) SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_GETCURRENTVIEW);
+ if (hwnd)
+ {
+ wchar_t* title = (wchar_t *)GetPropW(hwnd, L"TITLE");
+ if (title && 0 == lstrcmp(title, playlists[i].title)) SendMessage(hwnd, WM_PLAYLIST_UNLOAD, 0, 0);
+ }
+ if (playlists.at(i).treeId)
+ mediaLibrary.RemoveTreeItem(playlists.at(i).treeId);
+ playlists.eraseAt(i);
+ i--;
+ watcher->dirty = TRUE;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ else if (WATCHER_MSG_STATUS == message)
+ {
+ if (STATUS_SCANER_STOPPED == param || STATUS_SCANER_FINISHED == param)
+ {
+ if (watcher->dirty)
+ {
+ SavePlaylists();
+ RefreshPlaylistsList();
+ watcher->dirty = FALSE;
+ }
+
+ }
+ }
+
+
+ return 1;
+}
diff --git a/Src/Plugins/Library/ml_playlists/AddPlaylist.cpp b/Src/Plugins/Library/ml_playlists/AddPlaylist.cpp
new file mode 100644
index 00000000..a228da48
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/AddPlaylist.cpp
@@ -0,0 +1,124 @@
+#include "main.h"
+#include <shlwapi.h>
+#include "resource.h"
+
+wchar_t *createPlayListDBFileName(wchar_t *filename) // filename is ignored but used for temp space, make sure it's 1024+256 chars =)
+{
+ wchar_t *filenameptr;
+ int x = 32;
+ for (;;)
+ {
+ GetTempFileNameW(g_path, L"plf", GetTickCount() + x*5000, filename);
+ if ( wcslen(filename) > 4)
+ {
+ if (g_config->ReadInt(L"playlist_m3u8", 1))
+ lstrcpyW(filename + wcslen(filename) - 4, L".m3u8");
+ else
+ lstrcpyW(filename + wcslen(filename) - 4, L".m3u");
+ }
+ HANDLE h = CreateFileW(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW, 0, 0);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ filenameptr = filename + wcslen(g_path) + 1;
+ CloseHandle(h);
+ break;
+ }
+ if (++x > 4096)
+ {
+ filenameptr = L"error.m3u";
+ break;
+ }
+ }
+ return filenameptr;
+}
+
+void playlists_AddToCloudPrompt(HWND hwndDlg)
+{
+ if (g_config->ReadInt(L"cloud_always", 0) && !g_config->ReadInt(L"cloud_prompt", 0) && g_config->ReadInt(L"cloud", 1))
+ {
+ wchar_t titleStr[64] = {0};
+ if (MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_CLOUD_UNCHECKED),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SENDTO_NEW_CLOUD_PLAYLIST, titleStr, 64),
+ MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES)
+ {
+ g_config->WriteInt(L"cloud", (IsDlgButtonChecked(hwndDlg, IDC_CLOUD) == BST_CHECKED));
+ }
+ else
+ {
+ CheckDlgButton(hwndDlg, IDC_CLOUD, playlists_CloudAvailable() && g_config->ReadInt(L"cloud", 1));
+ }
+ g_config->WriteInt(L"cloud_prompt", 1);
+ }
+ else
+ {
+ g_config->WriteInt(L"cloud", (IsDlgButtonChecked(hwndDlg, IDC_CLOUD) == BST_CHECKED));
+ }
+}
+
+int AddToCloud()
+{
+ if (playlists_CloudAvailable())
+ {
+ if (g_config->ReadInt(L"cloud_always", 1))
+ {
+ g_config->WriteInt(L"cloud", 1);
+ return 1;
+ }
+ else
+ {
+ g_config->WriteInt(L"cloud", 0);
+ }
+ }
+ return 0;
+}
+
+INT_PTR CALLBACK AddPlaylistDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetProp(hwndDlg, L"pladdcb", (HANDLE)lParam);
+ CheckDlgButton(hwndDlg, IDC_CLOUD, AddToCloud());
+ PostMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_NAME), TRUE);
+ break;
+ }
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ wchar_t name[256] = {0};
+ GetDlgItemText(hwndDlg, IDC_NAME, name, 255);
+ if (!name[0])
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_ENTER_A_NAME),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR, titleStr, 32), MB_OK);
+ break;
+ }
+
+ wchar_t filename[1024 + 256] = {0};
+ createPlayListDBFileName(filename);
+ bool callback = !!GetProp(hwndDlg, L"pladdcb");
+ AddPlaylist(callback, name, filename, true, (playlists_CloudAvailable() ? g_config->ReadInt(L"cloud", 1) : 0));
+ if (callback) AGAVE_API_PLAYLISTS->Flush(); // REVIEW: save immediately? or only at the end?
+ EndDialog(hwndDlg, 1);
+ }
+ break;
+
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ case IDC_CLOUD:
+ {
+ playlists_AddToCloudPrompt(hwndDlg);
+ }
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/CurrentPlaylist.cpp b/Src/Plugins/Library/ml_playlists/CurrentPlaylist.cpp
new file mode 100644
index 00000000..91d54dce
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/CurrentPlaylist.cpp
@@ -0,0 +1,313 @@
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#include "main.h"
+#include "CurrentPlaylist.h"
+#include "Playlist.h"
+#include "../winamp/wa_ipc.h"
+#include "../nu/AutoWide.h"
+#include "PlaylistDirectoryCallback.h"
+#include "api__ml_playlists.h"
+
+extern Playlist currentPlaylist;
+
+bool currentPlaylist_ImportFromDisk( HWND hwnd )
+{
+ wchar_t oldCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, oldCurPath);
+
+ wchar_t temp[1024] = {0};
+ wchar_t filter[1024] = {0};
+ AGAVE_API_PLAYLISTMANAGER->GetFilterList(filter, 1024);
+ OPENFILENAMEW l = {sizeof(l), };
+ l.hwndOwner = hwnd;
+ l.lpstrFilter = filter;
+ l.lpstrFile = temp;
+ l.nMaxFile = 1023;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_IMPORT_PLAYLIST);
+ l.lpstrDefExt = L"m3u";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
+
+ l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER;
+
+ bool ret = false;
+ if ( GetOpenFileNameW( &l ) )
+ {
+ wchar_t newCurPath[ MAX_PATH ] = { 0 };
+ GetCurrentDirectoryW( MAX_PATH, newCurPath );
+ WASABI_API_APP->path_setWorkingPath( newCurPath );
+
+ wchar_t titleStr[ 32 ] = { 0 };
+ int w = currentPlaylist.GetNumItems() == 0 ? IDYES :
+ MessageBox( hwnd, WASABI_API_LNGSTRINGW( IDS_APPEND_IMPORTED_PLAYLIST ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_LIBRARY_QUESTION, titleStr, 32 ),
+ MB_YESNOCANCEL | MB_ICONQUESTION );
+
+ if ( w != IDCANCEL )
+ {
+ if ( w == IDNO )
+ currentPlaylist.Clear();
+
+ AGAVE_API_PLAYLISTMANAGER->Load( temp, &currentPlaylist );
+ ret = true;
+ }
+ }
+
+ SetCurrentDirectoryW( oldCurPath );
+ return ret;
+}
+
+bool currentPlaylist_ImportFromWinamp( HWND hwnd )
+{
+ wchar_t titleStr[ 32 ] = { 0 };
+ int w = currentPlaylist.GetNumItems() == 0 ? IDNO :
+ MessageBox( hwnd, WASABI_API_LNGSTRINGW( IDS_APPEND_ACTIVE_PLAYLIST ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_LIBRARY_QUESTION, titleStr, 32 ),
+ MB_YESNOCANCEL | MB_ICONQUESTION );
+
+ if ( w != IDCANCEL )
+ {
+ if ( w == IDNO )
+ currentPlaylist.Clear();
+
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITEPLAYLIST );
+ wchar_t *m3udir = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETM3UDIRECTORYW );
+ wchar_t s[ MAX_PATH ] = { 0 };
+ PathCombineW( s, m3udir, L"winamp.m3u8" );
+
+ AGAVE_API_PLAYLISTMANAGER->Load( s, &currentPlaylist );
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CurrentPlaylist_DeleteMissing()
+{
+ bool ret = false;
+ size_t x = currentPlaylist.GetNumItems();
+ while ( x-- )
+ {
+ wchar_t fn[ 1024 ] = { 0 };
+ currentPlaylist.GetItem( x, fn, 1024 );
+ if ( !wcsstr( fn, L"://" ) && !wcsstr( fn, L":\\\\" ) && !( PathFileExistsW( fn ) ) )
+ {
+ currentPlaylist.Remove( x );
+ ret = true;
+ }
+ }
+
+ return ret;
+}
+
+void CurrentPlaylist_Export(HWND dlgparent)
+{
+ wchar_t oldCurPath[MAX_PATH] = {0};
+ GetCurrentDirectoryW(MAX_PATH, oldCurPath);
+
+ wchar_t temp[MAX_PATH] = {0};
+ OPENFILENAMEW l = {sizeof(OPENFILENAMEW), 0};
+ lstrcpynW(temp, (wchar_t*)GetPropW(dlgparent, L"TITLE"), MAX_PATH);
+ Playlists_ReplaceBadPathChars(temp);
+ l.hwndOwner = dlgparent;
+ l.hInstance = plugin.hDllInstance;
+ l.nFilterIndex = g_config->ReadInt(L"filter", 3);
+ l.lpstrFilter = (LPCWSTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 3, IPC_GET_PLAYLIST_EXTLISTW);
+ l.lpstrFile = temp;
+ l.nMaxFile = MAX_PATH;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_EXPORT_PLAYLIST);
+ l.lpstrDefExt = L"m3u";
+ l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_OVERWRITEPROMPT;
+
+ if ( GetSaveFileNameW( &l ) )
+ {
+ wchar_t newCurPath[ MAX_PATH ] = { 0 };
+ GetCurrentDirectoryW( MAX_PATH, newCurPath );
+
+ WASABI_API_APP->path_setWorkingPath( newCurPath );
+ AGAVE_API_PLAYLISTMANAGER->Save( temp, &currentPlaylist );
+ }
+
+ g_config->WriteInt( L"filter", l.nFilterIndex );
+
+ SetCurrentDirectoryW( oldCurPath );
+}
+
+bool CurrentPlaylist_AddLocation( HWND hwndDlg )
+{
+ bool ret = false;
+ char *p = (char *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)hwndDlg, IPC_OPENURLBOX );
+ if ( p )
+ {
+ //size_t s = currentPlaylist.GetNumItems();
+ AutoWide wideFn( p );
+ if ( AGAVE_API_PLAYLISTMANAGER->Load( wideFn, &currentPlaylist ) != PLAYLISTMANAGER_SUCCESS )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length = -1;
+ mediaLibrary.GetFileInfo( wideFn, title, FILETITLE_SIZE, &length );
+ currentPlaylist.AppendWithInfo( wideFn, title, length * 1000 );
+ }
+
+ ret = true;
+
+ // TODO: if (GetPrivateProfileInt("winamp", "rofiob", 1, WINAMP_INI)&1) PlayList_sort(2, s);
+
+ GlobalFree( (HGLOBAL)p );
+ }
+
+ return ret;
+}
+
+static INT_PTR CALLBACK browseCheckBoxProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_INITDIALOG )
+ {
+ int rofiob = GetPrivateProfileIntA( "winamp", "rofiob", 1, mediaLibrary.GetWinampIni() );
+ if ( !( rofiob & 2 ) )
+ CheckDlgButton( hwndDlg, IDC_CHECK1, BST_CHECKED );
+ }
+
+ if ( uMsg == WM_COMMAND )
+ {
+ if ( LOWORD( wParam ) == IDC_CHECK1 )
+ {
+ int rofiob = GetPrivateProfileIntA( "winamp", "rofiob", 1, mediaLibrary.GetWinampIni() );
+ if ( IsDlgButtonChecked( hwndDlg, IDC_CHECK1 ) )
+ rofiob &= ~2;
+ else
+ rofiob |= 2;
+
+ char blah[ 32 ] = { 0 };
+ StringCchPrintfA( blah, 32, "%d", rofiob );
+ WritePrivateProfileStringA( "winamp", "rofiob", blah, mediaLibrary.GetWinampIni() );
+ }
+ }
+
+ return 0;
+}
+
+static int CALLBACK WINAPI BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData )
+{
+ switch ( uMsg )
+ {
+ case BFFM_INITIALIZED:
+ {
+ SetWindowTextW( hwnd, WASABI_API_LNGSTRINGW( IDS_ADD_DIR_TO_PLAYLIST ) );
+ SendMessageW( hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)WASABI_API_APP->path_getWorkingPath() );
+
+ HWND h2 = FindWindowEx( hwnd, NULL, NULL, L"__foo2" );
+ if ( h2 )
+ ShowWindow( h2, SW_HIDE );
+
+ HWND h = WASABI_API_CREATEDIALOGW( IDD_BROWSE_PLFLD, hwnd, browseCheckBoxProc );
+ SetWindowPos( h, 0, 4, 4, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
+ ShowWindow( h, SW_SHOWNA );
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows( hwnd, browseEnumProc, 0 );
+ }
+ }
+
+ return 0;
+}
+
+bool CurrentPlaylist_AddDirectory( HWND hwndDlg )
+{
+ BROWSEINFOW bi = { 0 };
+ wchar_t name[ MAX_PATH ] = { 0 };
+ bi.hwndOwner = hwndDlg;
+ bi.pidlRoot = 0;
+ bi.pszDisplayName = name;
+ bi.lpszTitle = L"__foo2";
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ bi.lParam = 0;
+
+ ITEMIDLIST *idlist = SHBrowseForFolderW( &bi );
+ if ( idlist )
+ {
+ //size_t s = currentPlaylist.GetNumItems();
+ wchar_t path[ MAX_PATH ] = { 0 };
+ SHGetPathFromIDListW( idlist, path );
+ WASABI_API_APP->path_setWorkingPath( path );
+
+ extern void Shell_Free( void *p );
+ Shell_Free( idlist );
+ WASABI_API_APP->path_setWorkingPath( path );
+
+ PlaylistDirectoryCallback dirCallback( mediaLibrary.GetExtensionList(), mediaLibrary.GetWinampIni() );
+
+ AGAVE_API_PLAYLISTMANAGER->LoadDirectory( path, &currentPlaylist, &dirCallback );
+
+ //int rofiob = GetPrivateProfileInt("winamp", "rofiob", 1, WINAMP_INI);
+ // TODO: if (rofiob&1) PlayList_sort(2, s);
+ return true;
+ }
+
+ return false;
+}
+
+bool CurrentPlaylist_AddFiles( HWND hwndDlg )
+{
+ wchar_t oldCurPath[ MAX_PATH ] = { 0 };
+ GetCurrentDirectoryW( MAX_PATH, oldCurPath );
+
+ const int len = 256 * 1024 - 128;
+ wchar_t *temp;
+ OPENFILENAMEW l = { sizeof( l ), };
+
+ static int q;
+ if ( q )
+ return false;
+
+ q = 1;
+ temp = (wchar_t *)GlobalAlloc( GPTR, sizeof( wchar_t ) * len );
+ l.hwndOwner = hwndDlg;
+ wchar_t *fsb = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 1, IPC_GET_EXTLISTW );
+
+ l.lpstrFilter = fsb;
+ l.lpstrFile = temp;
+ l.nMaxFile = len - 1;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW( IDS_ADD_FILES_TO_PLAYLIST );
+ l.lpstrDefExt = L"";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
+
+ l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
+
+ bool ret = false;
+ if ( GetOpenFileNameW( &l ) )
+ {
+ wchar_t newCurPath[ MAX_PATH ] = { 0 };
+ GetCurrentDirectoryW( MAX_PATH, newCurPath );
+ WASABI_API_APP->path_setWorkingPath( newCurPath );
+
+ if ( temp[ wcslen( temp ) + 1 ] )
+ {
+ AGAVE_API_PLAYLISTMANAGER->LoadFromDialog( temp, &currentPlaylist );
+ ret = true;
+ // TODO: if (GetPrivateProfileInt("winamp", "rofiob", 1, WINAMP_INI)&1) PlayList_sort(2, sp);
+ }
+ else
+ {
+ if ( AGAVE_API_PLAYLISTMANAGER->Load( temp, &currentPlaylist ) != PLAYLISTMANAGER_SUCCESS )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length;
+ mediaLibrary.GetFileInfo( temp, title, FILETITLE_SIZE, &length );
+ currentPlaylist.AppendWithInfo( temp, title, length * 1000 );
+ ret = true;
+ }
+ }
+ }
+
+ SetCurrentDirectoryW( oldCurPath );
+ GlobalFree( fsb );
+ GlobalFree( temp );
+
+ q = 0;
+
+ return ret;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/CurrentPlaylist.h b/Src/Plugins/Library/ml_playlists/CurrentPlaylist.h
new file mode 100644
index 00000000..66ecfdfc
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/CurrentPlaylist.h
@@ -0,0 +1,12 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_CURRENTPLAYLIST_H
+#define NULLSOFT_ML_PLAYLISTS_CURRENTPLAYLIST_H
+
+bool currentPlaylist_ImportFromDisk(HWND hwnd);
+bool currentPlaylist_ImportFromWinamp(HWND hwnd);
+bool CurrentPlaylist_DeleteMissing();
+void CurrentPlaylist_Export(HWND dlgparent);
+bool CurrentPlaylist_AddLocation(HWND hwndDlg);
+bool CurrentPlaylist_AddDirectory(HWND hwndDlg);
+bool CurrentPlaylist_AddFiles(HWND hwndDlg);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/DESIGN.TXT b/Src/Plugins/Library/ml_playlists/DESIGN.TXT
new file mode 100644
index 00000000..9968d67f
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/DESIGN.TXT
@@ -0,0 +1,7 @@
+since everything (hopefully) is being done on the main thread,
+there isn't any locking done around the playlists vector.
+We probably want to change that in the future
+
+
+TODO:
+ability to take a path from elsewhere \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/Playlist.cpp b/Src/Plugins/Library/ml_playlists/Playlist.cpp
new file mode 100644
index 00000000..566aedf1
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/Playlist.cpp
@@ -0,0 +1,331 @@
+#include "main.h"
+#include "./playlist.h"
+#include <algorithm>
+#include "nu/MediaLibraryInterface.h"
+#include "Winamp/strutil.h"
+
+Playlist::~Playlist()
+{
+ Clear();
+}
+
+
+void Playlist::Clear()
+{
+ for ( pl_entry *entry : entries )
+ delete entry;
+
+ entries.clear();
+
+ lengthInMS = 0;
+}
+
+void Playlist::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info )
+{
+ entries.push_back( new pl_entry( filename, title, lengthInMS, info ) );
+
+ this->lengthInMS += lengthInMS;
+}
+
+void Playlist::AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS )
+{
+ this->lengthInMS += lengthInMS;
+ entries.push_back( new pl_entry( filename, title, lengthInMS ) );
+}
+
+void Playlist::AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS, std::map<std::wstring, std::wstring> &p_extended_infos )
+{
+ this->lengthInMS += lengthInMS;
+
+ pl_entry *l_new_pl_entry = new pl_entry( filename, title, lengthInMS );
+
+ if ( !p_extended_infos.empty() )
+ {
+ for ( auto l_extended_info : p_extended_infos )
+ l_new_pl_entry->_extended_infos.emplace( _wcsdup( l_extended_info.first.c_str() ), _wcsdup( l_extended_info.second.c_str() ) );
+ }
+
+ entries.push_back( l_new_pl_entry );
+}
+
+size_t Playlist::GetNumItems()
+{
+ return entries.size();
+}
+
+size_t Playlist::GetItem( size_t item, wchar_t *filename, size_t filenameCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetFilename( filename, filenameCch );
+}
+
+size_t Playlist::GetItemTitle( size_t item, wchar_t *title, size_t titleCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetTitle( title, titleCch );
+}
+
+const wchar_t *Playlist::ItemTitle( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filetitle;
+}
+
+const wchar_t *Playlist::ItemName( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filename;
+}
+
+int Playlist::GetItemLengthMilliseconds( size_t item )
+{
+ if ( item >= entries.size() )
+ return -1;
+
+ return entries[ item ]->GetLengthInMilliseconds();
+}
+
+size_t Playlist::GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetExtendedInfo( metadata, info, infoCch );
+}
+
+int Playlist::Reverse()
+{
+ // TODO: keep a bool flag and just do size-item-1 every time a GetItem* function is called
+ std::reverse( entries.begin(), entries.end() );
+
+ return PLAYLIST_SUCCESS;
+}
+
+int Playlist::Swap( size_t item1, size_t item2 )
+{
+ std::swap( entries[ item1 ], entries[ item2 ] );
+
+ return PLAYLIST_SUCCESS;
+}
+
+class RandMod
+{
+public:
+ RandMod( int ( *_generator )( ) ) : generator( _generator ) {}
+ int operator ()( int n ) { return generator() % n; }
+ int ( *generator )( );
+};
+
+int Playlist::Randomize( int ( *generator )( ) )
+{
+ RandMod randMod( generator );
+ std::random_shuffle( entries.begin(), entries.end(), randMod );
+
+ return PLAYLIST_SUCCESS;
+}
+
+void Playlist::Remove( size_t item )
+{
+ lengthInMS -= entries[item]->length;
+ delete entries[item];
+ entries.erase(entries.begin() + item);
+}
+
+void Playlist::SetItemFilename( size_t item, const wchar_t *filename )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetFilename( filename );
+}
+
+void Playlist::SetItemTitle( size_t item, const wchar_t *title )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetTitle( title );
+}
+
+void Playlist::SetItemLengthMilliseconds( size_t item, int length )
+{
+ if ( item < entries.size() )
+ {
+ lengthInMS -= entries[ item ]->length;
+ entries[ item ]->SetLengthMilliseconds( length );
+ lengthInMS += length;
+ }
+}
+
+
+void GetTitle( pl_entry *&a )
+{
+ if ( !a->cached )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length = -1;
+
+ mediaLibrary.GetFileInfo( a->filename, title, FILETITLE_SIZE, &length );
+
+ a->SetLengthMilliseconds( length * 1000 );
+ a->SetTitle( title );
+ }
+}
+
+static bool PlayList_sortByTitle( pl_entry *&a, pl_entry *&b )
+{
+ GetTitle( a );
+ GetTitle( b );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE /*|NORM_IGNOREKANATYPE*/ | NORM_IGNOREWIDTH, a->filetitle, -1, b->filetitle, -1 );
+
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return CompareStringLogical(a.strTitle, b.strTitle)<0;
+}
+
+static bool PlayList_sortByFile( pl_entry *&a, pl_entry *&b ) //const void *a, const void *b)
+{
+ const wchar_t *file1 = PathFindFileNameW( a->filename );
+ const wchar_t *file2 = PathFindFileNameW( b->filename );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE |*/ NORM_IGNOREWIDTH, file1, -1, file2, -1 );
+
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return FileCompareLogical(file1, file2)<0;
+}
+
+static bool PlayList_sortByDirectory( pl_entry *&a, pl_entry *&b ) // by dir, then by title
+{
+ const wchar_t *directory1 = a->filename;
+ const wchar_t *directory2 = b->filename;
+
+ const wchar_t *directoryEnd1 = scanstr_backcW( directory1, L"\\", 0 );
+ const wchar_t *directoryEnd2 = scanstr_backcW( directory2, L"\\", 0 );
+
+ size_t dirLen1 = directoryEnd1 - directory1;
+ size_t dirLen2 = directoryEnd2 - directory2;
+
+ if ( !dirLen1 && !dirLen2 ) // both in the current directory?
+ return PlayList_sortByFile( a, b ); // not optimized, because the function does another scanstr_back, but easy for now :)
+
+ if ( !dirLen1 ) // only the first dir is empty?
+ return true; // sort it first
+
+ if ( !dirLen2 ) // only the second dir empty?
+ return false; // empty dirs go first
+
+#if 0 // TODO: grab this function from winamp
+ int comp = FileCompareLogicalN( directory1, dirLen1, directory2, dirLen2 );
+ if ( comp == 0 )
+ return PlayList_sortByFile( a, b );
+ else
+ return comp < 0;
+#endif
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE | */NORM_IGNOREWIDTH, directory1, dirLen1, directory2, dirLen2 );
+ if ( comp == CSTR_EQUAL ) // same dir
+ return PlayList_sortByFile( a, b ); // do second sort
+ else // different dirs
+ return comp == CSTR_LESS_THAN;
+}
+
+int Playlist::SortByTitle()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByTitle );
+
+ return 1;
+}
+
+int Playlist::SortByFilename()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByFile );
+
+ return 1;
+}
+
+int Playlist::SortByDirectory()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByDirectory );
+
+ return 1;
+}
+/*
+int Playlist::Move(size_t itemSrc, size_t itemDest)
+{
+ if (itemSrc < itemDest)
+ std::rotate(&entries[itemSrc], &entries[itemSrc], &entries[itemDest]);
+ else
+ if (itemSrc > itemDest)
+ std::rotate(&entries[itemDest], &entries[itemSrc], &entries[itemSrc]);
+ return 1;
+}*/
+
+bool Playlist::IsCached( size_t item )
+{
+ return entries[ item ]->cached;
+}
+
+bool Playlist::IsLocal( size_t item )
+{
+ return entries[ item ]->isLocal();
+}
+
+void Playlist::ClearCache( size_t item )
+{
+ entries[ item ]->cached = false;
+}
+
+void Playlist::InsertPlaylist( Playlist &copy, size_t index )
+{
+ for ( pl_entry *l_entry : copy.entries )
+ {
+ entries.insert( entries.begin() + index, l_entry );
+ ++index;
+ lengthInMS += l_entry->length;
+ }
+
+ copy.entries.clear();
+}
+
+void Playlist::AppendPlaylist( Playlist &copy )
+{
+ for ( pl_entry *l_entry : copy.entries )
+ {
+ this->entries.push_back( l_entry );
+ lengthInMS += l_entry->length;
+ }
+
+ copy.entries.clear();
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS Playlist
+
+START_MULTIPATCH;
+START_PATCH( patch_playlist )
+M_VCB( patch_playlist, ifc_playlist, IFC_PLAYLIST_CLEAR, Clear )
+//M_VCB(patch_playlist, ifc_playlist, IFC_PLAYLIST_APPENDWITHINFO, AppendWithInfo)
+//M_VCB(patch_playlist, ifc_playlist, IFC_PLAYLIST_APPEND, Append)
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETNUMITEMS, GetNumItems )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEM, GetItem )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMTITLE, GetItemTitle )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMEXTENDEDINFO, GetItemExtendedInfo )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_REVERSE, Reverse )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SWAP, Swap )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_RANDOMIZE, Randomize )
+M_VCB( patch_playlist, ifc_playlist, IFC_PLAYLIST_REMOVE, Remove )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYTITLE, SortByTitle )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYFILENAME, SortByFilename )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYDIRECTORY, SortByDirectory )
+NEXT_PATCH( patch_playlistloadercallback )
+M_VCB( patch_playlistloadercallback, ifc_playlistloadercallback, IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile );
+END_PATCH
+END_MULTIPATCH; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/Playlist.h b/Src/Plugins/Library/ml_playlists/Playlist.h
new file mode 100644
index 00000000..4b750447
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/Playlist.h
@@ -0,0 +1,66 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLIST_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLIST_H
+
+#include "playlist/ifc_playlist.h"
+
+#include "bfc/multipatch.h"
+#include "playlist/pl_entry.h"
+#include "playlist/ifc_playlistloadercallback.h"
+
+enum
+{
+ patch_playlist,
+ patch_playlistloadercallback
+};
+
+class Playlist : public MultiPatch<patch_playlist, ifc_playlist>, public MultiPatch<patch_playlistloadercallback, ifc_playlistloadercallback>
+{
+public:
+ Playlist() {}
+ virtual ~Playlist();
+
+ void Clear();
+ void OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info );
+
+ void AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS );
+ void AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS, std::map<std::wstring, std::wstring> &p_extended_infos );
+
+ size_t GetNumItems();
+
+ size_t GetItem( size_t item, wchar_t *filename, size_t filenameCch );
+ size_t GetItemTitle( size_t item, wchar_t *title, size_t titleCch );
+ const wchar_t *ItemTitle( size_t item );
+ const wchar_t *ItemName( size_t item );
+ int GetItemLengthMilliseconds( size_t item ); // TODO: maybe microsecond for better resolution?
+ size_t GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch );
+
+ bool IsCached( size_t item );
+ bool IsLocal( size_t item );
+ void ClearCache( size_t item );
+
+ void SetItemFilename( size_t item, const wchar_t *filename );
+ void SetItemTitle( size_t item, const wchar_t *title );
+ void SetItemLengthMilliseconds( size_t item, int length );
+
+ int Reverse();
+ int Swap( size_t item1, size_t item2 );
+ int Randomize( int ( *generator )( ) );
+ void Remove( size_t item );
+
+ int SortByTitle();
+ int SortByFilename();
+ int SortByDirectory(); //sorts by directory and then by filename
+
+ void InsertPlaylist( Playlist &copy, size_t index );
+ void AppendPlaylist( Playlist &copy );
+
+protected:
+ RECVS_MULTIPATCH;
+
+public:
+ typedef std::vector<pl_entry*> PlaylistEntries;
+ PlaylistEntries entries;
+ uint64_t lengthInMS = 0;
+};
+
+#endif // !NULLSOFT_ML_PLAYLISTS_PLAYLIST_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.cpp b/Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.cpp
new file mode 100644
index 00000000..58799815
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.cpp
@@ -0,0 +1,41 @@
+#include "main.h"
+#include "PlaylistDirectoryCallback.h"
+
+PlaylistDirectoryCallback::PlaylistDirectoryCallback(const char *_extlist, const char *winampIni)
+ : extlist(_extlist), recurse(true)
+{
+ if (winampIni)
+ {
+ int rofiob = GetPrivateProfileIntA("winamp", "rofiob", 1, winampIni);
+ recurse = (rofiob & 2) ? false : true;
+ }
+}
+
+bool PlaylistDirectoryCallback::ShouldRecurse(const wchar_t *path)
+{
+ return recurse;
+}
+
+bool PlaylistDirectoryCallback::ShouldLoad(const wchar_t *filename)
+{
+ const wchar_t *ext = PathFindExtensionW(filename);
+ if (!*ext)
+ return false;
+
+ ext++;
+
+ const char *a = extlist;
+ while (a && *a)
+ {
+ if (!lstrcmpiW(AutoWide(a), ext))
+ return true;
+ a += lstrlenA(a) + 1;
+ }
+ return false;
+}
+
+#define CBCLASS PlaylistDirectoryCallback
+START_DISPATCH;
+CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse )
+CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad )
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.h b/Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.h
new file mode 100644
index 00000000..2ee79671
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistDirectoryCallback.h
@@ -0,0 +1,15 @@
+#include "../playlist/ifc_playlistdirectorycallback.h"
+
+class PlaylistDirectoryCallback : public ifc_playlistdirectorycallback
+{
+public:
+ PlaylistDirectoryCallback(const char *_extlist, const char *winampIni=0);
+bool ShouldRecurse(const wchar_t *path);
+ bool ShouldLoad(const wchar_t *filename);
+
+ const char *extlist;
+ bool recurse;
+
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistInfo.cpp b/Src/Plugins/Library/ml_playlists/PlaylistInfo.cpp
new file mode 100644
index 00000000..87a6da8d
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistInfo.cpp
@@ -0,0 +1,233 @@
+#include <assert.h>
+#include <strsafe.h>
+
+#include "main.h"
+#include "PlaylistInfo.h"
+#include "nu/AutoLock.h"
+
+int uniqueAddress;
+
+using namespace Nullsoft::Utility;
+
+static INT_PTR FindTreeID( GUID playlist_guid )
+{
+ INT_PTR treeId = 0;
+
+ //if ( tree_to_guid_map.reverseLookup( playlist_guid, &treeId ) )
+ // return treeId;
+ //else
+ // return 0;
+
+ // Look for values and return the index
+ for (auto& item : tree_to_guid_map)
+ {
+ if (0 == memcmp(&item.second, &playlist_guid, sizeof(GUID)))
+ {
+ return item.first;
+ }
+ }
+
+ return 0;
+}
+
+PlaylistInfo::PlaylistInfo()
+{
+ Clear();
+}
+
+void PlaylistInfo::Clear()
+{
+ length = 0;
+ numItems = 0;
+ treeId = 0;
+ index = 0;
+ iterator = 0;
+ cloud = 0;
+ playlist_guid = INVALID_GUID;
+}
+
+bool PlaylistInfo::Associate( INT_PTR _treeId )
+{
+ Clear();
+
+ treeId = _treeId;
+ playlist_guid = tree_to_guid_map[treeId];
+
+ if ( AGAVE_API_PLAYLISTS->GetPosition( playlist_guid, &index ) == API_PLAYLISTS_SUCCESS )
+ {
+ iterator = AGAVE_API_PLAYLISTS->GetIterator();
+
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_itemCount, &numItems, sizeof( numItems ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_totalTime, &length, sizeof( length ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_cloud, &cloud, sizeof( cloud ) );
+ }
+ else
+ playlist_guid = INVALID_GUID;
+
+ return Valid();
+}
+
+// as a pre-condition, AGAVE_API_PLAYLISTS needs to be locked
+PlaylistInfo::PlaylistInfo( size_t p_index )
+{
+ Clear();
+
+ index = p_index;
+ iterator = AGAVE_API_PLAYLISTS->GetIterator();
+ playlist_guid = AGAVE_API_PLAYLISTS->GetGUID( index );
+
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_itemCount, &numItems, sizeof( numItems ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_totalTime, &length, sizeof( length ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_cloud, &cloud, sizeof( cloud ) );
+
+ treeId = FindTreeID( playlist_guid ); // try to find treeId
+}
+
+// as a pre-condition, AGAVE_API_PLAYLISTS needs to be locked
+PlaylistInfo::PlaylistInfo( GUID p_playlist_guid )
+{
+ Clear();
+
+ playlist_guid = p_playlist_guid;
+
+ if ( AGAVE_API_PLAYLISTS->GetPosition( playlist_guid, &index ) == API_PLAYLISTS_SUCCESS )
+ {
+ iterator = AGAVE_API_PLAYLISTS->GetIterator();
+
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_itemCount, &numItems, sizeof( numItems ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_totalTime, &length, sizeof( length ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_cloud, &cloud, sizeof( cloud ) );
+
+ treeId = FindTreeID( playlist_guid ); // try to find treeId
+ }
+ else
+ playlist_guid = INVALID_GUID;
+}
+
+PlaylistInfo::PlaylistInfo( const PlaylistInfo &copy )
+{
+ Clear();
+
+ index = copy.index;
+ iterator = copy.iterator;
+ playlist_guid = copy.playlist_guid;
+ length = copy.length;
+ numItems = copy.numItems;
+ treeId = copy.treeId;
+ cloud = copy.cloud;
+}
+
+size_t PlaylistInfo::GetIndex()
+{
+ assert( Valid() );
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ size_t curIterator = AGAVE_API_PLAYLISTS->GetIterator();
+ if ( curIterator != iterator )
+ {
+ iterator = curIterator;
+ AGAVE_API_PLAYLISTS->GetPosition( playlist_guid, &index );
+ }
+
+ return index;
+}
+
+void PlaylistInfo::IssueSaveCallback()
+{
+ assert( Valid() );
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_SAVED, GetIndex(), (intptr_t)&uniqueAddress );
+}
+
+void PlaylistInfo::Refresh()
+{
+ assert( Valid() );
+
+ if ( AGAVE_API_PLAYLISTS->GetPosition( playlist_guid, &index ) == API_PLAYLISTS_SUCCESS )
+ {
+ iterator = AGAVE_API_PLAYLISTS->GetIterator();
+
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_itemCount, &numItems, sizeof( numItems ) );
+ AGAVE_API_PLAYLISTS->GetInfo( index, api_playlists_totalTime, &length, sizeof( length ) );
+ }
+ else
+ playlist_guid = INVALID_GUID;
+}
+
+const wchar_t *PlaylistInfo::GetFilename()
+{
+ assert( Valid() );
+
+ if ( _filename.empty() )
+ _filename = AGAVE_API_PLAYLISTS->GetFilename( GetIndex() );
+
+ return _filename.c_str();
+}
+
+// TODO: we should investigate caching the title
+const wchar_t *PlaylistInfo::GetName()
+{
+ assert( Valid() );
+
+ if ( _title.empty() )
+ _title = AGAVE_API_PLAYLISTS->GetName( GetIndex() );
+
+ return _title.c_str();
+}
+
+size_t PlaylistInfo::GetLength()
+{
+ assert( Valid() );
+
+ return length;
+}
+
+void PlaylistInfo::SetLength( size_t newLength )
+{
+ assert( Valid() );
+
+ length = newLength;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ AGAVE_API_PLAYLISTS->SetInfo( GetIndex(), api_playlists_totalTime, &length, sizeof( length ) );
+}
+
+size_t PlaylistInfo::GetSize()
+{
+ assert( Valid() );
+
+ return numItems;
+}
+
+size_t PlaylistInfo::GetCloud()
+{
+ assert( Valid() );
+
+ return cloud;
+}
+
+void PlaylistInfo::SetCloud(size_t newCloud)
+{
+ assert( Valid() );
+
+ cloud = newCloud;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ AGAVE_API_PLAYLISTS->SetInfo( GetIndex(), api_playlists_cloud, &cloud, sizeof( cloud ) );
+}
+
+bool PlaylistInfo::Valid()
+{
+ return !!( playlist_guid != INVALID_GUID );
+}
+
+void PlaylistInfo::SetSize( size_t newSize )
+{
+ assert( Valid() );
+
+ numItems = newSize;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ AGAVE_API_PLAYLISTS->SetInfo( GetIndex(), api_playlists_itemCount, &numItems, sizeof( numItems ) );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistInfo.h b/Src/Plugins/Library/ml_playlists/PlaylistInfo.h
new file mode 100644
index 00000000..df2f5722
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistInfo.h
@@ -0,0 +1,58 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLIST_INFO_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLIST_INFO_H
+
+#include <windows.h> // for MAX_PATH
+#include <iostream> // for std::wstring
+
+// REVIEW: what if we want this to be an ifc_playlist * from elsewhere instead of a physical m3u file
+// maybe this should be a playlist factory instead?
+class PlaylistInfo
+{
+ /* --- Methods --- */
+public:
+ PlaylistInfo();
+ PlaylistInfo( GUID _playlist_guid );
+ PlaylistInfo( size_t p_index ); // as a pre-condition, AGAVE_API_PLAYLISTS needs to be locked
+ PlaylistInfo( const PlaylistInfo &copy );
+
+ bool Valid();
+
+ bool Associate( INT_PTR _treeId );
+
+ size_t GetIndex();
+ const wchar_t *GetFilename();
+ const wchar_t *GetName();
+
+ size_t GetLength();
+ void SetLength( size_t newLength );
+
+ size_t GetSize();
+ void SetSize( size_t newSize );
+
+ size_t GetCloud();
+ void SetCloud( size_t newCloud );
+
+ void Refresh();
+
+ void IssueSaveCallback();
+
+private:
+ void Clear();
+
+ /* --- Data --- */
+public:
+ INT_PTR treeId; // if it's being displayed as a media library tree, the id is here (0 otherwise)
+ GUID playlist_guid;
+
+private:
+ size_t index;
+ size_t iterator;
+ size_t length; // in seconds
+ size_t numItems;
+ size_t cloud;
+
+ std::wstring _title;
+ std::wstring _filename;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistView.cpp b/Src/Plugins/Library/ml_playlists/PlaylistView.cpp
new file mode 100644
index 00000000..83beebfe
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistView.cpp
@@ -0,0 +1,300 @@
+#include "main.h"
+#include "PlaylistView.h"
+#include "Playlist.h"
+#include "CurrentPlaylist.h"
+#include "api__ml_playlists.h"
+#include "../ml_local/api_mldb.h"
+#include "../ml_pmp/pmp.h"
+#include <strsafe.h>
+
+extern Playlist currentPlaylist;
+
+static BOOL playlist_GetDisplayInfo( NMLVDISPINFO *lpdi )
+{
+ size_t item = lpdi->item.iItem;
+ if ( item < 0 || item >= currentPlaylist.GetNumItems() )
+ return 0;
+
+ if ( lpdi->item.mask & LVIF_TEXT )
+ {
+ switch ( lpdi->item.iSubItem )
+ {
+ case 0:
+ {
+ if ( !currentPlaylist.IsCached( item ) )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length = -1;
+ mediaLibrary.GetFileInfo( currentPlaylist.ItemName( item ), title, FILETITLE_SIZE, &length );
+ currentPlaylist.SetItemLengthMilliseconds( item, length * 1000 );
+ currentPlaylist.SetItemTitle( item, title );
+ }
+
+ // CUT: currentPlaylist.GetItemTitle(item, lpdi->item.pszText, lpdi->item.cchTextMax);
+ const wchar_t *title = currentPlaylist.ItemTitle( item );
+ if ( !title )
+ title = currentPlaylist.ItemName( item );
+
+ // TODO - just using for debugging to check values
+#ifdef DEBUG
+ wchar_t info[ 128 ] = { 0 };
+ if ( currentPlaylist.GetItemExtendedInfo( item, L"cloud", info, 128 ) )
+ {
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"[%s] %d. %s", info, item + 1, title );
+ }
+ else
+ {
+#endif
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d. %s", item + 1, title );
+#ifdef DEBUG
+ }
+#endif
+ }
+ break;
+
+ case 1:
+ {
+ wchar_t info[ 16 ] = { 0 };
+ if ( currentPlaylist.GetItemExtendedInfo( item, L"cloud_status", info, 16 ) )
+ {
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%s", info );
+ }
+ else
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d", 4 );
+ }
+ break;
+
+ case 2:
+ {
+ if ( currentPlaylist.GetItemLengthMilliseconds( item ) == 0 ) // if the length is 0, then we'll re-read it
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length = 0;
+ mediaLibrary.GetFileInfo( currentPlaylist.ItemName( item ), title, FILETITLE_SIZE, &length );
+ if ( length == 0 )
+ currentPlaylist.SetItemLengthMilliseconds( item, -1000 );
+ else
+ {
+ currentPlaylist.SetItemLengthMilliseconds( item, length * 1000 );
+ }
+ }
+
+ int length = currentPlaylist.GetItemLengthMilliseconds( item ) / 1000;
+ if ( length <= 0 )
+ lpdi->item.pszText[ 0 ] = 0;
+ else
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d:%02d", length / 60, length % 60 );
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+BOOL playlist_OnCustomDraw( HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult )
+{
+ static BOOL bDrawFocus;
+ static RECT rcView;
+ static CLOUDCOLUMNPAINT cloudColumnPaint;
+
+ *pResult = CDRF_DODEFAULT;
+
+ switch ( plvcd->nmcd.dwDrawStage )
+ {
+ case CDDS_PREPAINT:
+ *pResult |= CDRF_NOTIFYITEMDRAW;
+ CopyRect( &rcView, &plvcd->nmcd.rc );
+
+ cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ cloudColumnPaint.hdc = plvcd->nmcd.hdc;
+ cloudColumnPaint.prcView = &rcView;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ *pResult |= CDRF_NOTIFYSUBITEMDRAW;
+ bDrawFocus = ( CDIS_FOCUS & plvcd->nmcd.uItemState );
+ if ( bDrawFocus )
+ {
+ plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
+ *pResult |= CDRF_NOTIFYPOSTPAINT;
+ }
+ return TRUE;
+
+ case CDDS_ITEMPOSTPAINT:
+ if ( bDrawFocus )
+ {
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+ SendMessageW( plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc );
+
+ rc.left += 3;
+ DrawFocusRect( plvcd->nmcd.hdc, &rc );
+
+ plvcd->nmcd.uItemState |= CDIS_FOCUS;
+ bDrawFocus = FALSE;
+ }
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+
+ case( CDDS_SUBITEM | CDDS_ITEMPREPAINT ):
+ // TODO need to have a map between column ids so we do this correctly
+ if ( plvcd->iSubItem == 1 )
+ {
+ if ( 0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right )
+ break;
+
+ cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ cloudColumnPaint.iSubItem = plvcd->iSubItem;
+
+ int cloud_icon = 4;
+ size_t item = plvcd->nmcd.dwItemSpec;
+
+ wchar_t info[ 16 ] = { 0 };
+ if ( currentPlaylist.GetItemExtendedInfo( item, L"cloud_status", info, 16 ) )
+ cloud_icon = _wtoi( info );
+
+ // TODO have this show an appropriate cloud icon for the playlist
+ // currently all we have is cloud or nothing as we'll only
+ // have files locally for this for the moment (need todo!!!)
+ cloudColumnPaint.value = cloud_icon;
+ cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
+ cloudColumnPaint.rgbBk = plvcd->clrTextBk;
+ cloudColumnPaint.rgbFg = plvcd->clrText;
+
+ if ( MLCloudColumn_Paint( plugin.hwndLibraryParent, &cloudColumnPaint ) )
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+BOOL playlist_Notify( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ LPNMHDR l = (LPNMHDR)lParam;
+ if ( l->idFrom == IDC_PLAYLIST_EDITOR )
+ {
+ switch ( l->code )
+ {
+ case NM_DBLCLK:
+ PlaySelection( g_config->ReadInt( L"enqueuedef", 0 ) == 1, g_config->ReadInt( L"plplaymode", 1 ) );
+ break;
+
+ case LVN_GETDISPINFO:
+ return playlist_GetDisplayInfo( (NMLVDISPINFO *)lParam );
+
+ case LVN_BEGINDRAG:
+ we_are_drag_and_dropping = 1;
+ SetCapture( hwndDlg );
+ break;
+
+ case LVN_ITEMCHANGED:
+ case LVN_ODSTATECHANGED:
+ UpdatePlaylistTime( hwndDlg );
+ break;
+
+ case NM_CUSTOMDRAW:
+ {
+ LRESULT result = 0;
+ if ( cloud_avail && playlist_OnCustomDraw( hwndDlg, (NMLVCUSTOMDRAW *)lParam, &result ) )
+ {
+ SetWindowLongPtrW( hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result );
+
+ return 1;
+ }
+ break;
+ }
+
+ case NM_CLICK:
+ {
+ LPNMITEMACTIVATE pnmitem = (LPNMITEMACTIVATE)lParam;
+ if ( cloud_avail && pnmitem->iItem != -1 && pnmitem->iSubItem == 1 )
+ {
+ RECT itemRect = { 0 };
+ if ( pnmitem->iSubItem )
+ ListView_GetSubItemRect( pnmitem->hdr.hwndFrom, pnmitem->iItem, pnmitem->iSubItem, LVIR_BOUNDS, &itemRect );
+ else
+ {
+ ListView_GetItemRect( pnmitem->hdr.hwndFrom, pnmitem->iItem, &itemRect, LVIR_BOUNDS );
+ itemRect.right = itemRect.left + ListView_GetColumnWidth( pnmitem->hdr.hwndFrom, pnmitem->iSubItem );
+ }
+
+ MapWindowPoints( l->hwndFrom, HWND_DESKTOP, (POINT *)&itemRect, 2 );
+
+ //int cloud_devices = 0;
+ HMENU cloud_hmenu = 0;
+ int mark = playlist_list.GetSelectionMark();
+ if ( mark != -1 )
+ {
+ wchar_t filename[ MAX_PATH ] = { 0 };
+ currentPlaylist.entries[ mark ]->GetFilename( filename, MAX_PATH );
+
+ cloud_hmenu = CreatePopupMenu();
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)&filename, (intptr_t)&cloud_hmenu );
+ if ( cloud_hmenu )
+ {
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, cloud_hmenu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY, itemRect.right, itemRect.top, hwndDlg, NULL );
+ if ( r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER )
+ { // deals with cloud specific menus
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+
+ int mode = 0; // deals with cloud specific menus
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode );
+ // TODO
+ /*switch (mode)
+ {
+ case 1:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"5");
+ break;
+
+ case 2:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+
+ case 4:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+ }
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);*/
+ }
+
+ DestroyMenu( cloud_hmenu );
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ switch ( l->code )
+ {
+ case HDN_ITEMCHANGING:
+ {
+ LPNMHEADERW phdr = (LPNMHEADERW)lParam;
+ if ( phdr->pitem && ( HDI_WIDTH & phdr->pitem->mask ) && phdr->iItem == 1 )
+ {
+ if ( !cloud_avail )
+ phdr->pitem->cxy = 0;
+ else
+ {
+ INT width = phdr->pitem->cxy;
+ if ( MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width ) )
+ phdr->pitem->cxy = width;
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistView.h b/Src/Plugins/Library/ml_playlists/PlaylistView.h
new file mode 100644
index 00000000..1eeab508
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistView.h
@@ -0,0 +1,32 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLISTVIEW_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLISTVIEW_H
+
+#include "../nu/listview.h"
+
+#define WM_PLAYLIST WM_USER + 1980
+
+#define WM_PLAYLIST_RELOAD WM_PLAYLIST + 1
+#define WM_PLAYLIST_UNLOAD WM_PLAYLIST + 2
+
+
+extern int we_are_drag_and_dropping;
+extern W_ListView playlist_list;
+
+class PlayListView : public W_ListView
+{
+public:
+
+};
+// TODO: make a nice pretty class to wrap the playlist view
+
+BOOL playlist_Notify( HWND hwndDlg, WPARAM wParam, LPARAM lParam );
+void PlaySelection( int enqueue, int is_all );
+void UpdatePlaylistTime( HWND hwndDlg );
+void TagEditor( HWND hwnd );
+void SyncPlaylist();
+void Playlist_DeleteSelected( int selected );
+void Playlist_ResetSelected();
+void EditEntry( HWND parent );
+void DownloadSelectedEntries( HWND hwndDlg );
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistsCB.cpp b/Src/Plugins/Library/ml_playlists/PlaylistsCB.cpp
new file mode 100644
index 00000000..8a1a3b01
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistsCB.cpp
@@ -0,0 +1,120 @@
+#include "main.h"
+#include "PlaylistsCB.h"
+using namespace Nullsoft::Utility;
+
+static UINT_PTR refreshTimer;
+void CALLBACK RefreshPlaylistsCallback(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ if (idEvent == 777)
+ {
+ KillTimer(plugin.hwndWinampParent, idEvent);
+ refreshTimer = 0;
+ RefreshPlaylistsList();
+ }
+}
+
+int PlaylistsCB::notify(int msg, intptr_t param1, intptr_t param2)
+{
+ switch (msg)
+ {
+ case api_playlists::PLAYLIST_ADDED:
+ {
+ AutoLockT<api_playlists> lock(AGAVE_API_PLAYLISTS); // should already be locked, but can't hurt
+ size_t newIndex = static_cast<size_t>(param1);
+ PlaylistInfo playlist(newIndex);
+ if (playlist.Valid())
+ {
+ // TODO: check where newIndex is, so we can insert into the middle of the list if necessary
+ /* TODO: maybe stuff this in Set/GetInfo somewhere
+ if (makeTree)
+ */
+ MakeTree(playlist);
+ if (refreshTimer) KillTimer(plugin.hwndWinampParent, refreshTimer);
+ refreshTimer = SetTimer(plugin.hwndWinampParent, 777, 250, RefreshPlaylistsCallback);
+ if (!param2) AGAVE_API_PLAYLISTS->Flush(); // REVIEW: save immediately? or only at the end?
+ }
+ return 1;
+ }
+ break;
+ case api_playlists::PLAYLIST_REMOVED_PRE:
+ {
+ AutoLockT<api_playlists> lock(AGAVE_API_PLAYLISTS); // should already be locked, but can't hurt
+ size_t index = static_cast<size_t>(param1),
+ count = AGAVE_API_PLAYLISTS->GetCount();
+ PlaylistInfo info(index);
+ if (info.Valid())
+ {
+ // is a number of cases where this just seems to fail and so we revert to manually
+ // parsing the playlists tree inorder to find and remove the expected playlist by
+ // the index of the playlist (since we don't alter the order, this should be safe)
+ if (!info.treeId)
+ {
+ INT_PTR id = mediaLibrary.GetChildId(playlistsTreeId);
+ if (id)
+ {
+ for (size_t i = 0; i < count; i++)
+ {
+ if (i == index)
+ {
+ mediaLibrary.RemoveTreeItem(id);
+ break;
+ }
+
+ if (!(id = mediaLibrary.GetNextId(id))) break;
+ }
+ }
+ }
+ else
+ mediaLibrary.RemoveTreeItem(info.treeId);
+
+ tree_to_guid_map.erase(info.treeId);
+ }
+ return 1;
+ }
+ break;
+ case api_playlists::PLAYLIST_REMOVED_POST:
+ {
+ if (refreshTimer) KillTimer(plugin.hwndWinampParent, refreshTimer);
+ refreshTimer = SetTimer(plugin.hwndWinampParent, 777, 250, RefreshPlaylistsCallback);
+ return 1;
+ }
+ break;
+ case api_playlists::PLAYLIST_RENAMED:
+ {
+ AutoLockT<api_playlists> lock(AGAVE_API_PLAYLISTS); // should already be locked, but can't hurt
+ // tell the media library to rename the treeview node
+ size_t index = static_cast<size_t>(param1);
+ PlaylistInfo playlist(index);
+ if (playlist.Valid())
+ {
+ mediaLibrary.RenameTreeId(playlist.treeId, playlist.GetName());
+ }
+ return 1;
+ }
+ break;
+ case api_playlists::PLAYLIST_SAVED:
+ if (param2 != (intptr_t)&uniqueAddress)
+ {
+ AutoLockT<api_playlists> lock(AGAVE_API_PLAYLISTS); // should already be locked, but can't hurt
+ size_t index = static_cast<size_t>(param1);
+ playlist_ReloadGUID(AGAVE_API_PLAYLISTS->GetGUID(index));
+ }
+ break;
+ case api_playlists::PLAYLIST_FLUSH_REQUEST:
+ if (param2 != (intptr_t)&uniqueAddress)
+ {
+ AutoLockT<api_playlists> lock(AGAVE_API_PLAYLISTS); // should already be locked, but can't hurt
+ size_t index = static_cast<size_t>(param1);
+ playlist_SaveGUID(AGAVE_API_PLAYLISTS->GetGUID(index));
+ }
+ break;
+ }
+ return 0;
+}
+
+#define CBCLASS PlaylistsCB
+START_DISPATCH;
+CB(SYSCALLBACK_GETEVENTTYPE, getEventType);
+CB(SYSCALLBACK_NOTIFY, notify);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistsCB.h b/Src/Plugins/Library/ml_playlists/PlaylistsCB.h
new file mode 100644
index 00000000..f2e21c35
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistsCB.h
@@ -0,0 +1,16 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLISTSCB_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLISTSCB_H
+
+#include <api/syscb/callbacks/syscb.h>
+#include "../playlist/api_playlists.h"
+
+class PlaylistsCB : public SysCallback
+{
+public:
+ FOURCC getEventType() { return api_playlists::SYSCALLBACK; }
+ int notify(int msg, intptr_t param1 = 0, intptr_t param2 = 0);
+
+protected:
+ RECVS_DISPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistsCOM.cpp b/Src/Plugins/Library/ml_playlists/PlaylistsCOM.cpp
new file mode 100644
index 00000000..7d1e3209
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistsCOM.cpp
@@ -0,0 +1,178 @@
+#include "main.h"
+#include "PlaylistsCOM.h"
+using namespace Nullsoft::Utility;
+
+enum
+{
+ DISP_PLALISTS_GETXML = 777,
+
+};
+
+#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
+HRESULT PlaylistsCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ CHECK_ID("GetXML", DISP_PLALISTS_GETXML)
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+ }
+
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT PlaylistsCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT PlaylistsCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+static void WriteEscaped(FILE *fp, const wchar_t *str)
+{
+ // TODO: for speed optimization,
+ // we should wait until we hit a special character
+ // and write out everything else so before it,
+ // like how ASX loader does it
+ while (str && *str)
+ {
+ switch (*str)
+ {
+ case L'&':
+ fputws(L"&amp;", fp);
+ break;
+ case L'>':
+ fputws(L"&gt;", fp);
+ break;
+ case L'<':
+ fputws(L"&lt;", fp);
+ break;
+ case L'\'':
+ fputws(L"&apos;", fp);
+ break;
+ case L'\"':
+ fputws(L"&quot;", fp);
+ break;
+ default:
+ fputwc(*str, fp);
+ break;
+ }
+ // write out the whole UTF-16 character
+ wchar_t *next = CharNextW(str);
+ while (++str != next)
+ fputwc(*str, fp);
+
+ }
+}
+
+static void SavePlaylistsXML(const wchar_t *destination, size_t limit)
+{
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ FILE *fp = _wfopen(destination, L"wb");
+ fputws(L"\xFEFF", fp);
+ fwprintf(fp, L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>");
+ size_t numPlaylists = limit ? min(limit, AGAVE_API_PLAYLISTS->GetCount()) : AGAVE_API_PLAYLISTS->GetCount();
+ fwprintf(fp, L"<playlists playlists=\"%lu\">", numPlaylists);
+
+ for (size_t i = 0; i != numPlaylists; i++)
+ {
+ fputws(L"<playlist filename=\"", fp);
+ WriteEscaped(fp, AGAVE_API_PLAYLISTS->GetFilename(i));
+ fputws(L"\" title=\"", fp);
+ WriteEscaped(fp, AGAVE_API_PLAYLISTS->GetName(i));
+ unsigned int numItems = 0, length = 0;
+
+ AGAVE_API_PLAYLISTS->GetInfo(i, api_playlists_itemCount, &numItems, sizeof(numItems));
+ AGAVE_API_PLAYLISTS->GetInfo(i, api_playlists_totalTime, &length, sizeof(length));
+
+ fwprintf(fp, L"\" songs=\"%u\" seconds=\"%u\"/>",
+ numItems,
+ length);
+ }
+ fwprintf(fp, L"</playlists>");
+ fclose(fp);
+}
+
+HRESULT PlaylistsCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case DISP_PLALISTS_GETXML:
+ {
+ int max = 0;
+ if (pdispparams->cArgs == 1)
+ {
+ max = _wtoi(pdispparams->rgvarg[0].bstrVal);
+ }
+
+ wchar_t tempPath[MAX_PATH] = {0};
+ GetTempPathW(MAX_PATH, tempPath);
+ wchar_t tempFile[MAX_PATH] = {0};
+ GetTempFileNameW(tempPath, L"mpx", 0, tempFile);
+
+ SavePlaylistsXML(tempFile, max);
+ HANDLE plFile = CreateFile(tempFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
+ size_t flen = SetFilePointer(plFile, 0, NULL, FILE_END);
+ SetFilePointer(plFile, 0, NULL, FILE_BEGIN);
+
+ SAFEARRAY *bufferArray = SafeArrayCreateVector(VT_UI1, 0, flen);
+ void *data;
+ SafeArrayAccessData(bufferArray, &data);
+ DWORD bytesRead = 0;
+ ReadFile(plFile, data, flen, &bytesRead, 0);
+ SafeArrayUnaccessData(bufferArray);
+ CloseHandle(plFile);
+ DeleteFile(tempFile);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_ARRAY | VT_UI1;
+ V_ARRAY(pvarResult) = bufferArray;
+
+
+ }
+ return S_OK;
+
+
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP PlaylistsCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+ULONG PlaylistsCOM::AddRef(void)
+{
+ return 0;
+}
+
+
+ULONG PlaylistsCOM::Release(void)
+{
+ return 0;
+}
+
+
diff --git a/Src/Plugins/Library/ml_playlists/PlaylistsCOM.h b/Src/Plugins/Library/ml_playlists/PlaylistsCOM.h
new file mode 100644
index 00000000..8b34e8dd
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/PlaylistsCOM.h
@@ -0,0 +1,20 @@
+#ifndef NULLSOFT_PLAYLISTSCOM_H
+#define NULLSOFT_PLAYLISTSCOM_H
+
+#include <ocidl.h>
+
+class PlaylistsCOM : public IDispatch
+{
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/RenamePlaylist.cpp b/Src/Plugins/Library/ml_playlists/RenamePlaylist.cpp
new file mode 100644
index 00000000..14fa4f4d
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/RenamePlaylist.cpp
@@ -0,0 +1,72 @@
+#include "main.h"
+#include "resource.h"
+using namespace Nullsoft::Utility;
+
+static INT_PTR CALLBACK RenamePlaylistDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static GUID playlist_guid = INVALID_GUID;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ playlist_guid = *(GUID *)lParam;
+
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo playlist(playlist_guid);
+ if (playlist.Valid())
+ {
+ size_t index = playlist.GetIndex();
+ const wchar_t *title = AGAVE_API_PLAYLISTS->GetName(index);
+ SetDlgItemText(hwndDlg, IDC_NAME, title);
+
+ SendMessage(GetDlgItem(hwndDlg, IDC_NAME), EM_SETSEL, 0, -1);
+ SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_NAME), TRUE);
+
+ }
+ else
+ EndDialog(hwndDlg, 1);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ wchar_t name[1024] = {0};
+ GetDlgItemText(hwndDlg, IDC_NAME, name, 1023);
+ name[1023] = 0;
+ if (!name[0])
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_ENTER_A_NAME),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,32), MB_OK);
+ break;
+ }
+
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+
+ PlaylistInfo playlist(playlist_guid);
+
+ if (playlist.Valid())
+ {
+ size_t index = playlist.GetIndex();
+ AGAVE_API_PLAYLISTS->RenamePlaylist(index, name);
+ AGAVE_API_PLAYLISTS->Flush(); // TODO: save immediately? or only at the end?
+ }
+
+ EndDialog(hwndDlg, 1);
+ }
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ }
+ return FALSE;
+};
+
+void RenamePlaylist(GUID _guid, HWND parent)
+{
+ WASABI_API_DIALOGBOXPARAMW(IDD_RENAME_PLAYLIST, parent, RenamePlaylistDialogProc, (LPARAM)&_guid);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/SendTo.cpp b/Src/Plugins/Library/ml_playlists/SendTo.cpp
new file mode 100644
index 00000000..a6dc5d9e
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/SendTo.cpp
@@ -0,0 +1,697 @@
+#include <strsafe.h>
+
+#include "main.h"
+#include "api__ml_playlists.h"
+#include "Playlist.h"
+#include "PlaylistView.h"
+#include "resource.h"
+#include "../nu/AutoWideFn.h"
+#include <vector>
+
+/* TODO:
+ Somehow replace index values in send-to with GUID's. Possibly by making a vector of GUIDs (like view_playlists).
+ Since index values could (in theory) change in a background thread while send-to interaction is going on.
+*/
+
+extern Playlist currentPlaylist;
+
+using namespace Nullsoft::Utility;
+
+static std::vector<GUID> sendto_playlistGUIDs;
+
+int playlists_CloudAvailable()
+{
+ if (IPC_GET_CLOUD_HINST == -1) IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_GET_CLOUD_ACTIVE == -1) IPC_GET_CLOUD_ACTIVE = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudActive", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+
+ return (cloud_avail = /*0/*/!(!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))/**/);
+}
+
+int playlists_CloudInstalled()
+{
+ if (IPC_GET_CLOUD_HINST == -1) IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+
+ return (!(!cloud_hinst || cloud_hinst == (HINSTANCE)1));
+}
+
+int playlists_BuildSendTo(int sourceType, INT_PTR context)
+{
+ if (sourceType == ML_TYPE_ITEMRECORDLISTW || sourceType == ML_TYPE_ITEMRECORDLIST ||
+ sourceType == ML_TYPE_FILENAMES || sourceType == ML_TYPE_STREAMNAMES ||
+ sourceType == ML_TYPE_FILENAMESW || sourceType == ML_TYPE_STREAMNAMESW ||
+ sourceType == ML_TYPE_CDTRACKS)
+ {
+ if (g_config->ReadInt(L"pl_send_to", DEFAULT_PL_SEND_TO))
+ {
+ mediaLibrary.BranchSendTo(context);
+ mediaLibrary.AddToSendTo(WASABI_API_LNGSTRINGW(IDS_SENDTO_NEW_PLAYLIST), context, playlistsTreeId);
+
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ sendto_playlistGUIDs.clear();
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ for (size_t i = 0;i != count;i++)
+ {
+ PlaylistInfo info(i);
+ if (info.Valid())
+ {
+ sendto_playlistGUIDs.push_back(info.playlist_guid);
+ if (sendToIgnoreID != info.treeId)
+ {
+ mediaLibrary.AddToBranchSendTo(info.GetName(), context, reinterpret_cast<INT_PTR>(pluginMessageProc) + i);
+ }
+ }
+ }
+ mediaLibrary.EndBranchSendTo(WASABI_API_LNGSTRINGW(IDS_SENDTO_ML_PLAYLISTS), context);
+ }
+ else
+ {
+ mediaLibrary.AddToSendTo(WASABI_API_LNGSTRINGW(IDS_SENDTO_PLAYLIST), context, playlistsTreeId);
+ }
+ }
+
+ return 0;
+}
+
+static void NewPlaylist( Playlist &newPlaylist, const wchar_t *playlistTitle, int makeTree, const wchar_t *newFilename = 0 )
+{
+ if ( playlistTitle && *playlistTitle )
+ {
+ size_t numItems = newPlaylist.GetNumItems();
+ wchar_t filename[ 1024 + 256 ] = { 0 };
+
+ if ( newFilename )
+ {
+ if ( PathIsFileSpecW( newFilename ) )
+ PathCombineW( filename, g_path, newFilename );
+ else
+ lstrcpynW( filename, newFilename, MAX_PATH );
+ }
+ else
+ PathCombineW( filename, g_path, createPlayListDBFileName( filename ) );
+
+ AGAVE_API_PLAYLISTMANAGER->Save( filename, &newPlaylist );
+ AddPlaylist( true, playlistTitle, filename, ( LOWORD( makeTree ) != 0 ), HIWORD( makeTree ), numItems, newPlaylist.lengthInMS );
+ }
+ else
+ {
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ size_t a = AGAVE_API_PLAYLISTS->GetCount();
+ playlists_Add( plugin.hwndLibraryParent, false );
+
+ if ( AGAVE_API_PLAYLISTS->GetCount() == a + 1 )
+ {
+ PlaylistInfo info( a );
+ if ( info.Valid() )
+ {
+ info.SetLength( (int)( newPlaylist.lengthInMS > 0 ? newPlaylist.lengthInMS / 1000 : 0 ) );
+ info.SetSize( newPlaylist.GetNumItems() );
+ info.SetCloud( HIWORD( makeTree ) );
+ wchar_t fn[ MAX_PATH ] = { 0 };
+ if ( PathIsFileSpecW( info.GetFilename() ) )
+ PathCombineW( fn, g_path, info.GetFilename() );
+ else
+ lstrcpynW( fn, info.GetFilename(), MAX_PATH );
+
+
+ for ( pl_entry *l_entry : newPlaylist.entries )
+ {
+ for ( pl_entry *l_current_entry : currentPlaylist.entries )
+ {
+ if ( wcscmp( l_entry->filename, l_current_entry->filename ) == 0 )
+ {
+ //SetTitle releases previously allocated string, no need to free previous one
+ l_entry->SetTitle(l_current_entry->filetitle);
+
+
+ if ( !l_current_entry->_extended_infos.empty() )
+ {
+ for ( auto l_current_extended_info : l_current_entry->_extended_infos )
+ l_entry->_extended_infos.emplace( _wcsdup( l_current_extended_info.first.c_str() ), _wcsdup( l_current_extended_info.second.c_str() ) );
+ }
+
+ break;
+ }
+ }
+ }
+
+ AGAVE_API_PLAYLISTMANAGER->Save( fn, &newPlaylist );
+ // delay the added notification being sent so for the cloud
+ // we can have the complete playlist available to work with
+ AGAVE_API_PLAYLISTS->Flush();
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_ADDED, a/* + 1*/, 0 );
+ UpdateTree( info, info.treeId );
+ }
+ }
+ }
+ RefreshPlaylistsList();
+}
+
+void AddPlaylistFromFilenames(const char *filenames, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename)
+{
+ Playlist newPlaylist;
+ while (filenames && *filenames)
+ {
+ AutoWide wideFn(filenames);
+ if (AGAVE_API_PLAYLISTMANAGER->Load(wideFn, &newPlaylist) != PLAYLISTMANAGER_SUCCESS) // try to load it as a playlist first
+ {
+ wchar_t title[FILETITLE_SIZE] = {0};
+ int length = 0;
+ mediaLibrary.GetFileInfo(wideFn, title, FILETITLE_SIZE, &length);
+ newPlaylist.AppendWithInfo(wideFn, title, length*1000); // otherwise just add it to the playlist directly
+ }
+
+ filenames += strlen(filenames) + 1;
+ }
+
+ NewPlaylist(newPlaylist, playlistTitle, makeTree, filename);
+}
+
+void AddPlaylistFromFilenamesW(const wchar_t *filenames, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename)
+{
+ Playlist newPlaylist;
+ while (filenames && *filenames)
+ {
+ if (AGAVE_API_PLAYLISTMANAGER->Load(filenames, &newPlaylist) != PLAYLISTMANAGER_SUCCESS) // try to load it as a playlist first
+ {
+ wchar_t title[FILETITLE_SIZE] = {0};
+ int length = 0;
+ mediaLibrary.GetFileInfo(filenames, title, FILETITLE_SIZE, &length);
+ newPlaylist.AppendWithInfo(filenames, title, (length > 0 ? length*1000 : 0)); // otherwise just add it to the playlist directly
+ }
+
+ filenames += wcslen(filenames) + 1;
+ }
+
+ NewPlaylist(newPlaylist, playlistTitle, makeTree, filename);
+}
+
+static wchar_t *itemrecordTagFunc(wchar_t *tag, void * p) //return 0 if not found
+{
+ itemRecord *t = (itemRecord *)p;
+ char buf[128] = {0};
+ char *value = NULL;
+
+ if (!_wcsicmp(tag, L"artist")) value = t->artist;
+ else if (!_wcsicmp(tag, L"album")) value = t->album;
+ else if (!_wcsicmp(tag, L"filename")) value = t->filename;
+ else if (!_wcsicmp(tag, L"title")) value = t->title;
+ else if (!_wcsicmp(tag, L"ext")) value = t->ext;
+ else if (!_wcsicmp(tag, L"year"))
+ {
+ if (t->year > 0)
+ {
+ StringCchPrintfA(buf, 128, "%04d", t->year);
+ value = buf;
+ }
+ }
+ else if (!_wcsicmp(tag, L"genre")) value = t->genre;
+ else if (!_wcsicmp(tag, L"comment")) value = t->comment;
+ else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track"))
+ {
+ if (t->track > 0)
+ {
+ StringCchPrintfA(buf, 128, "%02d", t->track);
+ value = buf;
+ }
+ }
+ else if (!_wcsicmp(tag, L"rating")) value = getRecordExtendedItem(t, "RATING");
+ else if (!_wcsicmp(tag, L"playcount")) value = getRecordExtendedItem(t, "PLAYCOUNT");
+ else if (!_wcsicmp(tag, L"bitrate")) value = getRecordExtendedItem(t, "BITRATE");
+ else
+ return 0;
+
+ if (!value) return reinterpret_cast<wchar_t *>(-1);
+ else return AutoWideDup(value);
+}
+
+static wchar_t *itemrecordWTagFunc(wchar_t *tag, void * p) //return 0 if not found
+{
+ itemRecordW *t = (itemRecordW *)p;
+ wchar_t buf[128] = {0};
+ wchar_t *value = NULL;
+
+ // TODO: more fields
+ if (!_wcsicmp(tag, L"artist")) value = t->artist;
+ else if (!_wcsicmp(tag, L"album")) value = t->album;
+ else if (!_wcsicmp(tag, L"filename")) value = t->filename;
+ else if (!_wcsicmp(tag, L"title")) value = t->title;
+ else if (!_wcsicmp(tag, L"year"))
+ {
+ if (t->year > 0)
+ {
+ StringCchPrintfW(buf, 128, L"%04d", t->year);
+ value = buf;
+ }
+ }
+ else if (!_wcsicmp(tag, L"genre")) value = t->genre;
+ else if (!_wcsicmp(tag, L"comment")) value = t->comment;
+ else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track"))
+ {
+ if (t->track > 0)
+ {
+ StringCchPrintfW(buf, 128, L"%02d", t->track);
+ value = buf;
+ }
+ }
+ else if (!_wcsicmp(tag, L"rating"))
+ {
+ if (t->rating > 0)
+ {
+ StringCchPrintfW(buf, 128, L"%d", t->rating);
+ value = buf;
+ }
+ }
+ else if (!_wcsicmp(tag, L"playcount")) value = t->comment;
+ else if (!_wcsicmp(tag, L"bitrate"))
+ {
+ if (t->bitrate > 0)
+ {
+ StringCchPrintfW(buf, 128, L"%d", t->bitrate);
+ value = buf;
+ }
+ }
+ else
+ return 0;
+
+ if (!value) return reinterpret_cast<wchar_t *>(-1);
+ else return _wcsdup(value);
+}
+
+static void fieldTagFuncFree(char * tag, void * p)
+{
+ free(tag);
+}
+
+static void BuildTitle(itemRecord *record, wchar_t *title, int lenCch)
+{
+ AutoWideFn wfn(record->filename);
+ waFormatTitleExtended fmt;
+ fmt.filename = wfn;
+ fmt.useExtendedInfo = 1;
+ fmt.out = title;
+ fmt.out_len = lenCch;
+ fmt.p = record;
+ fmt.spec = 0;
+ *(void **)&fmt.TAGFUNC = itemrecordTagFunc;
+ *(void **)&fmt.TAGFREEFUNC = fieldTagFuncFree;
+ *title = 0;
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+static void BuildTitleW(itemRecordW *record, wchar_t *title, int lenCch)
+{
+ waFormatTitleExtended fmt;
+ fmt.filename = record->filename;
+ fmt.useExtendedInfo = 1;
+ fmt.out = title;
+ fmt.out_len = lenCch;
+ fmt.p = record;
+ fmt.spec = 0;
+ *(void **)&fmt.TAGFUNC = itemrecordWTagFunc;
+ *(void **)&fmt.TAGFREEFUNC = fieldTagFuncFree;
+ *title = 0;
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+void AddPlaylistFromItemRecordList(itemRecordList *obj, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename)
+{
+ Playlist newPlaylist;
+ wchar_t title[FILETITLE_SIZE] = {0};
+ for (int x = 0; x < obj->Size; x ++)
+ {
+ BuildTitle(&obj->Items[x], title, FILETITLE_SIZE);
+ newPlaylist.AppendWithInfo(AutoWide(obj->Items[x].filename), title, obj->Items[x].length*1000);
+ }
+
+ NewPlaylist(newPlaylist, playlistTitle, makeTree, filename);
+}
+
+void AddPlaylistFromItemRecordListW(itemRecordListW *obj, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename)
+{
+ Playlist newPlaylist;
+ wchar_t title[FILETITLE_SIZE] = {0};
+ for (int x = 0; x < obj->Size; x ++)
+ {
+ BuildTitleW(&obj->Items[x], title, FILETITLE_SIZE);
+ newPlaylist.AppendWithInfo(obj->Items[x].filename, title, obj->Items[x].length*1000);
+ }
+
+ NewPlaylist(newPlaylist, playlistTitle, makeTree, filename);
+}
+
+static void AddToPlaylist(GUID playlist_guid, int sourceType, INT_PTR data)
+{
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+
+ playlist_Save(plugin.hwndWinampParent);
+ PlaylistInfo p(playlist_guid);
+ if (p.Valid())
+ {
+ Playlist playlist;
+ AGAVE_API_PLAYLISTMANAGER->Load(p.GetFilename(), &playlist);
+
+ if (sourceType == ML_TYPE_FILENAMES || sourceType == ML_TYPE_STREAMNAMES)
+ {
+ const char *ptr = (const char*)data;
+
+ while (ptr && *ptr)
+ {
+ AutoWide wideFn(ptr);
+ // try to load in playlist manager first
+ Playlist sentPlaylist;
+ if (sourceType == ML_TYPE_FILENAMES
+ && AGAVE_API_PLAYLISTMANAGER->Load(wideFn, &sentPlaylist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ playlist.AppendPlaylist(sentPlaylist);
+ }
+ else
+ {
+ wchar_t title[FILETITLE_SIZE] = {0};
+ int length = -1;
+ mediaLibrary.GetFileInfo(wideFn, title, FILETITLE_SIZE, &length);
+ playlist.AppendWithInfo(wideFn, title, length*1000);
+ }
+ ptr += strlen(ptr) + 1;
+ }
+ }
+ else if (sourceType == ML_TYPE_FILENAMESW || sourceType == ML_TYPE_STREAMNAMESW)
+ {
+ const wchar_t *ptr = (const wchar_t*)data;
+
+ while (ptr && *ptr)
+ {
+ // try to load in playlist manager first
+ Playlist sentPlaylist;
+ if (sourceType == ML_TYPE_FILENAMES
+ && AGAVE_API_PLAYLISTMANAGER->Load(ptr, &sentPlaylist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ playlist.AppendPlaylist(sentPlaylist);
+ }
+ else
+ {
+ wchar_t title[FILETITLE_SIZE] = {0};
+ int length = -1;
+ mediaLibrary.GetFileInfo(ptr, title, FILETITLE_SIZE, &length);
+
+ std::map<std::wstring, std::wstring> l_extended_infos;
+
+ if ( PathIsURLW( title ) )
+ {
+ wchar_t *end = 0;
+ for ( pl_entry *l_current_entry : currentPlaylist.entries )
+ {
+ if ( wcscmp( title, l_current_entry->filename ) == 0 )
+ {
+ StringCchCopyExW( title, FILETITLE_SIZE, l_current_entry->filetitle, &end, 0, 0 );
+
+ if ( !l_current_entry->_extended_infos.empty() )
+ {
+ for ( auto l_current_extended_info : l_current_entry->_extended_infos )
+ l_extended_infos.emplace( _wcsdup( l_current_extended_info.first.c_str() ), _wcsdup( l_current_extended_info.second.c_str() ) );
+ }
+
+ break;
+ }
+ }
+ }
+
+ if ( l_extended_infos.empty() )
+ playlist.AppendWithInfo( ptr, title, length * 1000 );
+ else
+ playlist.AppendWithInfo( ptr, title, length * 1000, l_extended_infos );
+ }
+ ptr += wcslen(ptr) + 1;
+ }
+ }
+ else if (sourceType == ML_TYPE_ITEMRECORDLIST || sourceType == ML_TYPE_CDTRACKS)
+ {
+ itemRecordList *obj = (itemRecordList *)data;
+
+ wchar_t title[FILETITLE_SIZE] = {0};
+ for (int x = 0; x < obj->Size; x ++)
+ {
+ BuildTitle(&obj->Items[x], title, FILETITLE_SIZE);
+ playlist.AppendWithInfo(AutoWide(obj->Items[x].filename), title, obj->Items[x].length*1000);
+ }
+ }
+ else if (sourceType == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *obj = (itemRecordListW *)data;
+
+ wchar_t title[FILETITLE_SIZE] = {0};
+ for (int x = 0; x < obj->Size; x ++)
+ {
+ BuildTitleW(&obj->Items[x], title, FILETITLE_SIZE);
+ playlist.AppendWithInfo(obj->Items[x].filename, title, obj->Items[x].length*1000);
+ }
+ }
+ else if (sourceType == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist *ptr = (mlPlaylist *)data;
+
+ Playlist sentPlaylist;
+ if (AGAVE_API_PLAYLISTMANAGER->Load(ptr->filename, &sentPlaylist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ playlist.AppendPlaylist(sentPlaylist);
+ }
+ }
+ else if (sourceType == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists= (mlPlaylist **)data;
+
+ while(playlists && *playlists)
+ {
+ mlPlaylist *ptr = *playlists;
+ Playlist sentPlaylist;
+ if (AGAVE_API_PLAYLISTMANAGER->Load(ptr->filename, &sentPlaylist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ playlist.AppendPlaylist(sentPlaylist);
+ }
+ playlists++;
+ }
+ }
+
+ AGAVE_API_PLAYLISTMANAGER->Save(p.GetFilename(), &playlist);
+ p.SetSize(playlist.GetNumItems());
+ p.IssueSaveCallback();
+
+ playlist_Reload();
+ }
+}
+
+int playlists_OnDropTarget(int id, int sourceType, INT_PTR data)
+{
+ if (id == playlistsTreeId)
+ {
+ switch (sourceType)
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ if (data)
+ AddPlaylistFromFilenames((const char *)data, 0, 1);
+ return 1;
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ if (data)
+ AddPlaylistFromFilenamesW((const wchar_t *)data, 0, 1);
+ return 1;
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_CDTRACKS:
+ if (data)
+ AddPlaylistFromItemRecordList((itemRecordList *)data, 0, 1);
+ return 1;
+ case ML_TYPE_ITEMRECORDLISTW:
+ if (data)
+ AddPlaylistFromItemRecordListW((itemRecordListW *)data, 0, 1);
+ return 1;
+ default:
+ return -1;
+ }
+ }
+ else if (FindTreeItem(id))
+ {
+ switch (sourceType)
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_ITEMRECORDLISTW:
+ case ML_TYPE_CDTRACKS:
+ if (data && !we_are_drag_and_dropping)
+ {
+ AddToPlaylist(tree_to_guid_map[id], sourceType, data);
+ }
+ return 1;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK SelectPlaylistProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ LRESULT index = SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_ADDSTRING, 0, (LPARAM) WASABI_API_LNGSTRINGW(IDS_SENDTO_NEW_PLAYLIST));
+ SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_SETITEMDATA , index, -1);
+ /*if (playlists_CloudAvailable())
+ {
+ index = SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_ADDSTRING, 0, (LPARAM) WASABI_API_LNGSTRINGW(IDS_SENDTO_NEW_CLOUD_PLAYLIST));
+ SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_SETITEMDATA , index, -1);
+ }*/
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ sendto_playlistGUIDs.clear();
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ for (size_t i = 0;i != count; i++)
+ {
+ PlaylistInfo info(i);
+ if (info.Valid())
+ {
+ sendto_playlistGUIDs.push_back(info.playlist_guid);
+ if (sendToIgnoreID != info.treeId)
+ {
+ index = SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_ADDSTRING, 0, (LPARAM)info.GetName());
+ SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_SETITEMDATA , index, i);
+ }
+ }
+ }
+ SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_SETCURSEL, 0, 0);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ int selection = SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_GETCURSEL, 0, 0);
+ if (selection != CB_ERR)
+ EndDialog(hwndDlg, SendDlgItemMessage(hwndDlg, IDC_PLAYLISTS, CB_GETITEMDATA , selection, 0));
+ else
+ EndDialog(hwndDlg, -2);
+ }
+ break;
+ case IDCANCEL:
+ {
+ EndDialog(hwndDlg, -2);
+ }
+ break;
+ }
+ break;
+
+ }
+ return 0;
+}
+
+int playlists_OnSendTo(int sourceType, INT_PTR data, int id)
+{
+ if (id == playlistsTreeId || id == playlistsCloudTreeId)
+ {
+ if (g_config->ReadInt(L"pl_send_to", DEFAULT_PL_SEND_TO))
+ {
+ int is_cloud = (id == playlistsCloudTreeId) || (id == playlistsTreeId && g_config->ReadInt(L"cloud", 1));
+ switch (sourceType)
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ AddPlaylistFromFilenames((const char *)data, 0, MAKELONG(1, is_cloud));
+ return 1;
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ AddPlaylistFromFilenamesW((const wchar_t *)data, 0, MAKELONG(1, is_cloud));
+ return 1;
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_CDTRACKS:
+ AddPlaylistFromItemRecordList((itemRecordList *)data, 0, MAKELONG(1, is_cloud));
+ return 1;
+ case ML_TYPE_ITEMRECORDLISTW:
+ AddPlaylistFromItemRecordListW((itemRecordListW *)data, 0, MAKELONG(1, is_cloud));
+ return 1;
+ case ML_TYPE_PLAYLIST:
+ {
+ mlPlaylist *ptr = (mlPlaylist *)data;
+ AddPlaylistFromFilenamesW(ptr->filename, 0, MAKELONG(1, is_cloud));
+ return 1;
+ }
+ case ML_TYPE_PLAYLISTS:
+ {
+ mlPlaylist **playlists= (mlPlaylist **)data;
+ while(playlists && *playlists)
+ {
+ mlPlaylist *ptr = *playlists;
+ AddPlaylistFromFilenamesW(ptr->filename, 0, MAKELONG(1, is_cloud));
+ playlists++;
+ }
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ size_t selection = WASABI_API_DIALOGBOXW(IDD_SELECT_PLAYLIST, NULL, SelectPlaylistProc);
+ if (selection == -2)
+ return -1;
+ else if (selection == -1)
+ {
+ switch (sourceType)
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ AddPlaylistFromFilenames((const char *)data, 0, true);
+ return 1;
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ AddPlaylistFromFilenamesW((const wchar_t *)data, 0, true);
+ return 1;
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_CDTRACKS:
+ AddPlaylistFromItemRecordList((itemRecordList *)data, 0, 1);
+ return 1;
+ case ML_TYPE_ITEMRECORDLISTW:
+ AddPlaylistFromItemRecordListW((itemRecordListW *)data, 0, 1);
+ return 1;
+ case ML_TYPE_PLAYLIST:
+ {
+ mlPlaylist *ptr = (mlPlaylist *)data;
+ AddPlaylistFromFilenamesW(ptr->filename, 0, true);
+ return 1;
+ }
+ case ML_TYPE_PLAYLISTS:
+ {
+ mlPlaylist **playlists= (mlPlaylist **)data;
+ while(playlists && *playlists)
+ {
+ mlPlaylist *ptr = *playlists;
+ AddPlaylistFromFilenamesW(ptr->filename, 0, true);
+ playlists++;
+ }
+ return 1;
+ }
+ }
+ }
+ else if (selection >= 0 && selection < sendto_playlistGUIDs.size())
+ {
+ AddToPlaylist(sendto_playlistGUIDs[selection], sourceType, data);
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ size_t x = id - reinterpret_cast<size_t>(pluginMessageProc);
+ if (x < sendto_playlistGUIDs.size())
+ {
+ AddToPlaylist(sendto_playlistGUIDs[x], sourceType, data);
+ return 1;
+ }
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/SendTo.h b/Src/Plugins/Library/ml_playlists/SendTo.h
new file mode 100644
index 00000000..b7b391e8
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/SendTo.h
@@ -0,0 +1,100 @@
+#ifndef NULLSOFT_SEND_TO_HELPER_CLASS_H
+#define NULLSOFT_SEND_TO_HELPER_CLASS_H
+
+#include "../../General/gen_ml/ml_ipc.h"
+#include "main.h"
+#include <strsafe.h>
+class SendToMenu
+{
+public:
+ SendToMenu()
+ {
+ memset(&sendTo, 0, sizeof(sendTo));
+ }
+ void AddHere(HWND hwnd, HMENU hMenu, int type, int simple = 0, int trueSourceType = 0)
+ {
+ sendTo.mode = 0;
+ sendTo.hwnd = 0;
+ sendTo.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ sendTo.mode = 1;
+ sendTo.hwnd = hwnd;
+ sendTo.data_type = type; //ML_TYPE_ITEMRECORDLIST;
+ sendTo.ctx[1] = simple;
+ sendTo.ctx[2] = trueSourceType;
+ sendTo.build_hMenu = hMenu;
+ }
+ }
+ bool WasClicked(int popUpReturnVal)
+ {
+ if (sendTo.mode == 2)
+ {
+ sendTo.menu_id = popUpReturnVal;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ return true;
+ }
+ return false;
+ }
+ void Cleanup()
+ {
+ if (sendTo.mode)
+ {
+ sendTo.mode = 4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ sendTo.build_hMenu = 0;
+ }
+
+ bool InitPopupMenu(WPARAM wParam)
+ {
+ if (wParam && (HMENU)wParam == sendTo.build_hMenu && sendTo.mode == 1)
+ {
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ sendTo.mode = 2;
+
+ return true;
+ }
+ return false;
+ }
+
+ // still need to free it on your own
+ void SendItemRecordList(itemRecordList *obj)
+ {
+ sendTo.data_type = ML_TYPE_ITEMRECORDLIST;
+ sendTo.mode = 3;
+ sendTo.data = (void*) & obj;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU);
+ }
+
+ void SendFilenames(const wchar_t *filenames)
+ {
+ sendTo.data_type = ML_TYPE_FILENAMESW;
+ sendTo.mode = 3;
+ sendTo.data = (void*)filenames;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU);
+ }
+
+ LRESULT SendPlaylist(mlPlaylist *playlist)
+ {
+ sendTo.data_type = ML_TYPE_PLAYLIST;
+ sendTo.mode = 3;
+ sendTo.data = (void*)playlist;
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU);
+ }
+
+ LRESULT SendPlaylists(mlPlaylist **playlists)
+ {
+ sendTo.data_type = ML_TYPE_PLAYLISTS;
+ sendTo.mode = 3;
+ sendTo.data = (void*)playlists;
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&sendTo, IPC_LIBRARY_SENDTOMENU);
+ }
+
+private:
+ librarySendToMenuStruct sendTo;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/api__ml_playlists.h b/Src/Plugins/Library/ml_playlists/api__ml_playlists.h
new file mode 100644
index 00000000..c4ec9f20
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/api__ml_playlists.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_API_H
+#define NULLSOFT_API_H
+
+#include "api/application/api_application.h"
+#define WASABI_API_APP applicationApi
+
+#include "api/service/api_service.h"
+#define WASABI_API_SVC serviceApi
+
+#include "api/syscb/api_syscb.h"
+#define WASABI_API_SYSCB sysCallbackApi
+
+#include "../playlist/api_playlists.h"
+extern api_playlists *playlistsApi;
+#define AGAVE_API_PLAYLISTS playlistsApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../Components/wac_downloadManager/wac_downloadManager_api.h"
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/main.h b/Src/Plugins/Library/ml_playlists/main.h
new file mode 100644
index 00000000..2aa135fe
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/main.h
@@ -0,0 +1,148 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_MAIN_H
+#define NULLSOFT_ML_PLAYLISTS_MAIN_H
+
+#include <windows.h>
+#include <shlwapi.h>
+#include "resource.h"
+
+#include "../../Plugins/General/gen_ml/ml.h"
+#include "../../Plugins/General/gen_ml/ml_ipc.h"
+#include "../../Plugins/General/gen_ml/ml_ipc_0313.h"
+#include "Winamp/wa_ipc.h"
+
+#include "../../Plugins/General/gen_ml/config.h"
+#include "PlaylistInfo.h"
+
+#include "nu/MediaLibraryInterface.h"
+#include "../../Plugins/General/gen_ml/menu.h"
+
+#include "replicant/nu/AutoWide.h"
+#include "replicant/nu/AutoChar.h"
+
+#include "nu/DialogSkinner.h"
+#include "api__ml_playlists.h"
+#include "..\..\..\nu\AutoLock.h"
+
+#include "ml_downloads/DownloadStatus.h"
+#include "ml_downloads/DownloadsDialog.h"
+#include "ml_downloads/Downloaded.h"
+
+#include <map>
+
+#define WINAMP_MANAGEPLAYLISTS 40385
+#define ID_DOSHITMENU_ADDNEWPLAYLIST 40031
+
+#ifndef FILENAME_SIZE
+#define FILENAME_SIZE (MAX_PATH * 4)
+#endif
+
+INT_PTR pluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+extern INT_PTR playlistsTreeId, playlistsCloudTreeId;
+extern HNAVITEM playlistItem;
+extern int imgPL, imgCloudPL;
+extern wchar_t current_playing[FILENAME_SIZE];
+INT_PTR LoadPlaylist(INT_PTR treeId);
+void Playlists_ReplaceBadPathChars(LPWSTR pszPath);
+extern HMENU wa_playlists_cmdmenu;
+void HookPlaylistEditor();
+void UnhookPlaylistEditor();
+void UpdateMenuItems(HWND hwndDlg, HMENU menu);
+
+INT_PTR CALLBACK view_playlistsDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+INT_PTR CALLBACK AddPlaylistDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK view_playlistDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+
+extern winampMediaLibraryPlugin plugin;
+extern HMENU g_context_menus, g_context_menus2, g_context_menus3;
+extern wchar_t g_path[MAX_PATH];
+static const bool ADD_TO_TREE = true;
+extern HINSTANCE cloud_hinst;
+extern int IPC_GET_CLOUD_HINST, IPC_GET_CLOUD_ACTIVE;
+extern int cloud_avail, normalimage, cloudImage, groupBtn, customAllowed, enqueuedef;
+extern HWND currentView;
+
+void AddPlaylist( int callback, const wchar_t *title, const wchar_t *filename, bool makeTree, int cloud, size_t numItems = 0, uint64_t length = 0 );
+void LoadPlaylists();
+void UpdatePlaylists();
+int AddToCloud();
+void playlists_Add(HWND parent, bool callback = true);
+void playlists_AddToCloudPrompt(HWND hwndDlg);
+
+enum { SORT_TITLE_ASCENDING, SORT_TITLE_DESCENDING, SORT_NUMBER_ASCENDING, SORT_NUMBER_DESCENDING };
+void playlists_Sort(size_t sort_type);
+
+wchar_t *createPlayListDBFileName(wchar_t *filename); // filename is ignored but used for temp space, make sure it's 1024+256 chars =)
+void Playlist_importFromWinamp();
+void Playlist_importFromFile(HWND dlgparent);
+void Playlist_importFromFolders(HWND dlgparent);
+bool FindTreeItem(INT_PTR treeId);
+
+void RenamePlaylist(GUID _guid, HWND parent);
+void DeletePlaylist(GUID _guid, HWND parent, bool confirm);
+
+void RefreshPlaylistsList();
+
+void HookMediaLibrary();
+void UnhookMediaLibrary();
+void playlist_ContextMenu(HWND hwndDlg, POINT p);
+
+extern C_Config *g_config;
+
+extern int (*warand)(void);
+
+void MakeTree(PlaylistInfo &playlist);
+void UpdateTree(PlaylistInfo &playlist, int tree_id);
+
+LRESULT pluginHandleIpcMessage(int msg, WPARAM param);
+extern HCURSOR hDragNDropCursor;
+void FormatLength(wchar_t *str, int length, int buf_len);
+
+extern int IPC_LIBRARY_SENDTOMENU;
+
+void Hook(HWND winamp);
+
+extern INT_PTR sendToIgnoreID;
+extern INT_PTR lastActiveID;
+void playlist_Reload(bool forced = false);
+void playlist_Save(HWND hwndDlg);
+void playlist_SaveGUID(GUID _guid);
+void playlist_ReloadGUID(GUID _guid);
+extern HWND activeHWND; // active playlist view
+
+int CALLBACK WINAPI _bcp( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);
+INT_PTR PlayPlaylist(INT_PTR treeId);
+
+INT_PTR playlists_OnClick(INT_PTR treeId, int clickType, HWND wnd);
+int playlists_OnKeyDown(int treeId, NMTVKEYDOWN *p, HWND hwndDlg);
+int playlists_OnDrag(int treeId, POINT *pt, int *type);
+int playlists_OnDrop(int treeId, POINT *pt, int destTreeId);
+
+int playlists_CloudAvailable();
+int playlists_CloudInstalled();
+
+/* SendTo.cpp */
+int playlists_BuildSendTo(int sourceType, INT_PTR context);
+void AddPlaylistFromFilenames(const char *filenames, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename=0);
+void AddPlaylistFromFilenamesW(const wchar_t *filenames, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename=0);
+void AddPlaylistFromItemRecordList(itemRecordList *obj, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename=0);
+void AddPlaylistFromItemRecordListW(itemRecordListW *obj, const wchar_t *playlistTitle, int makeTree, const wchar_t *filename=0);
+int playlists_OnDropTarget(int id, int type, INT_PTR data);
+int playlists_OnSendTo(int sourceType, INT_PTR data, int id);
+
+void SwapPlayEnqueueInMenu(HMENU listMenu);
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu);
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam);
+
+extern int uniqueAddress;
+
+typedef std::map<INT_PTR, GUID> TREE_TO_GUID_MAP;
+extern TREE_TO_GUID_MAP tree_to_guid_map;
+
+extern viewButtons view;
+
+#define DEFAULT_PL_SEND_TO 1
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/ml_playlists.cpp b/Src/Plugins/Library/ml_playlists/ml_playlists.cpp
new file mode 100644
index 00000000..1b81042a
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/ml_playlists.cpp
@@ -0,0 +1,237 @@
+//#define PLUGIN_NAME "Nullsoft Playlists"
+#define PLUGIN_VERSION L"1.78"
+
+#include "main.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#include "api__ml_playlists.h"
+#include "replicant/nu/AutoChar.h"
+#include "PlaylistsCOM.h"
+#include "api/service/waservicefactory.h"
+#include "PlaylistsCB.h"
+#include "playlist/plstring.h"
+
+#define PLAYLIST_IMAGE_INDEX 201
+#define PLAYLIST_CLOUD_IMAGE_INDEX 202
+
+PlaylistsCOM playlistsCOM;
+HMENU wa_playlists_cmdmenu = NULL;
+HMENU wa_play_menu = NULL;
+INT_PTR playlistsTreeId = 0, playlistsCloudTreeId = 3002;
+HNAVITEM playlistItem = 0;
+wchar_t g_path[ MAX_PATH ] = { 0 };
+HMENU g_context_menus = 0, g_context_menus2 = 0, g_context_menus3 = 0;
+int Init();
+void Quit();
+int( *warand )( void ) = 0;
+INT_PTR sendToIgnoreID = 0;
+int IPC_LIBRARY_PLAYLISTS_REFRESH = -1, IPC_CLOUD_ENABLED = -1;
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_playlists.dll)",
+ Init,
+ Quit,
+ pluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+HCURSOR hDragNDropCursor;
+C_Config *g_config = 0;
+int imgPL = 0, imgCloudPL = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_application *WASABI_API_APP = 0;
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+api_downloadManager *WAC_API_DOWNLOADMANAGER = 0;
+api_syscb *WASABI_API_SYSCB = 0;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
+PlaylistsCB playlistsCB;
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+
+template <class api_t>
+api_t *GetService( GUID serviceGUID )
+{
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( serviceGUID );
+ if ( sf )
+ return (api_t *)sf->getInterface();
+ else
+ return 0;
+}
+
+inline void ReleaseService( GUID serviceGUID, void *service )
+{
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( serviceGUID );
+ if ( sf )
+ sf->releaseInterface( service );
+}
+
+wchar_t prefsname[ 64 ] = { 0 };
+extern WORD waMenuID;
+int Init()
+{
+ waMenuID = (WORD)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_REGISTER_LOWORD_COMMAND );
+
+ AGAVE_API_PLAYLISTMANAGER = GetService<api_playlistmanager>( api_playlistmanagerGUID );
+ if ( !AGAVE_API_PLAYLISTMANAGER ) // no sense in continuing
+ return ML_INIT_FAILURE;
+
+ AGAVE_API_PLAYLISTS = GetService<api_playlists>( api_playlistsGUID );
+
+ if ( !AGAVE_API_PLAYLISTS ) // no sense in continuing
+ return ML_INIT_FAILURE;
+
+ WAC_API_DOWNLOADMANAGER = GetService<api_downloadManager>( DownloadManagerGUID );
+
+ WASABI_API_APP = GetService<api_application>( applicationApiServiceGuid );
+ WASABI_API_SYSCB = GetService<api_syscb>( syscbApiServiceGuid );
+
+ WASABI_API_SYSCB->syscb_registerCallback( &playlistsCB );
+
+ WASABI_API_EXPLORERFINDFILE = GetService<api_explorerfindfile>( ExplorerFindFileApiGUID );
+
+ // Hook(plugin.hwndWinampParent);
+ warand = ( int( * )( void ) )SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_RANDFUNC );
+
+ // need to get WASABI_API_APP first
+ plstring_init();
+
+ // loader so that we can get the localisation service api for use
+ WASABI_API_LNG = GetService<api_language>( languageApiGUID );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlPlaylistsLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ swprintf( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_PLAYLISTS ), PLUGIN_VERSION );
+ plugin.description = (char *)szDescription;
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ mediaLibrary.GetWinampIni(); // to prevent a SendMessage hang later
+
+ mediaLibrary.AddDispatch( L"Playlists", &playlistsCOM );
+ wchar_t inifile[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins", inifile, MAX_PATH );
+ CreateDirectoryW( inifile, NULL );
+ mediaLibrary.BuildPath( L"Plugins\\gen_ml.ini", inifile, MAX_PATH );
+ g_config = new C_Config( inifile );
+ enqueuedef = g_config->ReadInt( L"enqueuedef", 0 );
+
+ // if m3udir has been changed (not the same as inidir) then
+ // we attempt to use the same folder for our playlist files
+ const wchar_t *m3udir = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETM3UDIRECTORYW );
+ const wchar_t *inidir = mediaLibrary.GetIniDirectoryW();
+ if ( !lstrcmpiW( m3udir, inidir ) )
+ mediaLibrary.BuildPath( L"Plugins\\ml\\playlists", g_path, MAX_PATH );
+ else
+ lstrcpynW( g_path, m3udir, MAX_PATH );
+
+ CreateDirectoryW( g_path, NULL );
+
+ g_context_menus = WASABI_API_LOADMENU( IDR_MENU1 );
+ g_context_menus2 = WASABI_API_LOADMENU( IDR_MENU1 ); // this and next one are used for the combined buttons
+ g_context_menus3 = WASABI_API_LOADMENU( IDR_MENU1 ); // so we don't have to mess around the translators etc
+
+ HookMediaLibrary();
+
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+
+ HMENU wa_plcontext_menu = GetSubMenu( (HMENU)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, -1, IPC_GET_HMENU ), 2 );
+ if ( wa_plcontext_menu )
+ {
+ wa_playlists_cmdmenu = GetSubMenu( wa_plcontext_menu, 4 );
+ if ( wa_playlists_cmdmenu )
+ {
+ MENUITEMINFO i = { sizeof( i ), MIIM_TYPE, MFT_SEPARATOR, 0, 0 };
+ InsertMenuItem( wa_playlists_cmdmenu, 9, TRUE, &i );
+ MENUITEMINFO j = { sizeof( i ), MIIM_ID | MIIM_STATE | MIIM_TYPE, MFT_STRING, 0, WINAMP_MANAGEPLAYLISTS };
+ j.dwTypeData = WASABI_API_LNGSTRINGW( IDS_MANAGE_PLAYLISTS );
+ InsertMenuItem( wa_playlists_cmdmenu, 10, TRUE, &j );
+ }
+ }
+
+ IPC_CLOUD_ENABLED = SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "WinampCloudEnabled", IPC_REGISTER_WINAMP_IPCMESSAGE );
+ IPC_LIBRARY_PLAYLISTS_REFRESH = SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "ml_playlist_refresh", IPC_REGISTER_WINAMP_IPCMESSAGE );
+ wa_play_menu = GetSubMenu( (HMENU)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_HMENU ), 2 );
+
+ // lets extend menu that called on button press
+ int IPC_GET_ML_HMENU = (int)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "LibraryGetHmenu", IPC_REGISTER_WINAMP_IPCMESSAGE );
+ HMENU context_menu = (HMENU)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_ML_HMENU );
+
+ if ( context_menu )
+ {
+ HMENU btnMenu = GetSubMenu( context_menu, 0 );
+ if ( btnMenu )
+ {
+ MENUITEMINFO mii = { sizeof( MENUITEMINFO ) };
+
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ mii.fState = MFS_ENABLED;
+ InsertMenuItem( btnMenu, 0, TRUE, &mii );
+
+ mii.fMask = MIIM_TYPE | MIIM_ID;
+ mii.fType = MFT_STRING;
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW( IDS_MANAGE_PLAYLISTS );
+ mii.cch = (unsigned int)lstrlen( mii.dwTypeData );
+ mii.wID = WINAMP_MANAGEPLAYLISTS;
+ InsertMenuItem( btnMenu, 0, TRUE, &mii );
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW( IDS_NEW_PLAYLIST );
+ mii.cch = (unsigned int)lstrlen( mii.dwTypeData );
+ mii.wID = ID_DOSHITMENU_ADDNEWPLAYLIST;
+ InsertMenuItem( btnMenu, 0, TRUE, &mii );
+ }
+ }
+
+ imgPL = mediaLibrary.AddTreeImage( IDB_TREEITEM_PLAYLIST, PLAYLIST_IMAGE_INDEX, (BMPFILTERPROC)FILTER_DEFAULT1 );
+ imgCloudPL = mediaLibrary.AddTreeImage( IDB_TREEITEM_CLOUD_PLAYLIST, PLAYLIST_CLOUD_IMAGE_INDEX, (BMPFILTERPROC)FILTER_DEFAULT1 );
+
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF( IDS_PLAYLISTS, prefsname, 64 );
+ nis.item.pszInvariant = L"Playlists";
+ nis.item.id = playlistsTreeId = 3001; // for backwards compatability
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_ITEMID | NIMF_STYLE;
+
+ playlistItem = MLNavCtrl_InsertItem( plugin.hwndLibraryParent, &nis );
+
+ LoadPlaylists();
+
+ Hook( plugin.hwndWinampParent );
+ HookPlaylistEditor();
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ AGAVE_API_PLAYLISTS->Flush();
+ WASABI_API_SYSCB->syscb_deregisterCallback( &playlistsCB );
+
+ ReleaseService( api_playlistmanagerGUID, AGAVE_API_PLAYLISTMANAGER );
+ ReleaseService( api_playlistsGUID, AGAVE_API_PLAYLISTS );
+ ReleaseService( DownloadManagerGUID, WAC_API_DOWNLOADMANAGER );
+ ReleaseService( applicationApiServiceGuid, WASABI_API_APP );
+ ReleaseService( syscbApiServiceGuid, WASABI_API_SYSCB );
+ ReleaseService( ExplorerFindFileApiGUID, WASABI_API_EXPLORERFINDFILE );
+
+ UnhookPlaylistEditor();
+ UnhookMediaLibrary();
+
+ delete g_config;
+}
+
+extern "C" __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/ml_playlists.rc b/Src/Plugins/Library/ml_playlists/ml_playlists.rc
new file mode 100644
index 00000000..9ee4e1ed
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/ml_playlists.rc
@@ -0,0 +1,498 @@
+// 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_BROWSE_PLFLD DIALOGEX 0, 0, 254, 29
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+CONTROL "Use original playlist instead of a Winamp managed copy", IDC_EXTERNAL,
+"Button", BS_AUTOCHECKBOX | WS_TABSTOP, 5, 4, 193, 10
+CONTROL "Recursive scan", IDC_CHECK1, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 5, 17, 64, 10
+CONTROL "Available in the Cloud", IDC_CLOUD, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 83, 17, 85, 10
+END
+
+IDD_VIEW_PLAYLISTS DIALOGEX 0, 0, 184, 266
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+CONTROL "", IDC_PLAYLIST_LIST, "SysListView32", LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP, 0, 0, 182, 253
+CONTROL "View Playlist", IDC_VIEWLIST, "Button", BS_OWNERDRAW | WS_TABSTOP, 0, 255, 37, 11
+CONTROL "Play", IDC_PLAY, "Button", BS_OWNERDRAW | WS_TABSTOP, 39, 255, 22, 11
+CONTROL "Enqueue", IDC_ENQUEUE, "Button", BS_OWNERDRAW | WS_TABSTOP, 63, 255, 36, 11
+CONTROL "New...", IDC_CREATENEWPL, "Button", BS_OWNERDRAW | WS_TABSTOP, 101, 255, 22, 11
+CONTROL "Import", IDC_IMPORT, "Button", BS_OWNERDRAW | WS_TABSTOP, 125, 255, 29, 11
+CONTROL "Save...", IDC_SAVE, "Button", BS_OWNERDRAW | WS_TABSTOP, 156, 255, 28, 11
+END
+
+IDD_RENAME_PLAYLIST DIALOGEX 0, 0, 186, 42
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Rename Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+LTEXT "Name:", IDC_STATIC, 5, 9, 22, 8
+EDITTEXT IDC_NAME, 35, 7, 145, 12, ES_AUTOHSCROLL
+DEFPUSHBUTTON "OK", IDOK, 76, 25, 50, 13
+PUSHBUTTON "Cancel", IDCANCEL, 130, 25, 50, 13
+END
+
+IDD_ADD_PLAYLIST DIALOGEX 0, 0, 186, 42
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+LTEXT "Name:", IDC_STATIC, 5, 9, 22, 8
+EDITTEXT IDC_NAME, 35, 7, 145, 12, ES_AUTOHSCROLL
+DEFPUSHBUTTON "OK", IDOK, 76, 25, 50, 13
+PUSHBUTTON "Cancel", IDCANCEL, 130, 25, 50, 13
+END
+
+IDD_VIEW_PLAYLIST DIALOGEX 0, 0, 339, 59
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+CONTROL "", IDC_PLAYLIST_EDITOR, "SysListView32", LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP, 0, 0, 339, 34
+LTEXT "", IDC_PLSTATUS, 0, 36, 339, 10, SS_CENTERIMAGE | SS_ENDELLIPSIS
+CONTROL "Play", IDC_PLAY, "Button", BS_OWNERDRAW | WS_TABSTOP, 0, 48, 27, 11
+CONTROL "Send-To", IDC_BURN, "Button", BS_OWNERDRAW | WS_TABSTOP, 34, 48, 44, 11
+CONTROL "Add", IDC_ADD, "Button", BS_OWNERDRAW | WS_TABSTOP, 85, 48, 30, 11
+CONTROL "Rem", IDC_REM, "Button", BS_OWNERDRAW | WS_TABSTOP, 122, 48, 30, 11
+CONTROL "Sel", IDC_SEL, "Button", BS_OWNERDRAW | WS_TABSTOP, 159, 48, 30, 11
+CONTROL "Misc", IDC_MISC, "Button", BS_OWNERDRAW | WS_TABSTOP, 196, 48, 30, 11
+CONTROL "Manage Playlist", IDC_LIST, "Button", BS_OWNERDRAW | WS_TABSTOP, 233, 48, 69, 11
+CONTROL "Save", IDC_SAVE_PL, "Button", BS_OWNERDRAW | WS_DISABLED | WS_TABSTOP, 309, 48, 30, 11
+END
+
+IDD_EDIT_FN DIALOGEX 0, 0, 262, 95
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Edit Playlist Entry"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+RTEXT "Old Entry:", IDC_STATIC, 4, 7, 40, 12, SS_CENTERIMAGE
+EDITTEXT IDC_OLD, 49, 7, 206, 12, ES_AUTOHSCROLL | ES_READONLY | WS_DISABLED
+RTEXT "Old Title:", IDC_STATIC, 4, 24, 40, 12, SS_CENTERIMAGE
+EDITTEXT IDC_OLD_TITLE, 49, 24, 206, 12, ES_AUTOHSCROLL | ES_READONLY | WS_DISABLED
+RTEXT "New Entry:", IDC_STATIC, 4, 41, 40, 12, SS_CENTERIMAGE
+EDITTEXT IDC_NEW, 49, 41, 188, 12, ES_AUTOHSCROLL
+PUSHBUTTON "&...", IDC_PLAYLIST_EDIT_ENTRY_BROWSE, 238, 40, 17, 14
+RTEXT "New Title:", IDC_STATIC, 4, 58, 40, 12, SS_CENTERIMAGE
+EDITTEXT IDC_NEW_TITLE, 49, 58, 206, 12, ES_AUTOHSCROLL
+DEFPUSHBUTTON "OK", IDOK, 5, 76, 50, 14
+PUSHBUTTON "Cancel", IDCANCEL, 61, 76, 50, 14
+END
+
+IDD_SELECT_PLAYLIST DIALOGEX 0, 0, 209, 42
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Select Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+COMBOBOX IDC_PLAYLISTS, 4, 7, 200, 158, CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+PUSHBUTTON "Cancel", IDCANCEL, 154, 25, 50, 13
+DEFPUSHBUTTON "OK", IDOK, 100, 25, 50, 13
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_ADD_CLOUD_PLAYLIST$( DISABLED ) DIALOGEX 0, 0, 186, 42
+#else
+IDD_ADD_CLOUD_PLAYLIST DIALOGEX 0, 0, 186, 42
+#endif
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+LTEXT "Name:", IDC_STATIC, 5, 9, 22, 8
+EDITTEXT IDC_NAME, 35, 7, 145, 12, ES_AUTOHSCROLL
+CONTROL "&Available in the Cloud", IDC_CLOUD, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 6, 26, 88, 11
+DEFPUSHBUTTON "&OK", IDOK, 98, 25, 36, 13
+PUSHBUTTON "&Cancel", IDCANCEL, 138, 25, 42, 13
+END
+#endif
+
+IDD_IMPORT_PLFLD DIALOGEX 0, 0, 400, 20
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+CONTROL "Available in the Cloud", IDC_CLOUD, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 67, 3, 85, 10
+CONTROL "Use original playlist instead of a Winamp managed copy", IDC_EXTERNAL,
+"Button", BS_AUTOCHECKBOX | WS_TABSTOP, 167, 3, 193, 10
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+IDD_BROWSE_PLFLD, DIALOG
+BEGIN
+RIGHTMARGIN, 109
+BOTTOMMARGIN, 18
+END
+
+IDD_RENAME_PLAYLIST, DIALOG
+BEGIN
+LEFTMARGIN, 5
+RIGHTMARGIN, 180
+TOPMARGIN, 7
+BOTTOMMARGIN, 38
+END
+
+IDD_ADD_PLAYLIST, DIALOG
+BEGIN
+LEFTMARGIN, 5
+RIGHTMARGIN, 180
+TOPMARGIN, 7
+BOTTOMMARGIN, 38
+END
+
+IDD_VIEW_PLAYLIST, DIALOG
+BEGIN
+RIGHTMARGIN, 295
+END
+
+IDD_EDIT_FN, DIALOG
+BEGIN
+LEFTMARGIN, 5
+RIGHTMARGIN, 254
+RIGHTMARGIN, 255
+TOPMARGIN, 7
+BOTTOMMARGIN, 90
+END
+
+IDD_SELECT_PLAYLIST, DIALOG
+BEGIN
+LEFTMARGIN, 4
+RIGHTMARGIN, 204
+TOPMARGIN, 7
+BOTTOMMARGIN, 38
+END
+
+"IDD_ADD_CLOUD_PLAYLIST$(DISABLED)", DIALOG
+BEGIN
+LEFTMARGIN, 5
+RIGHTMARGIN, 180
+TOPMARGIN, 7
+BOTTOMMARGIN, 38
+END
+
+IDD_IMPORT_PLFLD, DIALOG
+BEGIN
+RIGHTMARGIN, 227
+BOTTOMMARGIN, 15
+END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_MENU1 MENU
+BEGIN
+POPUP "Playlist"
+BEGIN
+MENUITEM "Play\tEnter", IDC_PLAY
+MENUITEM "Enqueue\tShift+Enter", IDC_ENQUEUE
+POPUP "Send to:"
+BEGIN
+MENUITEM "", 40003
+END
+MENUITEM SEPARATOR
+MENUITEM "View Playlist\tCtrl+V", IDC_VIEWLIST
+MENUITEM SEPARATOR
+MENUITEM "Rename Playlist...\tF2", IDC_RENAME
+MENUITEM "New Playlist...\tShift+Insert", IDC_NEWPLAYLIST
+MENUITEM SEPARATOR
+MENUITEM "Delete Playlist\tDel", IDC_DELETE
+END
+POPUP "Playlistsmenu"
+BEGIN
+MENUITEM "New Playlist...", IDC_NEWPLAYLIST
+MENUITEM SEPARATOR
+MENUITEM "Import playlist from file...", IDC_IMPORT_PLAYLIST_FROM_FILE
+MENUITEM "Import active (current) playlist", IDC_IMPORT_WINAMP_PLAYLIST
+MENUITEM "Import playlist from folder...", 40010
+MENUITEM SEPARATOR
+POPUP "Sort playlists by:"
+BEGIN
+MENUITEM "Title alphabetically (A->Z)", ID_SORTPLAYLIST_TITLE_A_Z
+MENUITEM "Title alphabetically (Z->A)", ID_SORTPLAYLIST_TITLE_Z_A
+MENUITEM "Number of items ascending", ID_SORTPLAYLIST_NUMBEROFITEMSASCENDING
+MENUITEM "Number of items descending", ID_SORTPLAYLIST_NUMBEROFITEMSDESCENDING
+END
+MENUITEM SEPARATOR
+MENUITEM "Help", ID_PLAYLISTS_HELP
+END
+POPUP "PlaylistsImport"
+BEGIN
+MENUITEM "Import playlists from folder...", ID_PLAYLISTSIMPORT_IMPORTPLAYLISTSFROMFOLDER
+MENUITEM "Import (active) current playlist", IDC_IMPORT_WINAMP_PLAYLIST
+MENUITEM "Import playlist from file...", IDC_IMPORT_PLAYLIST_FROM_FILE
+END
+POPUP "PEditorMenus"
+BEGIN
+POPUP "RightClick"
+BEGIN
+MENUITEM "Play selection\tEnter", IDC_PLAY
+MENUITEM "Enqueue selection\tShift+Enter", IDC_ENQUEUE
+POPUP "Send to:"
+BEGIN
+MENUITEM "", 40003
+END
+MENUITEM SEPARATOR
+MENUITEM "Remove item(s)\tDelete", IDC_DELETE
+MENUITEM "Crop item(s)\tCtrl+Delete", IDC_CROP
+MENUITEM SEPARATOR
+MENUITEM "Download\t", IDC_PLAYLIST_DOWNLOAD_ENTRY
+MENUITEM SEPARATOR
+MENUITEM "View f&ile info...\tAlt+3", IDC_PLAYLIST_VIEW_FILE_INFO
+MENUITEM "Playlist entry\tCtrl+E", IDC_PLAYLIST_EDIT_ENTRY
+MENUITEM SEPARATOR
+MENUITEM "Explore item folder\tCtrl+F", IDC_PLAYLIST_EXPLOREITEMFOLDER
+END
+POPUP "Selection"
+BEGIN
+MENUITEM "Select &all\tCtrl+A", IDC_PLAYLIST_SELECT_ALL
+MENUITEM "Select &none", IDC_PLAYLIST_SELECT_NONE
+MENUITEM "Invert selection\tCtrl+I", IDC_PLAYLIST_INVERT_SELECTION
+END
+POPUP "Remove"
+BEGIN
+MENUITEM "Remove all &dead files", IDC_PLAYLIST_REMOVE_DEAD
+MENUITEM SEPARATOR
+MENUITEM "Remove all items", IDC_PLAYLIST_REMOVE_ALL
+MENUITEM "Crop selected items\tCtrl+Delete", IDC_CROP
+MENUITEM "Remove selected items\tDelete", IDC_DELETE
+MENUITEM "Physically remove selected item(s)", IDC_PLAYLIST_RECYCLE_SELECTED
+END
+POPUP "Add"
+BEGIN
+MENUITEM "Add file(s)\tL", IDC_ADD_FILES
+MENUITEM "Add folder\tShift+L", IDC_ADD_DIRECTORY
+MENUITEM "Add URL\tCtrl+L", IDC_ADD_LOCATION
+END
+POPUP "Misc"
+BEGIN
+MENUITEM "&Randomize list\tCtrl+Shift+R", IDC_PLAYLIST_RANDOMIZE
+MENUITEM "R&everse list\tCtrl+R", IDC_PLAYLIST_REVERSE
+MENUITEM SEPARATOR
+MENUITEM "Sort list by &path and filename", IDC_PLAYLIST_SORT_PATH
+MENUITEM "Sort list by &filename", IDC_PLAYLIST_SORT_FILENAME
+MENUITEM "Sort list by &title", IDC_PLAYLIST_SORT_TITLE
+MENUITEM SEPARATOR
+MENUITEM "Generate HTML playlist\tCtrl+Alt+G", ID_PLAYLIST_GENERATE_HTML
+MENUITEM SEPARATOR
+MENUITEM "Rebuild titles on selection\tCtrl+Shift+E", IDC_PLAYLIST_RESET_CACHE
+END
+POPUP "List"
+BEGIN
+MENUITEM "Export playlist...", IDC_EXPORT_PLAYLIST
+POPUP "Send playlist to"
+BEGIN
+MENUITEM "", 40003
+END
+MENUITEM SEPARATOR
+MENUITEM "Import playlist from file...", IDC_IMPORT_PLAYLIST_FROM_FILE
+MENUITEM "Import active playlist", IDC_IMPORT_WINAMP_PLAYLIST
+END
+END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_PLAYLIST BITMAP "resources\\ti_playlist_16x16x16.bmp"
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD_PLAYLIST$( DISABLED ) BITMAP "resources\\ti_cloud_playlist_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD_PLAYLIST BITMAP "resources\\ti_cloud_playlist_16x16x16.bmp"
+#endif
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_PLS_ACCELERATORS ACCELERATORS
+BEGIN
+VK_DELETE, IDC_DELETE, VIRTKEY, NOINVERT
+VK_RETURN, IDC_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+VK_INSERT, IDC_NEWPLAYLIST, VIRTKEY, SHIFT, NOINVERT
+VK_RETURN, IDC_PLAY, VIRTKEY, NOINVERT
+VK_F2, IDC_RENAME, VIRTKEY, NOINVERT
+"V", IDC_VIEWLIST, VIRTKEY, CONTROL, NOINVERT
+VK_RETURN, IDC_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+IDR_VIEW_PL_ACCELERATORS ACCELERATORS
+BEGIN
+"3", IDC_PLAYLIST_VIEW_FILE_INFO, VIRTKEY, ALT, NOINVERT
+"A", IDC_PLAYLIST_SELECT_ALL, VIRTKEY, CONTROL, NOINVERT
+"E", IDC_PLAYLIST_EDIT_ENTRY, VIRTKEY, CONTROL, NOINVERT
+"E", IDC_PLAYLIST_RESET_CACHE, VIRTKEY, CONTROL, ALT, NOINVERT
+"F", IDC_PLAYLIST_EXPLOREITEMFOLDER, VIRTKEY, CONTROL, NOINVERT
+"G", ID_PLAYLIST_GENERATE_HTML, VIRTKEY, CONTROL, ALT, NOINVERT
+"I", IDC_PLAYLIST_INVERT_SELECTION, VIRTKEY, CONTROL, NOINVERT
+"L", IDC_ADD_FILES, VIRTKEY, NOINVERT
+"L", IDC_ADD_LOCATION, VIRTKEY, CONTROL, NOINVERT
+"L", IDC_ADD_DIRECTORY, VIRTKEY, SHIFT, NOINVERT
+"R", IDC_PLAYLIST_REVERSE, VIRTKEY, CONTROL, NOINVERT
+"R", IDC_PLAYLIST_RANDOMIZE, VIRTKEY, SHIFT, CONTROL, NOINVERT
+VK_DELETE, IDC_DELETE, VIRTKEY, NOINVERT
+VK_DELETE, IDC_CROP, VIRTKEY, CONTROL, NOINVERT
+VK_F2, IDC_RENAME, VIRTKEY, NOINVERT
+VK_INSERT, IDC_NEWPLAYLIST, VIRTKEY, SHIFT, NOINVERT
+VK_RETURN, IDC_PLAY, VIRTKEY, NOINVERT
+VK_RETURN, IDC_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+VK_RETURN, IDC_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+IDS_NULLSOFT_PLAYLISTS "Nullsoft Playlists v%s"
+65535 "{5E766B4F-818E-4f14-9C42-0902B2C571DC}"
+END
+
+STRINGTABLE
+BEGIN
+IDS_IMPORT_PLAYLIST "Import playlist"
+IDS_APPEND_IMPORTED_PLAYLIST
+"Would you like to append the imported playlist to this playlist?\n\nSelect NO to overwrite this playlist with the imported playlist\nSelect YES to append the imported playlist to this playlist\nSelect CANCEL to not do anything"
+IDS_LIBRARY_QUESTION "Library Question"
+IDS_APPEND_ACTIVE_PLAYLIST
+"Would you like to append the active playlist to this playlist?\n\nSelect NO to overwrite this playlist with the active playlist\nSelect YES to append the active playlist to this playlist\nSelect CANCEL to not do anything"
+IDS_EXPORT_PLAYLIST "Export playlist"
+IDS_IMPORTED_PLAYLIST "Imported Playlist"
+IDS_ITEM "item"
+IDS_ADD_DIR_TO_PLAYLIST "Add directory to playlist"
+IDS_ADD_FILES_TO_PLAYLIST "Add file(s) to playlist"
+IDS_PLAYLISTS "Playlists"
+IDS_MANAGE_PLAYLISTS "&Manage Playlists..."
+IDS_NEW_PLAYLIST "New Play&list...\tShift+Insert"
+IDS_ENTER_A_NAME "You must enter a name!"
+IDS_ERROR "Error"
+END
+
+STRINGTABLE
+BEGIN
+IDS_SENDTO_NEW_PLAYLIST "New Playlist"
+IDS_SENDTO_ML_PLAYLISTS "Library Playlists"
+IDS_SENDTO_PLAYLIST "Library Playlist"
+IDS_IMPORT_PLAYLIST_FROM_FOLDER "Import playlist from folder"
+IDS_CONFIRM_DELETION "Are you sure you want to delete the selected playlist(s)"
+IDS_CONFIRMATION "Confirmation"
+IDS_PLAYLIST_TITLE "Playlist Title"
+IDS_ITEMS "Items"
+IDS_TIME "Time"
+IDS_ERROR_DELETING_FILES "Error deleting files"
+IDS_X_OF_X_SELECTED "%d/%d %s selected (%s/%s)"
+IDS_X_SELECTED "%d %s (%s)"
+IDS_ITEMS_LOWER "items"
+IDS_TITLE "Title"
+IDS_NO_PLAYLIST_IN_LIBRARY "No Playlist in Library"
+IDS_OPEN_PLAYLIST_FROM_ML "Open playlist from &Library"
+END
+
+STRINGTABLE
+BEGIN
+IDS_PLAYLIST_FROM_ML "Playlist from &Library"
+IDS_PLAYLIST_ERROR "Cannot save playlist: Error writing to file or file is read-only"
+IDS_PLAYLIST_ERROR_TITLE "Error saving playlist"
+IDS_DAY "day"
+IDS_DAYS "days"
+IDS_PL_FILE_MNGT "Winamp Playlist Management"
+IDS_EXTERNAL_CHECKED "Checking this will make Winamp use the original playlist file(s) instead of creating a Winamp managed copy which is not synchronized with the original playlist file(s).\n\nIf checked, you need to ensure the playlist file(s) exist and will be accessible always otherwise they will not be useable within Winamp. All actions will be done on the original playlist file e.g. delete will remove the original file instead of just removing the Winamp managed copy."
+IDS_EXTERNAL_ALREADY_ADDED
+"The playlist you have selected has already been imported into Winamp and will not be re-added."
+IDS_SOURCE_PL_MISSING "\n\nThe source playlist file for this playlist view cannot be located.\nThis can happen if the playlist has been physically removed.\n\nThe missing source playlist file is:\n%s"
+END
+
+STRINGTABLE
+BEGIN
+IDS_BROWSE_FOR_PLEDIT_ENTRY "Browse for playlist entry..."
+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_playlists/ml_playlists.sln b/Src/Plugins/Library/ml_playlists/ml_playlists.sln
new file mode 100644
index 00000000..3f8e0d2d
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/ml_playlists.sln
@@ -0,0 +1,81 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_playlists", "ml_playlists.vcxproj", "{71726F38-0625-4E16-BA79-32FE4F83E1E4}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
+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
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Debug|Win32.ActiveCfg = Debug|Win32
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Debug|Win32.Build.0 = Debug|Win32
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Debug|x64.ActiveCfg = Debug|x64
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Debug|x64.Build.0 = Debug|x64
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Release|Win32.ActiveCfg = Release|Win32
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Release|Win32.Build.0 = Release|Win32
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Release|x64.ActiveCfg = Release|x64
+ {71726F38-0625-4E16-BA79-32FE4F83E1E4}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3815C7C8-CBE1-4F34-B885-16D73AD02A60}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj b/Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj
new file mode 100644
index 00000000..f86211b4
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj
@@ -0,0 +1,359 @@
+<?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>{71726F38-0625-4E16-BA79-32FE4F83E1E4}</ProjectGuid>
+ <RootNamespace>ml_playlists</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;..\..\..\dlmgr;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_PLAYLISTS_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4995;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>..\ml_downloads\$(PlatformShortName)_$(Configuration)\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <ProjectReference>
+ <UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
+ </ProjectReference>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_PLAYLISTS_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4267;4838;4477;4090;4995;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>..\ml_downloads\$(PlatformShortName)_$(Configuration)\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <ProjectReference>
+ <UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
+ </ProjectReference>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\dlmgr;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_PLAYLISTS_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4995;%(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>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_PLAYLISTS_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4267;4838;4477;4090;4995;%(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>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\replicant\nx\nx.vcxproj">
+ <Project>{57c90706-b25d-4aca-9b33-95cdb2427c27}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\WAT\WAT.vcxproj">
+ <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="..\..\..\playlist\plstring.h" />
+ <ClInclude Include="..\..\..\playlist\pl_entry.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="api__ml_playlists.h" />
+ <ClInclude Include="CurrentPlaylist.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="Playlist.h" />
+ <ClInclude Include="PlaylistDirectoryCallback.h" />
+ <ClInclude Include="PlaylistInfo.h" />
+ <ClInclude Include="playlists.h" />
+ <ClInclude Include="PlaylistsCB.h" />
+ <ClInclude Include="PlaylistsCOM.h" />
+ <ClInclude Include="PlaylistView.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="SendTo.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\playlist\plstring.cpp" />
+ <ClCompile Include="..\..\..\playlist\pl_entry.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="AddPlaylist.cpp" />
+ <ClCompile Include="CurrentPlaylist.cpp" />
+ <ClCompile Include="ml_playlists.cpp" />
+ <ClCompile Include="ml_subclass.cpp" />
+ <ClCompile Include="pe_subclass.cpp" />
+ <ClCompile Include="Playlist.cpp" />
+ <ClCompile Include="PlaylistDirectoryCallback.cpp" />
+ <ClCompile Include="PlaylistInfo.cpp" />
+ <ClCompile Include="playlists.cpp" />
+ <ClCompile Include="PlaylistsCB.cpp" />
+ <ClCompile Include="PlaylistsCOM.cpp" />
+ <ClCompile Include="PlaylistView.cpp" />
+ <ClCompile Include="pluginproc.cpp" />
+ <ClCompile Include="RenamePlaylist.cpp" />
+ <ClCompile Include="SendTo.cpp" />
+ <ClCompile Include="view_pl.cpp" />
+ <ClCompile Include="view_playlists.cpp" />
+ <ClCompile Include="wa_subclass.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="Design.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_playlists.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_cloud_playlist_16x16x16.bmp" />
+ <Image Include="resources\ti_playlist_16x16x16.bmp" />
+ </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_playlists/ml_playlists.vcxproj.filters b/Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj.filters
new file mode 100644
index 00000000..664a41d8
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/ml_playlists.vcxproj.filters
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="AddPlaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CurrentPlaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_playlists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pe_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistDirectoryCallback.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistInfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="playlists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistsCB.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistsCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wa_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_playlists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pl.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SendTo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RenamePlaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pluginproc.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\playlist\pl_entry.cpp">
+ <Filter>Source Files\playlist</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\playlist\plstring.cpp">
+ <Filter>Source Files\playlist</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_playlists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CurrentPlaylist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistDirectoryCallback.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistInfo.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="playlists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistsCB.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistsCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SendTo.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\playlist\pl_entry.h">
+ <Filter>Header Files\playlist</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\playlist\plstring.h">
+ <Filter>Header Files\playlist</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_cloud_playlist_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_playlist_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="Design.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{510f3e24-3519-4357-a412-31ca9ea4e679}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{bd3dfff8-2c37-43e7-8cb2-1d701dcaadaa}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{d90e2a08-7da5-425c-97e7-eb41aa99ab8f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{b45d999a-7960-4fef-b55a-90b9530879f2}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{f4f89e40-7fa7-4faa-bdc9-bd6428e5132c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{0ee115a9-708c-4311-859e-2aee4ffb1971}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\playlist">
+ <UniqueIdentifier>{2be30a31-bc6a-4220-9c58-832fd397d80a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{dd4f54cd-8631-41f9-92a8-1b4c37afa4ce}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{21b175b4-e364-44c4-96f0-e67663b09ada}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{973fb9f8-4c13-41dd-aa80-c87c5664b559}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\playlist">
+ <UniqueIdentifier>{fd06d71f-c851-433d-a4bb-6458c5452422}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{0df480a4-437b-429d-860f-7110e7d6c78d}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_playlists.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/ml_subclass.cpp b/Src/Plugins/Library/ml_playlists/ml_subclass.cpp
new file mode 100644
index 00000000..9a0cc007
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/ml_subclass.cpp
@@ -0,0 +1,271 @@
+#include <shlwapi.h>
+
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+#include "CurrentPlaylist.h"
+#include "SendTo.h"
+#include "Playlist.h"
+#include "api__ml_playlists.h"
+
+using namespace Nullsoft::Utility;
+WNDPROC ml_wndProc = 0;
+
+static INT_PTR CALLBACK AddPlaylistDialogProc_sc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static wchar_t *title;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ title = (wchar_t*)lParam;
+ PostMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_NAME), TRUE);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ wchar_t name[256] = {0};
+ GetDlgItemText(hwndDlg, IDC_NAME, name, 255);
+ name[255] = 0;
+ if (!name[0])
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_ENTER_A_NAME),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,32), MB_OK);
+ break;
+ }
+ lstrcpyn(title,name,256);
+ EndDialog(hwndDlg,1);
+ }
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void AddPlaylist(mlAddPlaylist *addPlaylist)
+{
+ PlaylistInfo pl;
+ wchar_t filename[1024+256] = {0}; // use a longer buffer than MAX_PATH here because createPlayListDBFileName needs it
+ if (addPlaylist->flags & PL_FLAGS_IMPORT)
+ {
+ createPlayListDBFileName(filename);
+ CopyFileW(addPlaylist->filename, filename, FALSE);
+ }
+ else
+ lstrcpynW(filename, addPlaylist->filename, MAX_PATH);
+
+ int numItems = 0;
+ int length = 0;
+
+ wchar_t title[256] = {0};
+
+ if(addPlaylist->playlistName)
+ lstrcpynW(title, addPlaylist->playlistName, 256);
+ else // prompt for name
+ {
+ if(WASABI_API_DIALOGBOXPARAMW((playlists_CloudAvailable() ? IDD_ADD_CLOUD_PLAYLIST : IDD_ADD_PLAYLIST),
+ plugin.hwndLibraryParent, AddPlaylistDialogProc_sc, (LPARAM)title) == 0)
+ { // the user hit cancel
+ return;
+ }
+ }
+
+ if (addPlaylist->numItems == -1 || addPlaylist->length == -1)
+ {
+ Playlist temp;
+ AGAVE_API_PLAYLISTMANAGER->Load(filename, &temp);
+ numItems = temp.GetNumItems();
+ for (size_t i = 0;i != numItems;i++)
+ {
+ int len = temp.GetItemLengthMilliseconds(i) / 1000;
+ if (len>=0)
+ length += len;
+ }
+ }
+ else
+ {
+ length = addPlaylist->length;
+ numItems = addPlaylist->numItems;
+ }
+
+ AddPlaylist(true, title, filename, !!(addPlaylist->flags & PL_FLAG_SHOW), g_config->ReadInt(L"cloud", 1), numItems, length);
+}
+
+static void MakePlaylist(mlMakePlaylistV2 *makePlaylist)
+{
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ switch (makePlaylist->type)
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ AddPlaylistFromFilenames((const char *)makePlaylist->data, makePlaylist->playlistName, makePlaylist->flags);
+ if (makePlaylist->flags & PL_FLAG_FILL_FILENAME && makePlaylist->size == sizeof(mlMakePlaylistV2))
+ {
+ // TODO: not guaranteed to be at the end
+ size_t last_index = AGAVE_API_PLAYLISTS->GetCount() - 1;
+ lstrcpynW(makePlaylist->filename, AGAVE_API_PLAYLISTS->GetFilename(last_index), MAX_PATH);
+ }
+ return ;
+
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ AddPlaylistFromFilenamesW((const wchar_t *)makePlaylist->data, makePlaylist->playlistName, makePlaylist->flags);
+ if (makePlaylist->flags & PL_FLAG_FILL_FILENAME && makePlaylist->size == sizeof(mlMakePlaylistV2))
+ {
+ // TODO: not guaranteed to be at the end
+ size_t last_index = AGAVE_API_PLAYLISTS->GetCount() - 1;
+ lstrcpynW(makePlaylist->filename, AGAVE_API_PLAYLISTS->GetFilename(last_index), MAX_PATH);
+ }
+ return ;
+
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_CDTRACKS:
+ AddPlaylistFromItemRecordList((itemRecordList *)makePlaylist->data, makePlaylist->playlistName, makePlaylist->flags);
+ if (makePlaylist->flags & PL_FLAG_FILL_FILENAME && makePlaylist->size == sizeof(mlMakePlaylistV2))
+ {
+ // TODO: not guaranteed to be at the end
+ size_t last_index = AGAVE_API_PLAYLISTS->GetCount() - 1;
+ lstrcpynW(makePlaylist->filename, AGAVE_API_PLAYLISTS->GetFilename(last_index), MAX_PATH);
+ }
+ return ;
+
+ case ML_TYPE_ITEMRECORDLISTW:
+ AddPlaylistFromItemRecordListW((itemRecordListW *)makePlaylist->data, makePlaylist->playlistName, makePlaylist->flags);
+ if (makePlaylist->flags & PL_FLAG_FILL_FILENAME && makePlaylist->size == sizeof(mlMakePlaylistV2))
+ {
+ // TODO: not guaranteed to be at the end
+ size_t last_index = AGAVE_API_PLAYLISTS->GetCount() - 1;
+ lstrcpynW(makePlaylist->filename, AGAVE_API_PLAYLISTS->GetFilename(last_index), MAX_PATH);
+ }
+ return ;
+ }
+}
+
+static int GetPlaylistInfo(mlPlaylistInfo *info)
+{
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+
+ size_t num = info->playlistNum;
+ if (num >= AGAVE_API_PLAYLISTS->GetCount())
+ return 0;
+
+ PlaylistInfo playlist(num);
+ lstrcpynW(info->filename, playlist.GetFilename(), MAX_PATH);
+ lstrcpynW(info->playlistName, playlist.GetName(), 128);
+ info->length = playlist.GetLength();
+ info->numItems = playlist.GetSize();
+
+ return 1;
+}
+
+static INT_PTR PlaylistIPC(int msg, INT_PTR param)
+{
+ switch (msg)
+ {
+ case ML_IPC_NEWPLAYLIST: playlists_Add((HWND)param); return 1;
+ case ML_IPC_IMPORTPLAYLIST: Playlist_importFromFile((HWND)param); return 1;
+ case ML_IPC_SAVEPLAYLIST: CurrentPlaylist_Export((HWND)param); return 1; // TODO: can we guarantee a currently active playlist?
+ case ML_IPC_IMPORTCURRENTPLAYLIST: Playlist_importFromWinamp(); return 1;
+ // play/load the playlist passed as param
+ case ML_IPC_PLAY_PLAYLIST: PlayPlaylist(param); return 1;
+ case ML_IPC_LOAD_PLAYLIST: LoadPlaylist(param); return 1;
+ case ML_IPC_GETPLAYLISTWND: return(INT_PTR)activeHWND;
+ case ML_IPC_PLAYLIST_ADD: AddPlaylist((mlAddPlaylist *)param); return 1;
+ case ML_IPC_PLAYLIST_MAKE: MakePlaylist((mlMakePlaylistV2 *)param); return 1;
+ case ML_IPC_PLAYLIST_COUNT: return AGAVE_API_PLAYLISTS->GetCount();
+ case ML_IPC_PLAYLIST_INFO: return GetPlaylistInfo((mlPlaylistInfo *)param);
+ }
+ return 0;
+}
+
+extern SendToMenu treeViewSendTo;
+INT_PTR CALLBACK MediaLibraryProcedure(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (treeViewSendTo.InitPopupMenu(wParam))
+ return 0;
+
+ case WM_COMMAND:
+ {
+ switch (LOWORD(wParam))
+ {
+ case WINAMP_MANAGEPLAYLISTS:
+ mediaLibrary.SelectTreeItem(playlistsTreeId);
+ return 1;
+ case ID_DOSHITMENU_ADDNEWPLAYLIST:
+ playlists_Add(hwndDlg);
+ return 1;
+ }
+ }
+ break;
+
+ case WM_ML_IPC:
+ {
+ INT_PTR res = PlaylistIPC(lParam, wParam);
+ if (res)
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, res);
+ return TRUE;
+ }
+ }
+ }
+ return CallWindowProc(ml_wndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+void HookMediaLibrary()
+{
+ ml_wndProc = (WNDPROC)SetWindowLongPtr(plugin.hwndLibraryParent, DWLP_DLGPROC, (LONG_PTR)MediaLibraryProcedure);
+}
+
+void UnhookMediaLibrary()
+{
+ SetWindowLongPtr(plugin.hwndLibraryParent, DWLP_DLGPROC, (LONG_PTR)ml_wndProc);
+}
+
+#define TREE_PLAYLIST_ID_START 3002
+
+INT_PTR LoadPlaylist(INT_PTR treeId)
+{
+ if (!FindTreeItem(treeId))
+ return 0;
+
+ wchar_t wstr[MAX_PATH+1] = {0};
+ { // scope for lock
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+
+ PlaylistInfo info;
+ info.Associate(treeId);
+
+ playlist_SaveGUID(info.playlist_guid);
+
+ memset(wstr, 0, sizeof(wstr)); // easy (but slow) double null terminate
+ PathCombineW(wstr, g_path, info.GetFilename());
+ }
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+ enqueueFileWithMetaStructW s;
+ s.filename = wstr;
+ s.title = 0;
+ s.ext = NULL;
+ s.length = -1;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ return 1;
+}
+
+INT_PTR PlayPlaylist(INT_PTR treeId)
+{
+ if (LoadPlaylist(treeId))
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ return 1;
+ }
+ else
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/pe_subclass.cpp b/Src/Plugins/Library/ml_playlists/pe_subclass.cpp
new file mode 100644
index 00000000..9f8a128f
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/pe_subclass.cpp
@@ -0,0 +1,63 @@
+#include "main.h"
+
+static HMENU last_playlistscmdmenu = NULL;
+static WNDPROC PE_oldWndProc;
+static WORD waCmdMenuID;
+
+static BOOL CALLBACK PE_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_COMMAND && wParam > 45000 && wParam < 55000)
+ {
+ if (LoadPlaylist(wParam - 45000))
+ return 0;
+ }
+ else if (uMsg == WM_INITMENUPOPUP)
+ {
+ HMENU hmenuPopup = (HMENU) wParam;
+ if (hmenuPopup == wa_playlists_cmdmenu)
+ {
+ if (!waCmdMenuID)
+ {
+ waCmdMenuID = (WORD)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_REGISTER_LOWORD_COMMAND);
+ }
+ if (last_playlistscmdmenu)
+ {
+ RemoveMenu(wa_playlists_cmdmenu, waCmdMenuID, MF_BYCOMMAND);
+ DestroyMenu(last_playlistscmdmenu);
+ last_playlistscmdmenu = NULL;
+ }
+ mlGetTreeStruct mgts = { 3001, 45000, -1};
+ last_playlistscmdmenu = (HMENU)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) &mgts, ML_IPC_GETTREE);
+ if (last_playlistscmdmenu)
+ {
+ MENUITEMINFOW menuItem = {sizeof(MENUITEMINFOW), MIIM_SUBMENU | MIIM_ID | MIIM_TYPE, MFT_STRING,
+ MFS_ENABLED, waCmdMenuID, last_playlistscmdmenu, NULL, NULL, NULL,
+ WASABI_API_LNGSTRINGW(IDS_OPEN_PLAYLIST_FROM_ML), 0};
+ // if there's no playlists then let the user know this
+ if(!AGAVE_API_PLAYLISTS->GetCount())
+ {
+ wchar_t buf[64] = {0};
+ DestroyMenu(last_playlistscmdmenu);
+ menuItem.hSubMenu = last_playlistscmdmenu = CreateMenu();
+ InsertMenuW(menuItem.hSubMenu, 0, MF_BYPOSITION | MF_STRING | MF_GRAYED, 0, WASABI_API_LNGSTRINGW_BUF(IDS_NO_PLAYLIST_IN_LIBRARY,buf,64));
+ }
+ InsertMenuItemW(wa_playlists_cmdmenu, 1, TRUE, &menuItem);
+ }
+ }
+ }
+ return CallWindowProc(PE_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+static HWND hwnd_pe = NULL;
+void HookPlaylistEditor()
+{
+ hwnd_pe =(HWND)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,IPC_GETWND_PE,IPC_GETWND);
+
+ if (hwnd_pe)
+ PE_oldWndProc=(WNDPROC) SetWindowLongPtr(hwnd_pe,GWLP_WNDPROC,(LONG_PTR)PE_newWndProc);
+}
+
+void UnhookPlaylistEditor()
+{
+ SetWindowLongPtr(hwnd_pe,GWLP_WNDPROC,(LONG_PTR)PE_oldWndProc);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/playlists.cpp b/Src/Plugins/Library/ml_playlists/playlists.cpp
new file mode 100644
index 00000000..24588f81
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/playlists.cpp
@@ -0,0 +1,185 @@
+#include "main.h"
+#include "replicant/nu/AutoWide.h"
+#include "replicant/nu/AutoLock.h"
+#include <algorithm>
+#include <strsafe.h>
+
+using namespace Nullsoft::Utility;
+
+TREE_TO_GUID_MAP tree_to_guid_map;
+
+bool FindTreeItem( INT_PTR treeId )
+{
+ TREE_TO_GUID_MAP::iterator itr = tree_to_guid_map.find( treeId );
+
+ return itr != tree_to_guid_map.end();
+}
+
+void MakeTree( PlaylistInfo &playlist )
+{
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.pszText = const_cast<wchar_t *>( AGAVE_API_PLAYLISTS->GetName( playlist.GetIndex() ) );
+ nis.item.mask = NIMF_TEXT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_ITEMID;
+ nis.item.id = playlist.treeId = 3002 + playlist.GetIndex();
+ nis.hParent = playlistItem;
+
+ if ( playlists_CloudInstalled() )
+ nis.item.iImage = nis.item.iSelectedImage = ( !playlist.GetCloud() ? normalimage : cloudImage );
+ else
+ nis.item.iImage = nis.item.iSelectedImage = normalimage;
+
+ if ( MLNavCtrl_InsertItem( plugin.hwndLibraryParent, &nis ) )
+ tree_to_guid_map[ playlist.treeId ] = playlist.playlist_guid;
+}
+
+void UpdateTree( PlaylistInfo &playlist, int tree_id )
+{
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ size_t index = playlist.GetIndex();
+
+ MLTREEITEMW updatedItem = { NULL,MLTI_TEXT,NULL };
+ updatedItem.id = tree_id;
+ updatedItem.title = const_cast<wchar_t *>( AGAVE_API_PLAYLISTS->GetName( index ) );
+ updatedItem.imageIndex = ( !playlist.GetCloud() ? imgPL : imgCloudPL );
+
+ mediaLibrary.SetTreeItem( updatedItem );
+
+ tree_to_guid_map[ tree_id ] = playlist.playlist_guid;
+}
+
+void AddPlaylist( int callback, const wchar_t *title, const wchar_t *filename, bool makeTree, int cloud, size_t numItems, uint64_t length )
+{
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ wchar_t fullFilename[ MAX_PATH ] = { 0 };
+
+ if ( PathIsFileSpecW( filename ) )
+ PathCombineW( fullFilename, g_path, filename );
+ else
+ lstrcpynW( fullFilename, filename, MAX_PATH );
+
+ size_t newIndex = AGAVE_API_PLAYLISTS->AddPlaylist_NoCallback( fullFilename, title );
+
+ // try to get a valid length of the playlist time
+ // (important for the playlists view otherwise looks silly with just the number shown)
+ if ( !length )
+ {
+ length = AGAVE_API_PLAYLISTMANAGER->GetLongLengthMilliseconds( fullFilename );
+ if ( length > 0 ) length /= 1000;
+ else length = 0;
+ }
+
+ if ( cloud )
+ AGAVE_API_PLAYLISTS->SetInfo( newIndex, api_playlists_cloud, &cloud, sizeof( cloud ) );
+
+ if ( numItems > 0 )
+ AGAVE_API_PLAYLISTS->SetInfo( newIndex, api_playlists_itemCount, &numItems, sizeof( numItems ) );
+
+ if ( length > 0 )
+ AGAVE_API_PLAYLISTS->SetInfo( newIndex, api_playlists_totalTime, &length, sizeof( length ) );
+
+ if ( callback )
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_ADDED, newIndex, ( callback - 1 ) );
+}
+
+bool LoadOldPlaylists()
+{
+ bool erased = false;
+ int nb = g_config->ReadInt( L"query_num", 0 );
+ for ( int i = 0; i < nb; i++ )
+ {
+ wchar_t qn[ 128 ] = { 0 }, qv[ 128 ] = { 0 }, qm[ 128 ] = { 0 }, qmet[ 128 ] = { 0 };
+ StringCchPrintfW( qn, 128, L"query%i_name", i + 1 );
+ StringCchPrintfW( qv, 128, L"query%i_val", i + 1 );
+ StringCchPrintfW( qm, 128, L"query%i_mode", i + 1 );
+ StringCchPrintfW( qmet, 128, L"query%i_meta", i + 1 );
+
+ int queryMode = g_config->ReadInt( qm, 0 );
+ if ( queryMode == 32 )
+ {
+ wchar_t *name = g_config->ReadString( qn, NULL );
+ if ( !name )
+ continue;
+
+ name = _wcsdup( name );
+
+ wchar_t *val = g_config->ReadString( qv, NULL );
+ if ( val )
+ val = _wcsdup( val );
+
+ wchar_t filename[ MAX_PATH ] = { 0 };
+ PathCombineW( filename, g_path, val );
+
+ size_t numItems = AGAVE_API_PLAYLISTMANAGER->CountItems( filename );
+ AddPlaylist( true, name, filename, ADD_TO_TREE, AddToCloud(), numItems );
+
+ g_config->WriteString( qn, NULL );
+ g_config->WriteString( qv, NULL );
+ g_config->WriteString( qm, NULL );
+ g_config->WriteString( qmet, NULL );
+
+ erased = true;
+
+ free( name );
+ free( val );
+ }
+ }
+
+ return erased;
+}
+
+void LoadPlaylists()
+{
+ bool loadedOld = LoadOldPlaylists();
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ normalimage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_PLAYLIST );
+ cloudImage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_CLOUD_PLAYLIST );
+
+ for ( size_t i = 0; i != count; i++ )
+ {
+ PlaylistInfo info( i );
+ if ( info.Valid() )
+ MakeTree( info );
+ }
+
+ if ( loadedOld )
+ AGAVE_API_PLAYLISTS->Flush();
+}
+
+void UpdatePlaylists()
+{
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ normalimage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_PLAYLIST );
+ cloudImage = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_CLOUD_PLAYLIST );
+
+ for ( size_t i = 0; i != count; i++ )
+ {
+ PlaylistInfo info( i );
+ if ( info.Valid() )
+ UpdateTree( info, info.treeId );
+ }
+
+ if ( IsWindow( currentView ) )
+ PostMessage( currentView, WM_APP + 101, 0, 0 );
+}
+
+void Playlist_importFromWinamp()
+{
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITEPLAYLIST );
+ const wchar_t *m3udir = (const wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETM3UDIRECTORYW );
+ wchar_t s[ MAX_PATH ] = { 0 };
+ PathCombineW( s, m3udir, L"winamp.m3u8" );
+
+ wchar_t filename[ 1024 + 256 ] = { 0 };
+ wchar_t *filenameptr = createPlayListDBFileName( filename );
+
+ wchar_t gs[ MAX_PATH ] = { 0 };
+ PathCombineW( gs, g_path, filenameptr );
+ size_t numItems = AGAVE_API_PLAYLISTMANAGER->Copy( gs, s );
+
+ AddPlaylist( true, WASABI_API_LNGSTRINGW( IDS_IMPORTED_PLAYLIST ), gs, ADD_TO_TREE, ( !( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ? AddToCloud() : 0 ), numItems );
+ AGAVE_API_PLAYLISTS->Flush();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/playlists.h b/Src/Plugins/Library/ml_playlists/playlists.h
new file mode 100644
index 00000000..7e409cbe
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/playlists.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLISTS_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLISTS_H
+
+#include "PlaylistInfo.h"
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/playlistsXML.cpp b/Src/Plugins/Library/ml_playlists/playlistsXML.cpp
new file mode 100644
index 00000000..ef8359b4
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/playlistsXML.cpp
@@ -0,0 +1,88 @@
+#include "Main.h"
+#include "playlistsXML.h"
+#include "../nu/AutoChar.h"
+
+void PlaylistsXML::StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, api_xmlreaderparams *params)
+{
+ const wchar_t *filename = params->getItemValue(L"filename");
+ const wchar_t *title = params->getItemValue(L"title");
+ const wchar_t *countString = params->getItemValue(L"songs");
+
+ int numItems = 0;
+ if (countString && *countString)
+ numItems = _wtoi(countString);
+
+ const wchar_t *lengthString = params->getItemValue(L"seconds");
+ int length = -1000;
+ if (lengthString && *lengthString)
+ length = _wtoi(lengthString);
+
+ AddPlaylist(title, filename, true, numItems, length);
+}
+
+void PlaylistsXML::LoadFile(const wchar_t *filename)
+{
+ if (!parser)
+ return ; // no sense in continuing if there's no parser available
+
+ HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
+ if (file == INVALID_HANDLE_VALUE)
+ return ;
+
+ char data[1024] = {0};
+ DWORD bytesRead;
+ while (true)
+ {
+ if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead)
+ {
+ if (parser->xmlreader_feed(data, bytesRead) != API_XML_SUCCESS)
+ {
+ CloseHandle(file);
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ CloseHandle(file);
+ parser->xmlreader_feed(0, 0);
+}
+
+PlaylistsXML::PlaylistsXML(): parser(0), parserFactory(0)
+{
+ parserFactory = WASABI_API_SVC->service_getServiceByGuid(api_xmlGUID);
+ if (parserFactory)
+ parser = (api_xml *)parserFactory->getInterface();
+
+ if (parser)
+ {
+ parser->xmlreader_registerCallback(L"Winamp:Playlists\fplaylist", this);
+ parser->xmlreader_registerCallback(L"playlists\fplaylist", this);
+ parser->xmlreader_open();
+ }
+}
+
+PlaylistsXML::~PlaylistsXML()
+{
+ if (parser)
+ {
+ parser->xmlreader_unregisterCallback(this);
+ parser->xmlreader_close();
+ }
+
+ if (parserFactory && parser)
+ parserFactory->releaseInterface(parser);
+
+ parserFactory = 0;
+ parser = 0;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS PlaylistsXML
+START_DISPATCH;
+VCB(ONSTARTELEMENT, StartTag)
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_playlists/playlistsXML.h b/Src/Plugins/Library/ml_playlists/playlistsXML.h
new file mode 100644
index 00000000..a165e77f
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/playlistsXML.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLISTSXML_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLISTSXML_H
+
+#include "../xml/api_xml.h"
+#include "../xml/api_xmlreadercallback.h"
+#include "api.h"
+#include <api/service/waServiceFactory.h>
+
+class PlaylistsXML : public api_xmlreadercallback
+{
+public:
+ PlaylistsXML();
+ ~PlaylistsXML();
+ void LoadFile(const wchar_t *filename);
+
+private:
+ RECVS_DISPATCH;
+ /* XML callbacks */
+ void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, api_xmlreaderparams *params);
+
+
+ api_xml *parser;
+ waServiceFactory *parserFactory;
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/pluginproc.cpp b/Src/Plugins/Library/ml_playlists/pluginproc.cpp
new file mode 100644
index 00000000..ed951d0b
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/pluginproc.cpp
@@ -0,0 +1,527 @@
+#include "main.h"
+#include "resource.h"
+#include <windows.h>
+#include <commctrl.h>
+#include "SendTo.h"
+#include "ml_local/api_mldb.h"
+#include "ml_pmp/pmp.h"
+#include "replicant/nswasabi/ReferenceCounted.h"
+#include "replicant/nx/win/nxstring.h"
+#include "replicant/nu/AutoChar.h"
+#include "nu/AutoCharFn.h"
+#include "nu/menushortcuts.h"
+
+#include <api/syscb/callbacks/syscb.h>
+#include <api/syscb/callbacks/browsercb.h>
+
+using namespace Nullsoft::Utility;
+INT_PTR lastActiveID = 0;
+SendToMenu treeViewSendTo;
+HWND currentView = 0;
+int playlists_ContextMenu(INT_PTR param1, HWND hHost, POINTS pts);
+
+LRESULT pluginHandleIpcMessage(int msg, WPARAM param)
+{
+ return SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
+}
+
+INT_PTR pluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ switch (message_type)
+ {
+ case ML_MSG_NO_CONFIG:
+ return TRUE;
+
+ case ML_MSG_TREE_ONCREATEVIEW:
+ if (param1 == playlistsTreeId)
+ {
+ return (INT_PTR)(currentView = WASABI_API_CREATEDIALOGW(IDD_VIEW_PLAYLISTS, (HWND)param2, view_playlistsDialogProc));
+ }
+ else // if param1 is a valid playlist
+ {
+ if (FindTreeItem(param1))
+ {
+ lastActiveID = param1;
+ // TODO: what if it's an ifc_playlist provided from elsewhere instead of just a filename?
+ return (INT_PTR)(currentView = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_PLAYLIST, (HWND)param2, view_playlistDialogProc, lastActiveID));
+ }
+ }
+ break;
+
+ case ML_MSG_ONSENDTOBUILD:
+ return playlists_BuildSendTo(param1, param2);
+
+ case ML_MSG_ONSENDTOSELECT:
+ return playlists_OnSendTo(param1, param2, param3);
+
+ case ML_MSG_TREE_ONCLICK:
+ return playlists_OnClick(param1, param2, (HWND)param3);
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, playlistsTreeId);
+ if (hItem == myItem)
+ {
+ return playlists_ContextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+ }
+ else
+ {
+ NAVITEM nvItem = {sizeof(NAVITEM),hItem,NIMF_ITEMID,};
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ if (FindTreeItem(nvItem.id))
+ {
+ HWND wnd = (HWND)param2;
+ sendToIgnoreID = nvItem.id;
+ HMENU menu = GetSubMenu(g_context_menus, 0),
+ sendToMenu = GetSubMenu(menu, 2);
+ treeViewSendTo.AddHere(wnd, sendToMenu, ML_TYPE_FILENAMES, 1, (ML_TYPE_PLAYLIST+1)); // we're going to lie about the type for now
+ // make sure that we call init on this otherwise the sendto menu will most likely fail
+ treeViewSendTo.InitPopupMenu((WPARAM)sendToMenu);
+
+ // tweaked to not fudge on the ml tree
+ EnableMenuItem(menu, IDC_PLAY, MF_BYCOMMAND | MF_ENABLED);
+ EnableMenuItem(menu, IDC_ENQUEUE, MF_BYCOMMAND | MF_ENABLED);
+ EnableMenuItem(menu, IDC_DELETE, MF_BYCOMMAND | MF_ENABLED);
+ EnableMenuItem(menu, ID_QUERYMENU_ADDNEWQUERY, MF_BYCOMMAND | MF_ENABLED);
+ EnableMenuItem(menu, IDC_RENAME, MF_BYCOMMAND | MF_ENABLED);
+ EnableMenuItem(menu, IDC_ENQUEUE, MF_BYCOMMAND | MF_ENABLED);
+ EnableMenuItem(menu, 2, MF_BYPOSITION | MF_ENABLED);
+ EnableMenuItem(menu, IDC_VIEWLIST, MF_BYCOMMAND | (nvItem.id != lastActiveID ? MF_ENABLED : MF_DISABLED));
+
+ HMENU cloud_hmenu = (HMENU)0x666;
+ size_t index = 0;
+ if (playlists_CloudAvailable())
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(nvItem.id))
+ {
+ ReferenceCountedNXString uid;
+ NXStringCreateWithFormatting(&uid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ (int)info.playlist_guid.Data1, (int)info.playlist_guid.Data2,
+ (int)info.playlist_guid.Data3, (int)info.playlist_guid.Data4[0],
+ (int)info.playlist_guid.Data4[1], (int)info.playlist_guid.Data4[2],
+ (int)info.playlist_guid.Data4[3], (int)info.playlist_guid.Data4[4],
+ (int)info.playlist_guid.Data4[5], (int)info.playlist_guid.Data4[6],
+ (int)info.playlist_guid.Data4[7]);
+
+ index = info.GetIndex();
+
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)uid->string, (intptr_t)&cloud_hmenu);
+
+ if (cloud_hmenu && cloud_hmenu != (HMENU)0x666)
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0};
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ InsertMenuItemW(menu, 3, TRUE, &m);
+
+ wchar_t a[100] = {0};
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100);
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_hmenu;
+ InsertMenuItemW(menu, 4, TRUE, &m);
+ }
+ }
+ }
+
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ {
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_PL_ACCELERATORS);
+ int size = CopyAcceleratorTable(accel,0,0);
+ AppendMenuShortcuts(menu, &accel, size, MSF_REPLACE);
+ }
+
+ if (swapPlayEnqueue)
+ SwapPlayEnqueueInMenu(menu);
+
+ POINT pt;
+ POINTSTOPOINT(pt, MAKEPOINTS(param3));
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(wnd, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, wnd, NULL);
+ switch (r)
+ {
+ case IDC_PLAY:
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(nvItem.id))
+ {
+ playlist_SaveGUID(info.playlist_guid);
+ mediaLibrary.PlayFile(info.GetFilename());
+ }
+ }
+ break;
+ case IDC_ENQUEUE:
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(nvItem.id))
+ {
+ playlist_SaveGUID(info.playlist_guid);
+ mediaLibrary.EnqueueFile(info.GetFilename());
+ }
+ }
+ break;
+ case IDC_NEWPLAYLIST:
+ playlists_Add(wnd);
+ break;
+ case IDC_DELETE:
+ DeletePlaylist(tree_to_guid_map[nvItem.id], wnd, true);
+ break;
+ case IDC_RENAME:
+ RenamePlaylist(tree_to_guid_map[nvItem.id], wnd);
+ break;
+ case IDC_VIEWLIST:
+ mediaLibrary.SelectTreeItem(nvItem.id);
+ break;
+ default:
+ {
+ if (treeViewSendTo.WasClicked(r))
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(nvItem.id))
+ {
+ playlist_SaveGUID(info.playlist_guid);
+
+ info.Refresh();
+
+ mlPlaylist sendToPlaylist;
+ sendToPlaylist.filename = info.GetFilename();
+ sendToPlaylist.title = info.GetName();
+ sendToPlaylist.numItems = info.GetSize();
+ sendToPlaylist.length = info.GetLength();
+
+ if (treeViewSendTo.SendPlaylist(&sendToPlaylist) != 1)
+ {
+ // didn't like that
+ // let's try this way
+ wchar_t filenames[MAX_PATH + 1] = {0};
+ lstrcpyn(filenames, info.GetFilename(), MAX_PATH);
+ filenames[lstrlen(filenames) + 1] = 0;
+ treeViewSendTo.SendFilenames(filenames);
+ }
+ }
+ }
+ else
+ {
+ if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER) // deals with cloud specific menus
+ {
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+ int mode = -(int)index;
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode);
+ if (mode > 0)
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(nvItem.id))
+ {
+ info.SetCloud((mode == 1 ? 1 : 0));
+ AGAVE_API_PLAYLISTS->Flush();
+ UpdatePlaylists();
+ }
+ }
+ }
+ }
+ }
+ }
+ treeViewSendTo.Cleanup();
+ sendToIgnoreID = 0;
+ if (cloud_hmenu && cloud_hmenu != (HMENU)0x666)
+ {
+ DeleteMenu(menu, CLOUD_SOURCE_MENUS - 1, MF_BYCOMMAND);
+ DeleteMenu(menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND);
+ DestroyMenu(cloud_hmenu);
+ }
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+
+ case ML_MSG_TREE_ONKEYDOWN:
+ return playlists_OnKeyDown(param1, (NMTVKEYDOWN *)param2, (HWND)param3);
+
+ case ML_MSG_TREE_ONDRAG:
+ return playlists_OnDrag(param1, (POINT *)param2, (int *)param3);
+
+ case ML_MSG_TREE_ONDROP:
+ return playlists_OnDrop(param1, (POINT *)param2, param3);
+
+ case ML_MSG_TREE_ONDROPTARGET:
+ return playlists_OnDropTarget(param1, param2, param3);
+
+ case ML_MSG_PLAYING_FILE:
+ lstrcpynW(current_playing, (wchar_t*)param1, FILENAME_SIZE);
+ if (IsWindow(currentView)) PostMessage(currentView, WM_APP + 103, (WPARAM)current_playing, 0);
+ return FALSE;
+
+ case ML_MSG_WRITE_CONFIG:
+ if (param1)
+ {
+ // only save the ml playlists if saving winamp.m3u/m3u8
+ // else this will happen everytime the prefs are closed
+ AGAVE_API_PLAYLISTS->Flush();
+ }
+ return FALSE;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ {
+ enqueuedef = param1;
+ groupBtn = param2;
+ PostMessage(currentView, WM_APP + 104, param1, param2);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+void myOpenURL(HWND hwnd, wchar_t *loc)
+{
+ if (loc)
+ {
+ bool override=false;
+ WASABI_API_SYSCB->syscb_issueCallback(SysCallback::BROWSER, BrowserCallback::ONOPENURL, reinterpret_cast<intptr_t>(loc), reinterpret_cast<intptr_t>(&override));
+ if (!override)
+ ShellExecuteW(hwnd, L"open", loc, NULL, NULL, SW_SHOWNORMAL);
+ }
+}
+
+int playlists_ContextMenu( INT_PTR param1, HWND hHost, POINTS pts )
+{
+ POINT pt;
+ POINTSTOPOINT( pt, pts );
+ if ( -1 == pt.x || -1 == pt.y )
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if ( MLNavItem_GetRect( plugin.hwndLibraryParent, &itemRect ) )
+ {
+ MapWindowPoints( hHost, HWND_DESKTOP, (POINT *)&itemRect.rc, 2 );
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU menu = GetSubMenu( g_context_menus, 1 );
+
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY, pt.x, pt.y, hHost, NULL );
+ switch ( r )
+ {
+ case IDC_NEWPLAYLIST:
+ playlists_Add( hHost );
+ break;
+ case IDC_IMPORT_PLAYLIST_FROM_FILE:
+ Playlist_importFromFile( hHost );
+ break;
+ case IDC_IMPORT_WINAMP_PLAYLIST:
+ Playlist_importFromWinamp();
+ break;
+ case ID_PLAYLISTSMENU_IMPORTPLAYLISTFROMFOLDERS:
+ Playlist_importFromFolders( hHost );
+ break;
+ case ID_SORTPLAYLIST_TITLE_A_Z:
+ playlists_Sort( SORT_TITLE_ASCENDING );
+ break;
+ case ID_SORTPLAYLIST_TITLE_Z_A:
+ playlists_Sort( SORT_TITLE_DESCENDING );
+ break;
+ case ID_SORTPLAYLIST_NUMBEROFITEMSASCENDING:
+ playlists_Sort( SORT_NUMBER_ASCENDING );
+ break;
+ case ID_SORTPLAYLIST_NUMBEROFITEMSDESCENDING:
+ playlists_Sort( SORT_NUMBER_DESCENDING );
+ break;
+ case ID_PLAYLISTS_HELP:
+ myOpenURL( hHost, L"https://help.winamp.com/hc/articles/8109547717268-Winamp-Playlists" );
+ break;
+ }
+
+ Sleep( 100 );
+ MSG msg;
+ while ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ); //eat return
+
+ return TRUE;
+}
+
+// param1 = param of tree item, param2 = action type (below), param3 = HWND of main window
+INT_PTR playlists_OnClick(INT_PTR treeId, int clickType, HWND wnd)
+{
+ switch (clickType)
+ {
+ case ML_ACTION_DBLCLICK:
+ case ML_ACTION_ENTER:
+ {
+ if (FindTreeItem(treeId))
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(treeId))
+ {
+ playlist_SaveGUID(info.playlist_guid);
+ bool flip = (clickType==ML_ACTION_ENTER) && (GetAsyncKeyState(VK_SHIFT)&0x8000);
+
+ if ((!!(g_config->ReadInt(L"enqueuedef", 0) == 1)) ^ flip)
+ mediaLibrary.EnqueueFile(info.GetFilename());
+ else
+ mediaLibrary.PlayFile(info.GetFilename());
+ }
+ return 1;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+int playlists_OnKeyDown(int treeId, NMTVKEYDOWN *p, HWND hwndDlg)
+{
+ int ctrl = (GetAsyncKeyState(VK_CONTROL)&0x8000);
+ int shift = (GetAsyncKeyState(VK_SHIFT)&0x8000);
+
+ if (treeId == playlistsTreeId)
+ {
+ switch (p->wVKey)
+ {
+ case VK_INSERT:
+ {
+ if (shift && !ctrl) playlists_Add(plugin.hwndLibraryParent); return 1;
+ }
+ }
+ }
+ else if (FindTreeItem(treeId))
+ {
+ switch (p->wVKey)
+ {
+ case VK_F2: if (!shift && !ctrl) RenamePlaylist(tree_to_guid_map[treeId], plugin.hwndLibraryParent); return 1;
+ case VK_INSERT: if (shift && !ctrl) playlists_Add(plugin.hwndLibraryParent); return 1;
+ case VK_DELETE: if (!shift && !ctrl) DeletePlaylist(tree_to_guid_map[treeId], hwndDlg, true); return 1;
+ }
+ }
+ return 0;
+}
+
+int playlists_OnDrag(int treeId, POINT *pt, int *type)
+{
+ if (FindTreeItem(treeId))
+ {
+ *type = ML_TYPE_FILENAMES;
+ return 1;
+ }
+ return 0;
+}
+
+int playlists_OnDrop(int treeId, POINT *pt, int destTreeId)
+{
+ if (FindTreeItem(treeId))
+ {
+ AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
+ PlaylistInfo info;
+ if (info.Associate(treeId))
+ {
+ if (destTreeId == playlistsTreeId)
+ {
+ mediaLibrary.RemoveTreeItem(info.treeId);
+ tree_to_guid_map.erase(info.treeId);
+ AGAVE_API_PLAYLISTS->MoveBefore(info.GetIndex(), 0);
+
+ // TODO: move most of this to PlaylistsCB
+ // TODO use the more native code like in MakeTree(..)
+ MLTREEITEMW src = {sizeof(MLTREEITEMW), };
+ src.title = const_cast<wchar_t *>(info.GetName());
+ src.hasChildren = 0;
+ src.parentId = playlistsTreeId;
+ src.id = destTreeId;
+ src.imageIndex = (!info.GetCloud() ? imgPL : imgCloudPL);
+ mediaLibrary.InsertTreeItem(src);
+ info.treeId = src.id;
+ tree_to_guid_map[info.treeId] = info.playlist_guid;
+ mediaLibrary.SelectTreeItem(info.treeId);
+ }
+ else if (FindTreeItem(destTreeId))
+ {
+ PlaylistInfo dest;
+ if (dest.Associate(destTreeId))
+ {
+ mediaLibrary.RemoveTreeItem(info.treeId);
+ tree_to_guid_map.erase(info.treeId);
+ AGAVE_API_PLAYLISTS->MoveBefore(info.GetIndex(), dest.GetIndex()+1);
+
+ // TODO: move most of this to PlaylistsCB
+ // TODO use the more native code like in MakeTree(..)
+ MLTREEITEMW src = {sizeof(MLTREEITEMW), };
+ src.title = const_cast<wchar_t *>(info.GetName());
+ src.hasChildren = 0;
+ src.parentId = playlistsTreeId;
+ src.id = destTreeId;
+ src.imageIndex = (!info.GetCloud() ? imgPL : imgCloudPL);
+ mediaLibrary.InsertTreeItem(src);
+ info.treeId = src.id;
+ tree_to_guid_map[info.treeId] = info.playlist_guid;
+ mediaLibrary.SelectTreeItem(info.treeId);
+ }
+ return 1;
+ }
+ else
+ {
+ playlist_SaveGUID(info.playlist_guid);
+
+ info.Refresh();
+ mlPlaylist pl;
+ pl.filename = info.GetFilename();
+ pl.length = info.GetLength();
+ pl.numItems = info.GetSize();
+ pl.title = info.GetName();
+
+ mlDropItemStruct m = {0};
+ m.p = *pt;
+ m.flags = 0;
+ m.type = ML_TYPE_PLAYLIST;
+ m.result = 0;
+ m.data = (void *) & pl;
+ m.name = 0;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ /* TODO: fall back to this is result fails?
+ mlDropItemStruct m = {0};
+ m.p = *pt;
+ m.flags = ML_HANDLEDRAG_FLAG_NAME;
+ m.type=ML_TYPE_FILENAMES;
+ m.result = 0;
+ char filename[1025] = {0};
+ lstrcpynA(filename, AutoCharFn(playlists[index].filename), 1024);
+ filename[lstrlenA(filename)+1]=0;
+ m.data=(void *)filename;
+ AutoChar charTitle(playlists[index].title);
+ m.name = charTitle;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ */
+ }
+ }
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/resource.h b/Src/Plugins/Library/ml_playlists/resource.h
new file mode 100644
index 00000000..0ba7255a
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/resource.h
@@ -0,0 +1,177 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_playlists.rc
+//
+#define IDS_IMPORT_PLAYLIST 0
+#define IDS_APPEND_IMPORTED_PLAYLIST 1
+#define IDS_LIBRARY_QUESTION 2
+#define IDS_APPEND_ACTIVE_PLAYLIST 3
+#define IDS_EXPORT_PLAYLIST 4
+#define IDS_IMPORTED_PLAYLIST 6
+#define IDS_ITEM 7
+#define IDS_ADD_DIR_TO_PLAYLIST 8
+#define IDS_ADD_FILES_TO_PLAYLIST 9
+#define IDS_PLAYLISTS 11
+#define IDS_MANAGE_PLAYLISTS 12
+#define IDS_NEW_PLAYLIST 13
+#define IDS_ENTER_A_NAME 14
+#define IDS_ERROR 15
+#define IDS_SENDTO_NEW_PLAYLIST 16
+#define IDS_SENDTO_ML_PLAYLISTS 17
+#define IDS_SENDTO_PLAYLIST 18
+#define IDS_IMPORT_PLAYLIST_FROM_FOLDER 19
+#define IDS_CONFIRM_DELETION 20
+#define IDS_CONFIRMATION 21
+#define IDS_PLAYLIST_TITLE 22
+#define IDS_ITEMS 23
+#define IDS_TIME 24
+#define IDS_ERROR_DELETING_FILES 25
+#define IDS_X_OF_X_SELECTED 26
+#define IDS_X_SELECTED 27
+#define IDS_ITEMS_LOWER 28
+#define IDS_TITLE 29
+#define IDS_NO_PLAYLIST_IN_LIBRARY 30
+#define IDS_OPEN_PLAYLIST_FROM_ML 31
+#define IDS_PLAYLIST_FROM_ML 32
+#define IDS_PLAYLIST_ERROR 34
+#define IDS_PLAYLIST_ERROR_TITLE 35
+#define IDS_DAY 36
+#define IDS_DAYS 37
+#define IDB_TREEITEM_CLOUD_PLAYLIST 38
+#define IDS_SENDTO_NEW_CLOUD_PLAYLIST 39
+#define IDD_ADD_CLOUD_PLAYLIST 40
+#define IDS_CLOUD_UNCHECKED 40
+#define IDS_PL_FILE_MNGT 41
+#define IDS_EXTERNAL_CHECKED 42
+#define IDS_EXTERNAL_ALREADY_ADDED 43
+#define IDS_CLOUD_SOURCES 44
+#define IDS_UPLOAD_TO_CLOUD 45
+#define IDS_AVAILABLE_IN_CLOUD 46
+#define IDS_SOURCE_PL_MISSING 47
+#define IDS_TRACK_AVAILABLE 48
+#define IDS_UPLOAD_TO_SOURCE 49
+#define IDS_STRING53 50
+#define IDS_BROWSE_FOR_PLEDIT_ENTRY 50
+#define IDD_VIEW_PLAYLISTS 101
+#define IDR_MENU1 102
+#define IDD_RENAME_PLAYLIST 103
+#define IDD_ADD_PLAYLIST 104
+#define IDD_VIEW_PLAYLIST 105
+#define IDB_BITMAP1 106
+#define IDC_CURSOR1 107
+#define IDD_BROWSE_PLFLD 108
+#define IDD_SELECT_PLAYLIST 110
+#define IDD_EDIT_FN 111
+#define IDR_VIEW_PL_ACCELERATORS 113
+#define IDR_VIEW_PLS_ACCELERATORS 116
+#define IDC_CUSTOM 1000
+#define IDC_PLAYLIST_LIST 1001
+#define IDC_VIEWLIST 1002
+#define IDC_PLAY 1003
+#define IDC_NAME 1003
+#define IDC_ENQUEUE 1004
+#define IDC_CREATENEWPL 1005
+#define IDC_BURN 1005
+#define IDC_IMPORT 1006
+#define IDC_PLSTATUS 1006
+#define IDC_SAVE 1007
+#define IDC_ADD 1007
+#define IDB_TREEITEM_PLAYLIST 1007
+#define IDC_REM 1008
+#define IDC_SEL 1009
+#define IDC_MISC 1010
+#define IDC_LIST 1011
+#define IDC_PLAYLIST_EDITOR 1012
+#define IDC_CHECK1 1013
+#define IDC_SAVE_PL 1013
+#define IDC_EXTERNAL 1014
+#define IDC_OLD 1015
+#define IDD_IMPORT_PLFLD 1015
+#define IDC_NEW 1016
+#define IDC_PLAYLISTS 1016
+#define IDC_CLOUD 1017
+#define IDC_OLD_TITLE 1017
+#define IDC_NEW_TITLE 1018
+#define IDC_PLAYLIST_EDIT_ENTRY_BROWSE 1020
+#define ID_QUERYWND_PLAYQUERY 40001
+#define ID_QUERYWND_ENQUEUEQUERY 40002
+#define ID_MEDIAWND_ADDTOPLAYLIST 40003
+#define ID_QUERYWND_EDIT 40004
+#define ID_QUERYMENU_ADDNEWQUERY 40005
+#define ID_QUERYWND_DELETE 40006
+#define ID_QUERYMENU_ADDNEWPLAYLIST 40007
+#define ID_PLAYLISTSMENU_IMPORTPLAYLISTFROMFILE 40008
+#define ID_PLAYLISTSMENU_IMPORTACTIVECURRENTPLAYLIST 40009
+#define ID_PLAYLISTSMENU_IMPORTPLAYLISTFROMFOLDERS 40010
+#define ID_PLAYLISTSIMPORT_IMPORTPLAYLISTSFROMFOLDER 40011
+#define ID_PLAYLISTSIMPORT_IMPORTACTIVECURRENTPLAYLIST 40012
+#define ID_PLAYLISTSIMPORT_IMPORTPLAYLISTFROMFILE 40013
+#define ID_PECONTEXT_PLAYSELECTION 40014
+#define ID_PECONTEXT_ENQUEUESELECTION 40015
+#define ID_PECONTEXT_DELETESELECTION 40016
+#define ID_PEDITORMENUS_CROPSELECTEDITEMS 40017
+#define ID_PE_ID3 40018
+#define ID_PECONTEXT_SELECTNONE 40019
+#define ID_PECONTEXT_INVERTSELECTION 40020
+#define ID_PECONTEXT_SELECTALL 40021
+#define ID_PE_NONEXIST 40022
+#define ID_PE_REMOVEALL 40023
+#define IDC_PLAYLIST_ADDLOC 40024
+#define IDC_PLAYLIST_ADDDIR 40025
+#define IDC_PLAYLIST_ADDMP3 40026
+#define ID_PECONTEXT_RANDOMIZELIST 40027
+#define ID_PECONTEXT_REVERSELIST 40028
+#define ID_PE_S_PATH 40029
+#define ID_PE_S_FILENAME 40030
+#define ID_PE_S_TITLE 40031
+#define ID_PEDITORMENUS_LIST_EXPORTPLAYLIST 40032
+#define ID_PEDITORMENUS_LIST_IMPORTPLAYLISTFROMDISK 40033
+#define ID_PEDITORMENUS_LIST_IMPORTACTIVEPLAYLIST 40034
+#define IDC_EXPORT_PLAYLIST 40035
+#define IDC_IMPORT_PLAYLIST_FROM_FILE 40036
+#define IDC_IMPORT_WINAMP_PLAYLIST 40037
+#define IDC_PLAYLIST_RANDOMIZE 40038
+#define IDC_PLAYLIST_REVERSE 40039
+#define IDC_PLAYLIST_SORT_FILENAME 40041
+#define IDC_PLAYLIST_SORT_PATH 40042
+#define IDC_PLAYLIST_SORT_TITLE 40043
+#define IDC_ADD_LOCATION 40044
+#define IDC_ADD_DIRECTORY 40045
+#define IDC_ADD_FILES 40046
+#define IDC_PLAYLIST_REMOVE_DEAD 40047
+#define IDC_PLAYLIST_REMOVE_ALL 40048
+#define IDC_PLAYLIST_SELECT_NONE 40051
+#define IDC_PLAYLIST_INVERT_SELECTION 40052
+#define IDC_PLAYLIST_SELECT_ALL 40053
+#define IDC_PLAYLIST_VIEW_FILE_INFO 40054
+#define IDC_PLAYLIST_DOWNLOAD_ENTRY 40055
+#define IDC_RENAME 40056
+#define IDC_DELETE 40057
+#define IDC_CROP 40058
+#define IDC_NEWPLAYLIST 40059
+#define IDC_PLAYLIST_RESET_CACHE 40061
+#define IDC_PLAYLIST_EDIT_ENTRY 40063
+#define IDC_PLAYLIST_RECYCLE_SELECTED 40065
+#define ID_SORTPLAYLIST_NUMBEROFITEMSASCENDING 40081
+#define ID_SORTPLAYLIST_NUMBEROFITEMSDESCENDING 40082
+#define ID_SORTPLAYLIST_TITLE_A_Z 40083
+#define ID_SORTPLAYLIST_TITLE_Z_A 40084
+#define ID_PLAYLISTSMENU_HELP 40085
+#define ID_PLAYLISTS_HELP 40089
+#define ID_RIGHTCLICK_EXPLOREITEM 40092
+#define IDC_PLAYLIST_EXPLOREITEMFOLDER 40093
+#define ID_PLAYLIST_EXPLOREITEMFOLD 40096
+#define ID_PLAYLIST_EXPLOREITEMFOLDER 40097
+#define ID_PLAYLIST_GENERATE_HTML 40098
+#define IDS_NULLSOFT_PLAYLISTS 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 54
+#define _APS_NEXT_COMMAND_VALUE 40102
+#define _APS_NEXT_CONTROL_VALUE 1021
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_playlists/resources/ti_cloud_playlist_16x16x16.bmp b/Src/Plugins/Library/ml_playlists/resources/ti_cloud_playlist_16x16x16.bmp
new file mode 100644
index 00000000..fbf955e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/resources/ti_cloud_playlist_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_playlists/resources/ti_playlist_16x16x16.bmp b/Src/Plugins/Library/ml_playlists/resources/ti_playlist_16x16x16.bmp
new file mode 100644
index 00000000..9f9fd4e9
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/resources/ti_playlist_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_playlists/version.rc2 b/Src/Plugins/Library/ml_playlists/version.rc2
new file mode 100644
index 00000000..2565a3c2
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,78,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,78,0,0"
+ VALUE "InternalName", "Nullsoft Playlists"
+ VALUE "LegalCopyright", "Copyright © 2002-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_playlists.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_playlists/view_pl.cpp b/Src/Plugins/Library/ml_playlists/view_pl.cpp
new file mode 100644
index 00000000..05cd2e1b
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/view_pl.cpp
@@ -0,0 +1,2885 @@
+#include <strsafe.h>
+#include <shlobj.h>
+#include <direct.h>
+
+#include <atomic>
+
+#include "main.h"
+#include "Playlist.h"
+#include "resource.h"
+#include "nu/listview.h"
+#include "CurrentPlaylist.h"
+#include "nu/AutoCharFn.h"
+#include "../../General/gen_ml/ml.h"
+#include "../../General/gen_ml/ml_ipc.h"
+#include "SendTo.h"
+#include "PlaylistView.h"
+#include "PlaylistDirectoryCallback.h"
+#include "api__ml_playlists.h"
+#include "../../General/gen_ml/menufucker.h"
+#include "nu/menushortcuts.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#include "ml_local/api_mldb.h"
+#include "ml_pmp/pmp.h"
+#include "replicant/nswasabi/ReferenceCounted.h"
+#include "replicant/nx/win/nxstring.h"
+#include "playlist/plstring.h"
+
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/AutoLock.h"
+#include "../replicant/nu/AutoLock.h"
+
+#include "../../../WAT/wa_logger.h"
+
+#include <iostream>
+#include <fstream>
+
+#include "../../../Components/wac_network/wac_network_http_receiver_api.h"
+#include "../../../Components/wac_downloadManager/wac_downloadManager_api.h"
+
+//#include "../replicant/replicant/metadata/metadata.h"
+
+using namespace Nullsoft::Utility;
+
+
+#define SIMULTANEOUS_DOWNLOADS 1
+#define PLAYLIST_DOWNLOAD_SUBFOLDER "\\Winamp_Library\\"
+#define WINAMP_REDIRECT_LINK_PROXY_FILE L"http://client.winamp.com/fileproxy?destination="
+
+std::vector<DownloadToken> plDownloads;
+LockGuard itemsPlaylistQueueLock;
+
+Playlist currentPlaylist;
+wchar_t currentPlaylistFilename[FILENAME_SIZE] = {0};
+wchar_t currentPlaylistTitle[FILETITLE_SIZE] = {0};
+wchar_t current_playing[FILENAME_SIZE] = {0};
+W_ListView playlist_list;
+static GUID playlist_guid = INVALID_GUID;
+int IPC_LIBRARY_SENDTOMENU;
+int we_are_drag_and_dropping = 0;
+
+static void AutoSizePlaylistColumns();
+
+static SendToMenu sendTo;
+viewButtons view = {0};
+
+typedef enum
+{
+ SCROLLDIR_NONE = 0,
+ SCROLLDIR_UP = 1,
+ SCROLLDIR_DOWN = -1,
+} SCROLLDIR;
+
+#define SCROLLTIMER_ID 100
+
+static INT scrollDelay = 0;
+static INT scrollTimerElapse = 0;
+static int scrollDirection = SCROLLDIR_NONE;
+
+void UpdatePlaylistTime(HWND hwndDlg);
+
+static bool opened = false;
+static bool changed = false;
+static bool loaded = false;
+
+HWND activeHWND = 0;
+HWND saveHWND = 0;
+
+int groupBtn = 1;
+int customAllowed = 0;
+int enqueuedef = 0;
+
+void Changed( bool _changed = true )
+{
+ changed = _changed;
+
+ EnableWindow( saveHWND, changed );
+}
+
+void SyncPlaylist()
+{
+ if ( opened )
+ {
+ playlist_list.SetVirtualCount( (INT)currentPlaylist.GetNumItems() );
+ playlist_list.RefreshAll();
+ UpdatePlaylistTime( GetParent( playlist_list.getwnd() ) );
+
+ if ( !current_playing[ 0 ] )
+ lstrcpynW( current_playing, (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_PLAYING_FILENAME ), FILENAME_SIZE );
+
+ PostMessage( activeHWND, WM_APP + 103, (WPARAM)current_playing, 1 );
+ }
+}
+
+void SyncMenuWithAccelerators( HWND hwndDlg, HMENU menu )
+{
+ HACCEL szAccel[ 24 ] = { 0 };
+ INT c = WASABI_API_APP->app_getAccelerators( hwndDlg, szAccel, sizeof( szAccel ) / sizeof( szAccel[ 0 ] ), FALSE );
+
+ AppendMenuShortcuts( menu, szAccel, c, MSF_REPLACE );
+}
+
+void UpdateMenuItems( HWND hwndDlg, HMENU menu )
+{
+ bool swapPlayEnqueue = false;
+ if ( g_config->ReadInt( L"enqueuedef", 0 ) == 1 )
+ {
+ SwapPlayEnqueueInMenu( menu );
+
+ swapPlayEnqueue = true;
+ }
+
+ SyncMenuWithAccelerators( hwndDlg, menu );
+
+ if ( swapPlayEnqueue )
+ SwapPlayEnqueueInMenu( menu );
+}
+
+void TagEditor( HWND hwnd )
+{
+ wchar_t fn[ 1024 ] = { 0 };
+ wchar_t ft[ 1024 ] = { 0 };
+ int v = playlist_list.GetCount();
+ for ( int x = 0; x < v; x++ )
+ {
+ if ( playlist_list.GetSelected( x ) )
+ {
+ currentPlaylist.GetItem( x, fn, 1024 );
+ infoBoxParamW p;
+ p.filename = fn;
+ p.parent = hwnd;
+
+ if ( SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW ) )
+ break;
+
+ int length = -1;
+ mediaLibrary.GetFileInfo( fn, ft, 1024, &length );
+
+ currentPlaylist.SetItemTitle( x, ft );
+ currentPlaylist.SetItemLengthMilliseconds( x, length * 1000 );
+
+ playlist_list.RefreshItem( x );
+ Changed();
+ }
+ }
+
+ MSG msg;
+ while ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ); //eat return
+}
+
+void myOpenURL(HWND hwnd, wchar_t *loc);
+
+void Playlist_GenerateHtmlPlaylist(void)
+{
+ FILE *fp = 0;
+ wchar_t filename[MAX_PATH], tp[MAX_PATH] = {0};
+
+ if (!GetTempPathW(MAX_PATH,tp))
+ StringCchCopyW(tp, MAX_PATH, L".");
+
+ if ( GetTempFileNameW( tp, L"WHT", 0, filename ) )
+ {
+ DeleteFileW( filename );
+ StringCchCatW( filename, MAX_PATH, L".html" );
+ }
+ else
+ StringCchCopyW(filename, MAX_PATH, L"wahtml_tmp.html");
+
+ fp = _wfopen(filename, L"wt");
+ if ( !fp )
+ {
+ //MessageBox(activeHWND, IDS_HTML_ERROR_WRITE, IDS_ERROR, MB_OK | MB_ICONWARNING);
+ return;
+ }
+
+ fprintf(fp, "<!DOCTYPE html>\n"
+ "<html><head>\n"
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
+ "<meta name=\"generator\" content=\"Winamp 5.9\">\n"
+ "<style type=\"text/css\">body{background:#000040;font-family:arial,helvetica;font-size:9pt;font-weight:normal;}"
+ ".name{margin-top:-1em;margin-left:15px;font-size:40pt;color:#004080;text-align:left;font-weight:900;}"
+ ".name-small{margin-top:-3em;margin-left:140px;font-size:22pt;color:#E1E1E1;text-align:left;}"
+ "table{font-size:9pt;color:#004080;text-align:left;border-width:0px;padding:0px;letter-spacing:normal;}"
+ "hr{border:0;background-color:#FFBF00;height:1px;}"
+ "ol{color:#FFFFFF;font-size:11pt;}"
+ "table{margin-left:15px;color:#409FFF;border-width:0px;}"
+ ".val{color:#FFBF00;}"
+ ".header{color:#FFBF00;font-size:14pt;}"
+ "</style>\n"
+ "<title>Winamp Generated PlayList</title></head>\n"
+ "<body>"
+ "<div>"
+ "<div class=\"name\"><p>WINAMP</p></div>"
+ "<div class=\"name-small\"><p>playlist</p></div>"
+ "</div>"
+ "<hr><div>\n"
+ "<table><tr><td>\n");
+
+ int x, t = playlist_list.GetCount(), t_in_pl = 0, n_un = 0;
+ for ( x = 0; x < t; x++ )
+ {
+ int a = currentPlaylist.GetItemLengthMilliseconds( x );
+ if ( a >= 0 )
+ t_in_pl += ( a / 1000 );
+ else
+ n_un++;
+ }
+
+ if ( t != n_un )
+ {
+ int old_t_in_pl = t_in_pl;
+ t_in_pl += ( n_un * t_in_pl ) / ( t - n_un );
+
+ fprintf( fp, "<span class=\"val\">%d</span> track%s in playlist, ", t, t == 1 ? "" : "s" );
+ fprintf( fp, "average track length: <span class=\"val\">%d:%02d", old_t_in_pl / ( t - n_un ) / 60, ( old_t_in_pl / ( t - n_un ) ) % 60 );
+
+ fprintf( fp, "</span><br>%slaylist length: ", n_un ? "Estimated p" : "P" );
+
+ if ( t_in_pl / 3600 )
+ {
+ fprintf( fp, "<span class=\"val\">%d</span> hour%s ", t_in_pl / 3600, t_in_pl / 3600 == 1 ? "" : "s" );
+ t_in_pl %= 3600;
+ }
+
+ if ( t_in_pl / 60 )
+ {
+ fprintf( fp, "<span class=\"val\">%d</span> minute%s ", t_in_pl / 60, t_in_pl / 60 == 1 ? "" : "s" );
+ t_in_pl %= 60;
+ }
+
+ fprintf( fp, "<span class=\"val\">%d</span> second%s %s", t_in_pl, t_in_pl == 1 ? "" : "s", n_un ? "<br>(" : "" );
+ if ( n_un )
+ fprintf( fp, "<span class=\"val\">%d</span> track%s of unknown length)", n_un, n_un == 1 ? "" : "s" );
+
+ fprintf( fp,
+ "<br>Right-click <a href=\"file://%s\">here</a> to save this HTML file."
+ "</td></tr>",
+ (char *)AutoChar( filename, CP_UTF8 ) );
+ }
+ else
+ {
+ fprintf( fp, "There are no tracks in the current playlist.<br>" );
+ }
+
+ fprintf(fp, "</table></div>\n");
+
+ if ( t > 0 )
+ {
+ fprintf( fp, "<blockquote><span class=\"header\">Playlist files:</span><ol>" );
+
+ for ( x = 0; x < t; x++ )
+ {
+ wchar_t ft[ FILETITLE_SIZE ] = { 0 };
+ currentPlaylist.GetItemTitle( x, ft, FILENAME_SIZE );
+
+ AutoChar narrowFt( ft, CP_UTF8 );
+ char *p = narrowFt;
+
+ int l = currentPlaylist.GetItemLengthMilliseconds( x );
+ if ( l > 0 )
+ l /= 1000;
+
+ fprintf( fp, "<li>" );
+
+ while ( p && *p )
+ {
+ if ( *p == '&' )
+ fprintf( fp, "&amp;" );
+ else if ( *p == '<' )
+ fprintf( fp, "&lt;" );
+ else if ( *p == '>' )
+ fprintf( fp, "&gt;" );
+ else if ( *p == '\'' )
+ fprintf( fp, "&#39;" );
+ else if ( *p == '"' )
+ fprintf( fp, "&quot;" );
+ else
+ fputc( *p, fp );
+
+ p++;
+ }
+
+ if ( l > 0 )
+ fprintf( fp, " (%d:%02d) \n", l / 60, l % 60 );
+ else
+ fprintf( fp, " \n" );
+ }
+
+ fprintf( fp, "</ol></blockquote>" );
+ }
+
+ fprintf(fp, "<hr><br></body></html>");
+ fclose(fp);
+
+ myOpenURL(activeHWND, filename);
+}
+
+void Playlist_ResetSelected()
+{
+ int i = playlist_list.GetCount();
+ while ( i-- )
+ {
+ if ( playlist_list.GetSelected( i ) )
+ currentPlaylist.ClearCache( i );
+ }
+
+ Changed();
+ SyncPlaylist();
+}
+
+void Playlist_FindSelected()
+{
+ if ( playlist_list.GetSelectionMark() >= 0 )
+ {
+ int l = playlist_list.GetCount();
+ for ( int i = 0; i < l; i++ )
+ {
+ if ( playlist_list.GetSelected( i ) )
+ WASABI_API_EXPLORERFINDFILE->AddFile( (wchar_t *)currentPlaylist.ItemName( i ) );
+ }
+
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+void Playlist_DeleteSelected( int selected )
+{
+ selected = !!selected; // convert to 0 or 1
+
+ int i = playlist_list.GetCount();
+ while ( i-- )
+ {
+ if ( !( playlist_list.GetSelected( i ) ^ selected ) )
+ {
+ currentPlaylist.Remove( i );
+ playlist_list.Unselect( i );
+ }
+ }
+
+ Changed();
+ SyncPlaylist();
+}
+
+void Playlist_RecycleSelected( HWND hwndDlg, int selected )
+{
+ SHFILEOPSTRUCTW fileOp;
+ fileOp.hwnd = hwndDlg;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = 0;
+ fileOp.pTo = 0;
+ fileOp.fFlags = SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_USES_RECYCLEBIN ) ? FOF_ALLOWUNDO : 0;
+ fileOp.fAnyOperationsAborted = 0;
+ fileOp.hNameMappings = 0;
+ fileOp.lpszProgressTitle = 0;
+
+ selected = !!selected; // convert to 0 or 1
+ int i = playlist_list.GetCount();
+
+ wchar_t *files = new wchar_t[ i * ( MAX_PATH + 1 ) + 1 ]; // need room for each file name, null terminated. then have to null terminate the whole list
+ if ( files )
+ {
+ wchar_t *curFile = files;
+
+ for ( int x = 0; x < i; x++ )
+ {
+ if ( !( playlist_list.GetSelected( x ) ^ selected ) )
+ {
+ lstrcpynW( curFile, currentPlaylist.ItemName( x ), MAX_PATH );
+ curFile += wcslen( currentPlaylist.ItemName( x ) ) + 1;
+ }
+ }
+
+ if ( curFile != files )
+ {
+ curFile[ 0 ] = 0; // null terminate
+
+ fileOp.pFrom = files;
+
+ if ( SHFileOperationW( &fileOp ) )
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_ERROR_DELETING_FILES ), WASABI_API_LNGSTRINGW_BUF( IDS_ERROR, titleStr, 32 ), MB_OK );
+ }
+ else if ( !fileOp.fAnyOperationsAborted )
+ {
+ while ( i-- )
+ {
+ if ( !( playlist_list.GetSelected( i ) ^ selected ) )
+ {
+ currentPlaylist.Remove( i );
+ playlist_list.Unselect( i );
+ }
+ }
+ }
+ }
+
+ delete[] files;
+ }
+ else // if malloc failed ... maybe because there'l_enqueue_file too many items.
+ {
+ while ( i-- )
+ {
+ if ( !( playlist_list.GetSelected( i ) ^ selected ) )
+ {
+ fileOp.pFrom = currentPlaylist.ItemName( i );
+
+ if ( SHFileOperationW( &fileOp ) )
+ continue;
+
+ if ( fileOp.fAnyOperationsAborted )
+ break;
+
+ currentPlaylist.Remove( i );
+ playlist_list.Unselect( i );
+ }
+ }
+ }
+
+ Changed();
+ SyncPlaylist();
+}
+
+int GetSelectedLength()
+{
+ int length = 0;
+ int selected = -1;
+
+ while ( ( selected = playlist_list.GetNextSelected( selected ) ) != -1 )
+ {
+ int thisLen = currentPlaylist.GetItemLengthMilliseconds( selected );
+ if ( thisLen > 0 )
+ length += thisLen / 1000;
+ }
+
+ return length;
+}
+
+int GetTotalLength()
+{
+ int length = 0;
+ int len = playlist_list.GetCount();
+ for ( int i = 0; i < len; i++ )
+ {
+ int thisLen = currentPlaylist.GetItemLengthMilliseconds( i );
+ if ( thisLen > 0 )
+ length += thisLen / 1000;
+ }
+
+ return length;
+}
+
+void FormatLength( wchar_t *str, int length, int buf_len )
+{
+ if ( !length )
+ lstrcpynW( str, L"0:00", buf_len );
+ else if ( length < 60 * 60 )
+ StringCchPrintfW( str, buf_len, L"%d:%02d", length / 60, length % 60 );
+ else
+ {
+ int total_days = length / ( 60 * 60 * 24 );
+ if ( total_days )
+ {
+ length -= total_days * 60 * 60 * 24;
+ StringCchPrintfW( str, buf_len, L"%d %s+%d:%02d:%02d", total_days, WASABI_API_LNGSTRINGW( ( total_days == 1 ? IDS_DAY : IDS_DAYS ) ), length / 60 / 60, ( length / 60 ) % 60, length % 60 );
+ }
+ else
+ StringCchPrintfW( str, buf_len, L"%d:%02d:%02d", length / 60 / 60, ( length / 60 ) % 60, length % 60 );
+ }
+}
+
+void UpdatePlaylistTime(HWND hwndDlg)
+{
+ wchar_t str[64] = {0}, str2[32] = {0};
+ int selitems = playlist_list.GetSelectedCount();
+
+ int seltime = GetSelectedLength(), ttime = GetTotalLength();
+
+ FormatLength(str, seltime, 64);
+ FormatLength(str2, ttime, 32);
+
+ wchar_t buf2[128] = {0}, sStr[16] = {0};
+ if ( selitems )
+ StringCchPrintf( buf2, 128, WASABI_API_LNGSTRINGW( IDS_X_OF_X_SELECTED ), selitems, playlist_list.GetCount(), WASABI_API_LNGSTRINGW_BUF( playlist_list.GetCount() == 1 ? IDS_ITEM : IDS_ITEMS_LOWER, sStr, 16 ), str, str2 );
+ else
+ StringCchPrintf( buf2, 128, WASABI_API_LNGSTRINGW( IDS_X_SELECTED ), playlist_list.GetCount(), WASABI_API_LNGSTRINGW_BUF( playlist_list.GetCount() == 1 ? IDS_ITEM : IDS_ITEMS_LOWER, sStr, 16 ), str2 );
+
+ SetDlgItemText(hwndDlg, IDC_PLSTATUS, buf2);
+}
+
+static wchar_t *BuildFilenameList( int is_all )
+{
+ wchar_t filename[ MAX_PATH ] = { 0 };
+
+ size_t len = MAX_PATH;
+ wchar_t *str = (wchar_t *)calloc( len, sizeof( wchar_t ) );
+ size_t sofar = 0;
+
+ int numTracks = playlist_list.GetCount();
+ for ( int i = 0; i < numTracks; i++ )
+ {
+ if ( is_all || playlist_list.GetSelected( i ) )
+ {
+ if ( currentPlaylist.GetItem( i, filename, MAX_PATH ) )
+ {
+ int filenameLen = lstrlen( filename ) + 1;
+ if ( ( filenameLen + sofar ) > len )
+ {
+ int newLen = sofar * 2; // add some cushion
+ wchar_t *newStr = (wchar_t *)realloc( str, newLen * sizeof( wchar_t ) );
+ if ( !newStr )
+ {
+ newLen = sofar + filenameLen;
+ // try the minimum possible size to get this to work
+ newStr = (wchar_t *)realloc( str, newLen * sizeof( wchar_t ) );
+ if ( !newStr )
+ {
+ free( str );
+ return 0;
+ }
+ }
+ str = newStr;
+ }
+
+ lstrcpyn( str + sofar, filename, filenameLen );
+ sofar += filenameLen;
+ }
+ }
+ }
+
+ *( str + sofar ) = 0;
+
+ return str;
+}
+
+void PlaySelection( int enqueue, int is_all )
+{
+ if ( !enqueue )
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE );
+
+ int numTracks = playlist_list.GetCount();
+ for ( int i = 0; i < numTracks; i++ )
+ {
+ if ( is_all || playlist_list.GetSelected( i ) )
+ {
+ const wchar_t *filename = currentPlaylist.ItemName( i );
+ if ( filename )
+ {
+ enqueueFileWithMetaStructW l_enqueue_file;
+
+ std::map<std::wstring, std::wstring> &l_extended_infos = currentPlaylist.entries[ i ]->_extended_infos;
+
+ wa::strings::wa_string l_original_filename( filename );
+
+ if ( !currentPlaylist.entries[ i ]->isLocal() && !l_extended_infos.empty() && l_extended_infos.count( L"ext" ) == 1 && l_original_filename.contains( "://" ) )
+ {
+ wa::strings::wa_string l_ext( "." );
+ l_ext.append( ( *l_extended_infos.find( L"ext" ) ).second );
+ l_ext.toUpper();
+
+ wa::strings::wa_string l_filename( WINAMP_REDIRECT_LINK_PROXY_FILE );
+
+ l_filename.append( filename );
+
+ wa::strings::wa_string l_upper_case_filename( filename );
+ l_upper_case_filename.toUpper();
+
+ if ( !l_upper_case_filename.contains( l_ext.GetW() ) )
+ {
+ if ( !l_original_filename.contains( "?" ) )
+ l_filename.append( "?" );
+ else
+ l_filename.append( "&" );
+
+ l_filename.append( "ext=" );
+ l_filename.append( l_ext );
+ }
+
+
+ l_enqueue_file.filename = _wcsdup( l_filename.GetW().c_str() );
+ l_enqueue_file.ext = _wcsdup( ( *l_extended_infos.find( L"ext" ) ).second.c_str() );
+ }
+ else
+ {
+ l_enqueue_file.filename = filename;
+ l_enqueue_file.ext = NULL;
+ }
+
+
+ wa::strings::wa_string l_filename( l_enqueue_file.filename );
+
+ if ( l_filename.contains( "://" ) )
+ {
+ wsprintfW( _log_message_w, L"The link '%s' will be played!", l_filename.GetW().c_str() );
+
+ LOG_DEBUG( _log_message_w );
+ }
+
+
+ if ( currentPlaylist.IsCached( i ) )
+ {
+ l_enqueue_file.title = currentPlaylist.ItemTitle( i );
+ plstring_retain( (wchar_t *)l_enqueue_file.title );
+ l_enqueue_file.length = currentPlaylist.GetItemLengthMilliseconds( i ) / 1000;
+ }
+ else
+ {
+ l_enqueue_file.title = 0;
+ l_enqueue_file.length = 0;
+ }
+
+ plstring_retain( (wchar_t *)l_enqueue_file.filename );
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&l_enqueue_file, IPC_PLAYFILEW_NDE_TITLE );
+ }
+ }
+ }
+
+ if ( !enqueue )
+ {
+ if ( is_all )
+ {
+ int pos = playlist_list.GetNextSelected( -1 );
+ if ( pos != -1 )
+ {
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, pos, IPC_SETPLAYLISTPOS );
+ SendMessage( plugin.hwndWinampParent, WM_COMMAND, 40047, 0 ); // stop button, literally
+ SendMessage( plugin.hwndWinampParent, WM_COMMAND, 40045, 0 ); // play button, literally
+
+ return;
+ }
+ }
+
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY );
+ }
+}
+
+int playlist_Load( const wchar_t *playlistFileName )
+{
+ currentPlaylist.Clear();
+
+ return AGAVE_API_PLAYLISTMANAGER->Load( playlistFileName, &currentPlaylist );
+}
+
+LRESULT playlist_cloud_listview( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_NOTIFY )
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ switch ( l->code )
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ if ( cloud_avail && lvh.iItem != -1 && lvh.iSubItem == 1 )
+ {
+ LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
+ RECT r = { 0 };
+
+ if ( lvh.iSubItem )
+ ListView_GetSubItemRect( hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r );
+ else
+ {
+ ListView_GetItemRect( hwnd, lvh.iItem, &r, LVIR_BOUNDS );
+
+ r.right = r.left + ListView_GetColumnWidth( hwnd, 1 );
+ }
+
+ MapWindowPoints( hwnd, HWND_DESKTOP, (LPPOINT)&r, 2 );
+ SetWindowPos( tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE );
+
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ static wchar_t tt_buf2[256] = {L""};
+ if ( cloud_avail && lvh.iItem != -1 && lvh.iSubItem == 1 )
+ {
+ LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
+
+ static int last_item2 = -1;
+ if ( last_item2 == lvh.iItem )
+ {
+ lpnmtdi->lpszText = tt_buf2;
+
+ return 0;
+ }
+
+ wchar_t info[ 16 ] = { 0 };
+ currentPlaylist.GetItemExtendedInfo( lvh.iItem, L"cloud_status", info, 16 );
+
+ int status = _wtoi( info );
+ if ( status == 4 )
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_UPLOAD_TO_SOURCE, tt_buf2, ARRAYSIZE( tt_buf2 ) );
+ }
+ else
+ {
+ winampMediaLibraryPlugin *( *gp )( );
+ gp = ( winampMediaLibraryPlugin * ( __cdecl * )( void ) )GetProcAddress( cloud_hinst, "winampGetMediaLibraryPlugin" );
+ if ( gp )
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if ( mlplugin && ( mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER ) )
+ {
+ // TODO handle case when not in a device
+ WASABI_API_LNGSTRINGW_BUF( IDS_TRACK_AVAILABLE, tt_buf2, ARRAYSIZE( tt_buf2 ) );
+
+ wchar_t filepath[ 1024 ] = { 0 };
+ currentPlaylist.GetItem( lvh.iItem, filepath, 1024 );
+
+ nx_string_t *out_devicenames = 0;
+ size_t num_names = mlplugin->MessageProc( 0x405, (INT_PTR)&filepath, (INT_PTR)&out_devicenames, 0 );
+ if ( num_names > 0 )
+ {
+ for ( size_t i = 0; i < num_names; i++ )
+ {
+ if ( i > 0 )
+ StringCchCatW( tt_buf2, ARRAYSIZE( tt_buf2 ), L", " );
+
+ StringCchCatW( tt_buf2, ARRAYSIZE( tt_buf2 ), out_devicenames[ i ]->string );
+ }
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_UPLOAD_TO_SOURCE, tt_buf2, ARRAYSIZE( tt_buf2 ) );
+ }
+
+ if ( out_devicenames )
+ free( out_devicenames );
+ }
+ }
+ }
+
+ last_item2 = lvh.iItem;
+ lpnmtdi->lpszText = tt_buf2;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"cloud_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"cloud_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+void playlist_UpdateButtonText( HWND hwndDlg, int _enqueuedef )
+{
+ if ( groupBtn )
+ {
+ switch ( _enqueuedef )
+ {
+ case 1:
+ SetDlgItemTextW( hwndDlg, IDC_PLAY, view.enqueue );
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = { ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0 };
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p );
+ if ( pszTextW && pszTextW[ 0 ] != 0 )
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW( hwndDlg, IDC_PLAY, pszTextW );
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW( hwndDlg, IDC_PLAY, view.play );
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+static void playlist_Init( HWND hwndDlg, LPARAM lParam )
+{
+ HACCEL accel = WASABI_API_LOADACCELERATORSW( IDR_VIEW_PL_ACCELERATORS );
+ if ( accel )
+ WASABI_API_APP->app_addAccelerators( hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD );
+
+ if ( !view.play )
+ SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view );
+
+ opened = true;
+ loaded = false;
+ activeHWND = hwndDlg;
+ saveHWND = GetDlgItem(hwndDlg, IDC_SAVE_PL);
+
+ Changed( false );
+
+ cloud_avail = playlists_CloudAvailable();
+ groupBtn = g_config->ReadInt( L"groupbtn", 1 );
+ enqueuedef = ( g_config->ReadInt( L"enqueuedef", 0 ) == 1 );
+
+ // force create this as it helps resolve some quirks with play / enqueue handling and
+ // we cannot guarantee the button being on the dialog resource due to old lang packs.
+ if ( !IsWindow( GetDlgItem( hwndDlg, IDC_ENQUEUE ) ) )
+ {
+ HWND newnd = CreateWindowEx( WS_EX_NOPARENTNOTIFY, L"button", view.enqueue, WS_CHILD | WS_TABSTOP | BS_OWNERDRAW, 0, 0, 0, 0, hwndDlg, (HMENU)IDC_ENQUEUE, plugin.hDllInstance, 0 );
+ // make sure we're using an appropriate font for the display (may need to review this...)
+ SendMessage( newnd, WM_SETFONT, (WPARAM)SendDlgItemMessage( hwndDlg, IDC_PLAY, WM_GETFONT, 0, 0 ), 0 );
+ SetWindowPos( newnd, GetDlgItem( hwndDlg, IDC_PLAY ), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSIZE | SWP_SHOWWINDOW );
+ }
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = { ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG( IDC_CUSTOM, IDC_ENQUEUE ), (INT_PTR)L"ml_playlists" };
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p );
+ if ( pszTextW && pszTextW[ 0 ] != 0 )
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW( hwndDlg, IDC_CUSTOM, pszTextW );
+ }
+ else
+ {
+ customAllowed = FALSE;
+ }
+
+ // override the button text to save a bit more space
+ if ( !g_config->ReadInt( L"pltextbuttons", 0 ) )
+ {
+ SetDlgItemText( hwndDlg, IDC_ADD, L"+" );
+ SetDlgItemText( hwndDlg, IDC_REM, L"\u2212" );
+ }
+
+ /* skin dialog */
+ MLSKINWINDOW sw = {0};
+ sw.skinType = SKINNEDWND_TYPE_DIALOG;
+ sw.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ sw.hwndToSkin = hwndDlg;
+ MLSkinWindow( plugin.hwndLibraryParent, &sw );
+
+ /* skin status bar */
+ sw.hwndToSkin = GetDlgItem( hwndDlg, IDC_PLSTATUS );
+ sw.skinType = SKINNEDWND_TYPE_STATIC;
+ sw.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow( plugin.hwndLibraryParent, &sw );
+
+ /* skin listview */
+ HWND list = sw.hwndToSkin = GetDlgItem(hwndDlg, IDC_PLAYLIST_EDITOR);
+ sw.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ sw.style = SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS | SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow( plugin.hwndLibraryParent, &sw );
+ MLSkinnedScrollWnd_ShowHorzBar( sw.hwndToSkin, FALSE );
+
+ /* skin buttons */
+ sw.skinType = SKINNEDWND_TYPE_BUTTON;
+ sw.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | ( groupBtn ? SWBS_SPLITBUTTON : 0 );
+ const int buttonidz[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM };
+ for ( size_t i = 0; i != sizeof( buttonidz ) / sizeof( buttonidz[ 0 ] ); i++ )
+ {
+ sw.hwndToSkin = GetDlgItem( hwndDlg, buttonidz[ i ] );
+ if ( IsWindow( sw.hwndToSkin ) )
+ MLSkinWindow( plugin.hwndLibraryParent, &sw );
+ }
+
+ /* skin dropdown buttons */
+ sw.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWBS_DROPDOWNBUTTON;
+ const int buttonids[] = {IDC_BURN, IDC_ADD, IDC_REM, IDC_SEL, IDC_MISC, IDC_LIST};
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ sw.hwndToSkin = GetDlgItem( hwndDlg, buttonids[ i ] );
+ if ( IsWindow( sw.hwndToSkin ) )
+ MLSkinWindow( plugin.hwndLibraryParent, &sw );
+ }
+
+ sw.style -= SWBS_DROPDOWNBUTTON;
+ sw.hwndToSkin = GetDlgItem( hwndDlg, IDC_SAVE_PL );
+ MLSkinWindow( plugin.hwndLibraryParent, &sw );
+
+ // REVIEW: it'd be really nice to pass in a pointer to an ifc_playlist instead...
+ // at this point, the main issue is how to delete/release it when we're done
+ playlist_guid = tree_to_guid_map[ lParam ];
+
+
+ { // scope for lock
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlist_guid );
+ if ( info.Valid() )
+ {
+ // will check if the playlist file exists and update the view as needed
+ const wchar_t *filename = info.GetFilename();
+ if ( !PathFileExistsW( filename ) )
+ {
+ opened = false;
+
+ RECT r = { 0 };
+ HWND status = GetDlgItem( hwndDlg, IDC_PLSTATUS );
+
+ GetWindowRect( hwndDlg, &r );
+ MoveWindow( status, 20, 0, r.right - r.left - 40, r.bottom - r.top, FALSE );
+
+ const int ids[] = { IDC_PLAYLIST_EDITOR, IDC_PLAY, IDC_BURN, IDC_ADD, IDC_REM, IDC_SEL, IDC_MISC, IDC_LIST, IDC_SAVE_PL };
+ for ( size_t i = 0; i < sizeof( ids ) / sizeof( ids[ 0 ] ); i++ )
+ ShowWindow( GetDlgItem( hwndDlg, ids[ i ] ), FALSE );
+
+ // adjust the styles without needing extra resources, etc
+ DWORD style = GetWindowLongPtr( status, GWL_STYLE );
+ if ( style & SS_ENDELLIPSIS )
+ style -= SS_ENDELLIPSIS;
+
+ if ( style & SS_CENTERIMAGE )
+ style -= SS_CENTERIMAGE;
+
+ SetWindowLongPtr( status, GWL_STYLE, style | SS_CENTER | 0x2000 );
+ wchar_t buf[ 1024 ] = { 0 };
+ StringCchPrintfW( buf, 1024, WASABI_API_LNGSTRINGW( IDS_SOURCE_PL_MISSING ), filename );
+ SetWindowTextW( status, buf );
+ }
+
+ lstrcpynW( currentPlaylistFilename, filename, MAX_PATH );
+ playlist_Load( currentPlaylistFilename );
+
+ lstrcpynW( currentPlaylistTitle, info.GetName(), FILETITLE_SIZE );
+ }
+ }
+
+ SetPropW( hwndDlg, L"TITLE", currentPlaylistTitle );
+
+ playlist_list.setwnd( list );
+
+ playlist_list.AddCol( WASABI_API_LNGSTRINGW( IDS_TITLE ), 400 );
+
+ int width = 27;
+ MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width );
+ playlist_list.AddCol( L"", ( cloud_avail ? width : 0 ) );
+ playlist_list.AddAutoCol( WASABI_API_LNGSTRINGW( IDS_TIME ) );
+ playlist_list.JustifyColumn( 2, LVCFMT_RIGHT );
+
+ MLSkinnedHeader_SetCloudColumn( ListView_GetHeader( playlist_list.getwnd() ), ( cloud_avail ? 1 : -1 ) );
+
+ if (!GetPropW(list, L"cloud_list_proc"))
+ SetPropW( list, L"cloud_list_proc", (HANDLE)SetWindowLongPtrW( list, GWLP_WNDPROC, (LONG_PTR)playlist_cloud_listview ) );
+
+ playlist_UpdateButtonText( hwndDlg, enqueuedef == 1 );
+
+ SyncPlaylist();
+ SetWindowRedraw( playlist_list.getwnd(), FALSE );
+}
+
+static void playlist_Paint( HWND hwndDlg )
+{
+ int tab[] = { IDC_PLAYLIST_EDITOR | DCW_SUNKENBORDER };
+ dialogSkinner.Draw( hwndDlg, tab, ( opened ? 1 : 0 ) );
+}
+
+static void AutoSizePlaylistColumns()
+{
+ playlist_list.AutoSizeColumn( 2 );
+ RECT channelRect;
+ GetClientRect( playlist_list.getwnd(), &channelRect );
+ ListView_SetColumnWidth( playlist_list.getwnd(), 0, channelRect.right - playlist_list.GetColumnWidth( 1 ) - playlist_list.GetColumnWidth( 2 ) );
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL playlist_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags = 0 )
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem( hwndDlg, buttonId );
+
+ GetWindowRect( buttonHWND, &r );
+ UpdateMenuItems( hwndDlg, menu );
+ MLSkinnedButton_SetDropDownState( buttonHWND, TRUE );
+
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if ( !( flags & BPM_WM_COMMAND ) )
+ tpmFlags |= TPM_RETURNCMD;
+
+ int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, tpmFlags, r.left, r.top, hwndDlg, NULL );
+ if ( ( flags & BPM_ECHO_WM_COMMAND ) && x )
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( x, 0 ), 0 );
+
+ MLSkinnedButton_SetDropDownState( buttonHWND, FALSE );
+
+ return x;
+}
+
+static void playlist_Burn( HWND hwndDlg )
+{
+ HMENU blah = CreatePopupMenu();
+
+ sendToIgnoreID = lastActiveID;
+ sendTo.AddHere( hwndDlg, blah, ML_TYPE_FILENAMES );
+
+ int x = playlist_ButtonPopupMenu( hwndDlg, IDC_BURN, blah );
+ if ( sendTo.WasClicked( x ) )
+ {
+ int is_all = playlist_list.GetSelectedCount() == 0;
+ wchar_t *names = BuildFilenameList( is_all );
+ sendTo.SendFilenames( names );
+ free( names );
+ }
+
+ sendTo.Cleanup();
+
+ sendToIgnoreID = 0;
+}
+
+static void playlist_Play( HWND hwndDlg, HWND from, UINT idFrom )
+{
+ HMENU listMenu = GetSubMenu( g_context_menus2, 0 );
+ int count = GetMenuItemCount( listMenu );
+
+ if ( count > 2 )
+ {
+ for ( int i = 2; i < count; i++ )
+ DeleteMenu( listMenu, 2, MF_BYPOSITION );
+ }
+
+ playlist_ButtonPopupMenu( hwndDlg, idFrom, listMenu, BPM_WM_COMMAND );
+ UpdatePlaylistTime( hwndDlg );
+}
+
+static void playlist_Sel( HWND hwndDlg, HWND from )
+{
+ HMENU listMenu = GetSubMenu( GetSubMenu( g_context_menus, 3 ), 1 );
+ UINT menuStatus;
+
+ if ( playlist_list.GetNextSelected( -1 ) == -1 )
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_INVERT_SELECTION, menuStatus );
+
+ if ( playlist_list.GetCount() > 0 )
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_SELECT_ALL, menuStatus );
+ playlist_ButtonPopupMenu( hwndDlg, IDC_SEL, listMenu, BPM_WM_COMMAND );
+ UpdatePlaylistTime( hwndDlg );
+}
+
+static void playlist_Rem( HWND hwndDlg, HWND from )
+{
+ HMENU listMenu = GetSubMenu( GetSubMenu( g_context_menus, 3 ), 2 );
+ UINT menuStatus;
+ if ( playlist_list.GetNextSelected( -1 ) == -1 )
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+
+ EnableMenuItem( listMenu, IDC_DELETE, menuStatus );
+ EnableMenuItem( listMenu, IDC_CROP, menuStatus );
+
+ if ( playlist_list.GetCount() > 0 )
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_REMOVE_DEAD, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_REMOVE_ALL, menuStatus );
+
+ playlist_ButtonPopupMenu( hwndDlg, IDC_REM, listMenu, BPM_WM_COMMAND );
+ UpdatePlaylistTime( hwndDlg );
+}
+
+static void playlist_Add( HWND hwndDlg, HWND from )
+{
+ HMENU listMenu = GetSubMenu( GetSubMenu( g_context_menus, 3 ), 3 );
+
+ playlist_ButtonPopupMenu( hwndDlg, IDC_ADD, listMenu, BPM_WM_COMMAND );
+ UpdatePlaylistTime( hwndDlg );
+}
+
+static void playlist_Misc( HWND hwndDlg, HWND from )
+{
+ HMENU listMenu = GetSubMenu( GetSubMenu( g_context_menus, 3 ), 4 );
+
+ UINT menuStatus;
+ if ( playlist_list.GetCount() > 0 )
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_RANDOMIZE, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_REVERSE, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_SORT_PATH, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_SORT_FILENAME, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_SORT_TITLE, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_RESET_CACHE, menuStatus );
+
+ playlist_ButtonPopupMenu( hwndDlg, IDC_MISC, listMenu, BPM_WM_COMMAND );
+
+ UpdatePlaylistTime( hwndDlg );
+}
+
+static void playlist_List( HWND hwndDlg, HWND from )
+{
+ sendToIgnoreID = lastActiveID;
+ HMENU listMenu = GetSubMenu( GetSubMenu( g_context_menus, 3 ), 5 );
+ sendTo.AddHere( hwndDlg, GetSubMenu( listMenu, 1 ), ML_TYPE_FILENAMES );
+
+ int x = playlist_ButtonPopupMenu( hwndDlg, IDC_LIST, listMenu, BPM_ECHO_WM_COMMAND );
+ if ( sendTo.WasClicked( x ) )
+ {
+ wchar_t *names = BuildFilenameList( 1 );
+ sendTo.SendFilenames( names );
+
+ free( names );
+ }
+
+ sendTo.Cleanup();
+ UpdatePlaylistTime( hwndDlg );
+ sendToIgnoreID = 0;
+}
+
+static void playlist_Command( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_BURN:
+ playlist_Burn( hwndDlg );
+ break;
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ {
+ if ( HIWORD( wParam ) == MLBN_DROPDOWN )
+ {
+ playlist_Play( hwndDlg, (HWND)lParam, LOWORD( wParam ) );
+ break;
+ }
+ else
+ {
+ // if it'l_enqueue_file from an accelerator, use the appropriate setting
+ int action;
+ if ( LOWORD( wParam ) == IDC_PLAY )
+ action = ( HIWORD( wParam ) == 1 ) ? g_config->ReadInt( L"enqueuedef", 0 ) == 1 : 0;
+ else
+ action = ( HIWORD( wParam ) == 1 ) ? g_config->ReadInt( L"enqueuedef", 0 ) != 1 : 1;
+
+ PlaySelection( action, playlist_list.GetSelectedCount() == 0 );
+ }
+ }
+ break;
+ case IDC_SEL:
+ playlist_Sel( hwndDlg, (HWND)lParam );
+ break;
+ case IDC_REM:
+ playlist_Rem( hwndDlg, (HWND)lParam );
+ break;
+ case IDC_ADD:
+ playlist_Add( hwndDlg, (HWND)lParam );
+ break;
+ case IDC_MISC:
+ playlist_Misc( hwndDlg, (HWND)lParam );
+ break;
+ case IDC_LIST:
+ playlist_List( hwndDlg, (HWND)lParam );
+ break;
+ case IDC_DELETE:
+ Playlist_DeleteSelected( 1 );
+ break;
+ case IDC_CROP:
+ Playlist_DeleteSelected( 0 );
+ break;
+ case IDC_PLAYLIST_EXPLOREITEMFOLDER:
+ Playlist_FindSelected();
+ break;
+ case IDC_PLAYLIST_VIEW_FILE_INFO:
+ TagEditor( hwndDlg );
+ break;
+ case IDC_PLAYLIST_EDIT_ENTRY:
+ EditEntry( hwndDlg );
+ break;
+ case IDC_PLAYLIST_DOWNLOAD_ENTRY:
+ DownloadSelectedEntries( hwndDlg );
+ break;
+ case IDC_PLAYLIST_RANDOMIZE:
+ AGAVE_API_PLAYLISTMANAGER->Randomize( &currentPlaylist );
+ playlist_list.RefreshAll();
+ Changed();
+ break;
+ case IDC_PLAYLIST_REVERSE:
+ AGAVE_API_PLAYLISTMANAGER->Reverse( &currentPlaylist );
+ playlist_list.RefreshAll();
+ Changed();
+ break;
+ case IDC_PLAYLIST_RESET_CACHE:
+ Playlist_ResetSelected();
+ break;
+ case IDC_PLAYLIST_INVERT_SELECTION:
+ playlist_list.InvertSelection();
+ break;
+ case IDC_PLAYLIST_SELECT_ALL:
+ playlist_list.SelectAll();
+ break;
+ case IDC_ADD_FILES:
+ if ( CurrentPlaylist_AddFiles( hwndDlg ) ) Changed();
+ SyncPlaylist();
+ break;
+ case IDC_ADD_DIRECTORY:
+ if ( CurrentPlaylist_AddDirectory( hwndDlg ) ) Changed();
+ SyncPlaylist();
+ break;
+ case IDC_ADD_LOCATION:
+ if ( CurrentPlaylist_AddLocation( hwndDlg ) ) Changed();
+ SyncPlaylist();
+ break;
+ case IDC_PLAYLIST_SELECT_NONE:
+ playlist_list.UnselectAll();
+ break;
+ case IDC_PLAYLIST_REMOVE_DEAD:
+ if ( CurrentPlaylist_DeleteMissing() ) Changed();
+ SyncPlaylist();
+ break;
+ case IDC_PLAYLIST_REMOVE_ALL:
+ currentPlaylist.Clear();
+ Changed();
+ SyncPlaylist();
+ break;
+ case IDC_PLAYLIST_RECYCLE_SELECTED:
+ Playlist_RecycleSelected( hwndDlg, 1 );
+ break;
+ case IDC_PLAYLIST_SORT_PATH:
+ currentPlaylist.SortByDirectory();
+ Changed();
+ playlist_list.RefreshAll();
+ break;
+ case IDC_PLAYLIST_SORT_FILENAME:
+ currentPlaylist.SortByFilename();
+ Changed();
+ playlist_list.RefreshAll();
+ break;
+ case IDC_PLAYLIST_SORT_TITLE:
+ currentPlaylist.SortByTitle();
+ Changed();
+ playlist_list.RefreshAll();
+ break;
+ case IDC_EXPORT_PLAYLIST:
+ CurrentPlaylist_Export(hwndDlg);
+ break;
+ case IDC_IMPORT_PLAYLIST_FROM_FILE:
+ if ( currentPlaylist_ImportFromDisk( hwndDlg ) )
+ Changed();
+ SyncPlaylist();
+ break;
+ case IDC_IMPORT_WINAMP_PLAYLIST:
+ if ( currentPlaylist_ImportFromWinamp( hwndDlg ) ) Changed();
+ SyncPlaylist();
+ break;
+ case ID_PLAYLIST_GENERATE_HTML:
+ Playlist_GenerateHtmlPlaylist();
+ break;
+ case IDC_SAVE_PL:
+ if ( changed )
+ {
+ playlist_Save( hwndDlg );
+ Changed( false );
+ }
+ break;
+ }
+}
+
+void playlist_Save( HWND hwndDlg )
+{
+ if ( opened )
+ {
+ if ( currentPlaylistFilename[ 0 ] )
+ {
+ if ( AGAVE_API_PLAYLISTMANAGER->Save( currentPlaylistFilename, &currentPlaylist ) == PLAYLISTMANAGER_FAILED )
+ {
+ wchar_t msg[ 512 ] = { 0 };
+ MessageBox( hwndDlg, WASABI_API_LNGSTRINGW_BUF( IDS_PLAYLIST_ERROR, msg, 512 ), WASABI_API_LNGSTRINGW( IDS_PLAYLIST_ERROR_TITLE ), MB_OK | MB_ICONWARNING );
+ }
+ }
+
+ PlaylistInfo info( playlist_guid );
+ info.SetSize( currentPlaylist.GetNumItems() );
+ info.SetLength( GetTotalLength() );
+ info.IssueSaveCallback();
+ }
+}
+
+void playlist_SaveGUID( GUID _guid )
+{
+ if ( playlist_guid == _guid )
+ {
+ if ( currentPlaylistFilename[ 0 ] )
+ AGAVE_API_PLAYLISTMANAGER->Save( currentPlaylistFilename, &currentPlaylist );
+
+ PlaylistInfo info( playlist_guid );
+ info.SetSize( currentPlaylist.GetNumItems() );
+ info.SetLength( GetTotalLength() );
+ info.IssueSaveCallback();
+ }
+}
+
+void playlist_Destroy( HWND hwndDlg )
+{
+ WASABI_API_APP->app_removeAccelerators( hwndDlg );
+
+ if ( changed )
+ playlist_Save( hwndDlg );
+
+ current_playing[ 0 ] = 0;
+ currentPlaylistFilename[ 0 ] = 0;
+
+ currentPlaylist.Clear();
+ playlist_list.setwnd( NULL );
+
+ RemovePropW( hwndDlg, L"TITLE" );
+
+ opened = false;
+ activeHWND = 0;
+}
+
+void SwapPlayEnqueueInMenu( HMENU listMenu )
+{
+ int playPos = -1, enqueuePos = -1;
+ MENUITEMINFOW playItem = { sizeof( MENUITEMINFOW ), 0, }, enqueueItem = { sizeof( MENUITEMINFOW ), 0, };
+
+ int numItems = GetMenuItemCount( listMenu );
+
+ for ( int i = 0; i < numItems; i++ )
+ {
+ UINT id = GetMenuItemID( listMenu, i );
+ if ( id == IDC_PLAY )
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+
+ GetMenuItemInfoW( listMenu, i, TRUE, &playItem );
+ }
+ else if ( id == IDC_ENQUEUE )
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos = i;
+
+ GetMenuItemInfoW( listMenu, i, TRUE, &enqueueItem );
+ }
+ }
+
+ playItem.wID = IDC_ENQUEUE;
+ enqueueItem.wID = IDC_PLAY;
+
+ SetMenuItemInfoW( listMenu, playPos, TRUE, &playItem );
+ SetMenuItemInfoW( listMenu, enqueuePos, TRUE, &enqueueItem );
+}
+
+void playlist_ContextMenu( HWND hwndDlg, HWND from, int x, int y )
+{
+ if ( from != playlist_list.getwnd() )
+ return;
+
+ POINT pt = { x,y };
+
+ if ( x == -1 || y == -1 ) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT channelRect = { 0 };
+ int selected = playlist_list.GetNextSelected();
+ if ( selected != -1 ) // if something is selected we'll drop the menu from there
+ {
+ playlist_list.GetItemRect( selected, &channelRect );
+ ClientToScreen( hwndDlg, (POINT *)&channelRect );
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect( hwndDlg, &channelRect );
+
+ HWND hHeader = (HWND)SNDMSG( from, LVM_GETHEADER, 0, 0L );
+ RECT headerRect;
+ if ( ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) && GetWindowRect( hHeader, &headerRect ) )
+ channelRect.top += ( headerRect.bottom - headerRect.top );
+ }
+
+ x = channelRect.left;
+ y = channelRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(from, LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ( 0 == ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) || FALSE == GetWindowRect( hHeader, &headerRect ) )
+ SetRectEmpty( &headerRect );
+
+ if ( FALSE != PtInRect( &headerRect, pt ) )
+ return;
+
+ sendToIgnoreID = lastActiveID;
+ HMENU listMenu = GetSubMenu( GetSubMenu( g_context_menus, 3 ), 0 );
+
+ menufucker_t mf = { sizeof( mf ),MENU_MLPLAYLIST,listMenu,0x3000,0x4000,0 };
+
+ UINT menuStatus, do_mf = 0;
+ if ( playlist_list.GetNextSelected( -1 ) == -1 )
+ {
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+ EnableMenuItem( listMenu, 2, MF_BYPOSITION | MF_GRAYED );
+ }
+ else
+ {
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ EnableMenuItem( listMenu, 2, MF_BYPOSITION | MF_ENABLED );
+ sendTo.AddHere( hwndDlg, GetSubMenu( listMenu, 2 ), ML_TYPE_FILENAMES, 1 );
+
+ mf.extinf.mlplaylist.pl = &currentPlaylist;
+ mf.extinf.mlplaylist.list = playlist_list.getwnd();
+
+ pluginMessage message_build = { SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "menufucker_build", IPC_REGISTER_WINAMP_IPCMESSAGE ),(intptr_t)&mf,0 };
+ SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_build, ML_IPC_SEND_PLUGIN_MESSAGE );
+
+ do_mf = 1;
+ }
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_EXPLOREITEMFOLDER, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_VIEW_FILE_INFO, menuStatus );
+ EnableMenuItem( listMenu, IDC_PLAYLIST_EDIT_ENTRY, menuStatus );
+
+ int l_mark = playlist_list.GetSelectionMark();
+
+ if ( l_mark != -1 && !currentPlaylist.entries[ l_mark ]->isLocal() )
+ EnableMenuItem( listMenu, IDC_PLAYLIST_DOWNLOAD_ENTRY, menuStatus );
+ else
+ EnableMenuItem( listMenu, IDC_PLAYLIST_DOWNLOAD_ENTRY, MF_BYCOMMAND | MF_GRAYED );
+
+ EnableMenuItem( listMenu, IDC_DELETE, menuStatus );
+ EnableMenuItem( listMenu, IDC_CROP, menuStatus );
+
+ HMENU cloud_hmenu = 0;
+ if ( playlists_CloudAvailable() )
+ {
+ int mark = playlist_list.GetSelectionMark();
+ if ( mark != -1 )
+ {
+ wchar_t filename[ 1024 ] = { 0 };
+ currentPlaylist.entries[ mark ]->GetFilename( filename, 1024 );
+
+ cloud_hmenu = CreatePopupMenu();
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)&filename, (intptr_t)&cloud_hmenu );
+ if ( cloud_hmenu )
+ {
+ MENUITEMINFOW m = { sizeof( m ), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0 };
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ InsertMenuItemW( listMenu, 3, TRUE, &m );
+
+ wchar_t a[ 100 ] = { 0 };
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF( IDS_CLOUD_SOURCES, a, 100 );
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_hmenu;
+
+ InsertMenuItemW( listMenu, 4, TRUE, &m );
+ }
+ }
+ }
+
+ UpdateMenuItems(hwndDlg, listMenu);
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, listMenu, TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_LEFTALIGN | TPM_RETURNCMD, x, y, hwndDlg, NULL );
+
+ if ( r )
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( r, 0 ), 0 );
+
+ if ( do_mf )
+ {
+ pluginMessage message_result = { SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "menufucker_result", IPC_REGISTER_WINAMP_IPCMESSAGE ), (intptr_t)&mf, r, 0 };
+ SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_result, ML_IPC_SEND_PLUGIN_MESSAGE );
+ }
+
+ switch ( r )
+ {
+ case 0:
+ break;
+ case IDC_PLAYLIST_EXPLOREITEMFOLDER:
+ case IDC_PLAYLIST_VIEW_FILE_INFO:
+ case IDC_PLAYLIST_EDIT_ENTRY:
+ SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
+ break;
+ case IDC_PLAYLIST_DOWNLOAD_ENTRY:
+
+ break;
+ default:
+ if ( !( menuStatus & MF_GRAYED ) && sendTo.WasClicked( r ) )
+ {
+ wchar_t *names = BuildFilenameList( 0 );
+ sendTo.SendFilenames( names );
+ free( names );
+ }
+ else
+ {
+ if ( r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER ) // deals with cloud specific menus
+ {
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+ int mode = 0; // deals with cloud specific menus
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode );
+ // TODO
+ /*switch (mode)
+ {
+ case 1:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"5");
+ break;
+
+ case 2:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+
+ case 4:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+ }
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);*/
+ }
+ }
+ break;
+ }
+
+ if (!(menuStatus & MF_GRAYED))
+ sendTo.Cleanup();
+
+ sendToIgnoreID = 0;
+
+ if ( cloud_hmenu )
+ {
+ DeleteMenu( listMenu, CLOUD_SOURCE_MENUS - 1, MF_BYCOMMAND );
+ DeleteMenu( listMenu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND );
+ DestroyMenu( cloud_hmenu );
+ }
+}
+
+static void playlist_LeftButtonUp( HWND hwndDlg, WPARAM wParam, POINTS pts )
+{
+ if ( SCROLLDIR_NONE != scrollDirection )
+ {
+ KillTimer( hwndDlg, SCROLLTIMER_ID );
+ scrollDirection = SCROLLDIR_NONE;
+ }
+
+ if ( we_are_drag_and_dropping && GetCapture() == hwndDlg )
+ {
+ ReleaseCapture();
+
+ BOOL handled = FALSE;
+ POINT pt;
+ POINTSTOPOINT( pt, pts );
+
+ MapWindowPoints( hwndDlg, HWND_DESKTOP, &pt, 1 );
+ HWND hTarget = WindowFromPoint( pt );
+
+ if ( hTarget == playlist_list.getwnd() )
+ {
+ LVHITTESTINFO hitTest = { 0 };
+ POINTSTOPOINT( hitTest.pt, pts );
+ MapWindowPoints( hwndDlg, playlist_list.getwnd(), &hitTest.pt, 1 );
+ ListView_HitTest( playlist_list.getwnd(), &hitTest );
+
+ size_t position = hitTest.iItem;
+ if ( ( hitTest.flags & ( LVHT_ONITEM ) ) );
+ else if ( hitTest.flags & LVHT_ABOVE )
+ position = 0;
+ else if ( hitTest.flags & ( LVHT_BELOW | LVHT_NOWHERE ) )
+ position = playlist_list.GetCount();
+
+ if ( position != -1 )
+ {
+ RECT itemRect;
+ playlist_list.GetItemRect( position, &itemRect );
+ if ( hitTest.pt.y > ( itemRect.bottom + ( itemRect.top - itemRect.bottom ) / 2 ) )
+ position++;
+
+ Playlist tempList;
+ size_t selected = -1, numDeleted = 0;
+ // first, make a temporary list with all the selected items
+ // being careful to deal with the discrepancy between the listview and the real playlist
+ // as we remove items
+ while ( ( selected = playlist_list.GetNextSelected( selected ) ) != -1 )
+ {
+ tempList.entries.push_back(currentPlaylist.entries.at(selected - numDeleted));
+ currentPlaylist.entries.erase(currentPlaylist.entries.begin() + (selected - numDeleted));
+ if ((selected - numDeleted) < position)
+ position--;
+
+ numDeleted++;
+ }
+ playlist_list.UnselectAll();
+ // if dragging to the end of the playlist, handle things a bit differently from normal
+ if ( position > currentPlaylist.entries.size() )
+ {
+ position--;
+ while ( numDeleted-- )
+ {
+ currentPlaylist.entries.insert(currentPlaylist.entries.end(), tempList.entries.at(0));
+ playlist_list.SetSelected(position++); // we want the same filenames to be selected
+ tempList.entries.erase(tempList.entries.begin());
+ }
+ }
+ else
+ {
+ while ( numDeleted-- )
+ {
+ playlist_list.SetSelected(position); // we want the same filenames to be selected
+ currentPlaylist.entries.insert(currentPlaylist.entries.begin() + position, tempList.entries.at(0));
+ position++;
+ tempList.entries.erase(tempList.entries.begin());
+ }
+ }
+
+ Changed();
+ SyncPlaylist();
+
+ handled = TRUE;
+ }
+ }
+
+ we_are_drag_and_dropping = 0;
+
+ if ( !handled )
+ {
+ mlDropItemStruct m = { 0 };
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = pt;
+
+ pluginHandleIpcMessage( ML_IPC_HANDLEDRAG, (WPARAM)&m );
+
+ if ( m.result > 0 )
+ {
+ wchar_t *names = BuildFilenameList( 0 );
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void *)names;
+
+ pluginHandleIpcMessage( ML_IPC_HANDLEDROP, (WPARAM)&m );
+
+ free( names );
+ }
+ }
+ }
+}
+
+static void CALLBACK playlist_OnScrollTimer( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime )
+{
+ HWND hList = GetDlgItem( hwnd, IDC_PLAYLIST_EDITOR );
+
+ if ( SCROLLDIR_NONE == scrollDirection || NULL == hList )
+ {
+ KillTimer( hwnd, idEvent );
+
+ return;
+ }
+
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+
+ if ( SendMessage( hList, LVM_GETITEMRECT, (WPARAM)0, (LPARAM)&rc ) )
+ {
+ INT height = rc.bottom - rc.top;
+ if ( SCROLLDIR_UP == scrollDirection )
+ height = -height;
+
+ SendMessage( hList, LVM_SCROLL, 0, (LPARAM)height );
+ }
+
+ if ( scrollTimerElapse == scrollDelay )
+ {
+ static INT scrollInterval = 0;
+ if ( 0 == scrollInterval )
+ scrollInterval = GetProfileInt( TEXT( "windows" ), TEXT( "DragScrollInterval" ), DD_DEFSCROLLINTERVAL );
+
+ if ( 0 != scrollInterval )
+ SetTimer( hwnd, idEvent, scrollTimerElapse, playlist_OnScrollTimer );
+ else
+ KillTimer( hwnd, idEvent );
+ }
+}
+
+static INT playlist_GetScrollDirection( HWND hList, POINT pt )
+{
+ static INT scrollZone = 0;
+ if ( 0 == scrollZone )
+ scrollZone = GetProfileInt( TEXT( "windows" ), TEXT( "DragScrollInset" ), DD_DEFSCROLLINSET );
+
+ RECT rc, rcTest;
+ if ( 0 == scrollZone || !GetClientRect( playlist_list.getwnd(), &rc ) )
+ return SCROLLDIR_NONE;
+
+ CopyRect( &rcTest, &rc );
+
+ rcTest.top = rcTest.bottom - scrollZone;
+ if ( PtInRect( &rcTest, pt ) )
+ return SCROLLDIR_DOWN;
+
+ rcTest.top = rc.top;
+ rcTest.bottom = rcTest.top + scrollZone;
+
+ if ( 0 == ( LVS_NOCOLUMNHEADER & GetWindowLongPtr( hList, GWL_STYLE ) ) )
+ {
+ HWND hHeader = (HWND)SendMessage( hList, LVM_GETHEADER, 0, 0L );
+ if ( NULL != hHeader && 0 != ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) )
+ {
+ RECT rcHeader;
+ if ( GetWindowRect( hHeader, &rcHeader ) )
+ {
+ MapWindowPoints( HWND_DESKTOP, hList, ( (POINT *)&rcHeader ) + 1, 1 );
+
+ INT offset = rcHeader.bottom - rc.top;
+ if ( 0 != offset )
+ OffsetRect( &rcTest, 0, offset );
+ }
+ }
+ }
+
+ if ( PtInRect( &rcTest, pt ) )
+ return SCROLLDIR_UP;
+
+ return SCROLLDIR_NONE;
+}
+
+static void playlist_MouseMove( HWND hwndDlg, POINTS pts )
+{
+ if ( we_are_drag_and_dropping && GetCapture() == hwndDlg )
+ {
+ BOOL handled = FALSE;
+ POINT pt;
+ POINTSTOPOINT( pt, pts );
+
+ MapWindowPoints( hwndDlg, HWND_DESKTOP, &pt, 1 );
+ HWND hTarget = WindowFromPoint( pt );
+
+ INT scroll = SCROLLDIR_NONE;
+
+ if ( hTarget == playlist_list.getwnd() )
+ {
+ LVHITTESTINFO hitTest = { 0 };
+ POINTSTOPOINT( hitTest.pt, pts );
+ MapWindowPoints( hwndDlg, playlist_list.getwnd(), &hitTest.pt, 1 );
+
+ int position = ListView_HitTest( playlist_list.getwnd(), &hitTest );
+
+ if ( position != -1 )
+ {
+ scroll = playlist_GetScrollDirection( playlist_list.getwnd(), hitTest.pt );
+ handled = TRUE;
+ }
+ }
+
+ if ( scroll != scrollDirection )
+ {
+ if ( SCROLLDIR_NONE == scroll )
+ {
+ KillTimer( hwndDlg, SCROLLTIMER_ID );
+ }
+ else
+ {
+ if ( SCROLLDIR_NONE == scrollDirection )
+ {
+ if ( 0 == scrollDelay )
+ scrollDelay = GetProfileInt( TEXT( "windows" ), TEXT( "DragScrollDelay" ), DD_DEFSCROLLDELAY );
+
+ if ( 0 != scrollDelay )
+ {
+ scrollTimerElapse = scrollDelay;
+ SetTimer( hwndDlg, SCROLLTIMER_ID, scrollTimerElapse, playlist_OnScrollTimer );
+ }
+ }
+ }
+
+ scrollDirection = scroll;
+ }
+ if ( !handled )
+ {
+ mlDropItemStruct m = { 0 };
+ m.type = ML_TYPE_FILENAMES;
+ m.p = pt;
+ m.flags = 0; //ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ pluginHandleIpcMessage( ML_IPC_HANDLEDRAG, (WPARAM)&m );
+ }
+ else
+ SetCursor( hDragNDropCursor );
+ }
+}
+
+void playlist_Reload( bool forced )
+{
+ if ( opened || forced )
+ {
+ if ( !opened && forced )
+ {
+ PostMessage( plugin.hwndLibraryParent, WM_USER + 30, 0, 0 );
+
+ return;
+ }
+
+ playlist_Load( currentPlaylistFilename );
+ // reverted back to known state so any
+ // of our current changes are now gone
+ Changed( false );
+ SyncPlaylist();
+ }
+}
+
+void playlist_ReloadGUID( GUID _guid )
+{
+ if ( playlist_guid == _guid )
+ playlist_Reload( true );
+}
+
+void playlist_Unload( HWND hwndDlg )
+{
+ currentPlaylist.Clear();
+
+ currentPlaylistFilename[ 0 ] = 0x0000;
+
+ ListView_SetItemCount( playlist_list.getwnd(), 0 );
+ ListView_RedrawItems( playlist_list.getwnd(), 0, 0 );
+
+ UpdatePlaylistTime( hwndDlg );
+}
+
+void playlist_DropFiles( HDROP hDrop )
+{
+ wchar_t temp[ 2048 ] = { 0 };
+
+ int x;
+ int y = DragQueryFileW( hDrop, 0xffffffff, temp, 2048 );
+
+ Playlist newPlaylist;
+ for ( x = 0; x < y; x++ )
+ {
+ DragQueryFileW( hDrop, x, temp, 2048 );
+ if ( PathIsDirectory( temp ) )
+ {
+ PlaylistDirectoryCallback dirCallback( mediaLibrary.GetExtensionList() );
+ AGAVE_API_PLAYLISTMANAGER->LoadDirectory( temp, &newPlaylist, &dirCallback );
+ }
+ else
+ {
+ if ( AGAVE_API_PLAYLISTMANAGER->Load( temp, &newPlaylist ) != PLAYLISTMANAGER_SUCCESS )
+ {
+ //wchar_t title[400];
+ //int length;
+ //mediaLibrary.GetFileInfo(temp, title, 400, &length);
+ //newPlaylist.AppendWithInfo(temp, title, length*1000);
+ newPlaylist.AppendWithInfo( temp, 0, 0 ); // add with NULL info, will be fetched as needed
+ }
+ }
+ }
+
+ LVHITTESTINFO hitTest;
+ DragQueryPoint( hDrop, &hitTest.pt );
+
+ ListView_HitTest( playlist_list.getwnd(), &hitTest );
+
+ if ( hitTest.iItem != -1 )
+ {
+ RECT itemRect;
+ playlist_list.GetItemRect( hitTest.iItem, &itemRect );
+ if ( hitTest.pt.y > ( itemRect.bottom + ( itemRect.top - itemRect.bottom ) / 2 ) )
+ {
+ hitTest.iItem++;
+ if ( hitTest.iItem >= playlist_list.GetCount() )
+ hitTest.iItem = -1;
+ }
+ }
+
+ if ( hitTest.flags & LVHT_BELOW || hitTest.iItem == -1 )
+ currentPlaylist.AppendPlaylist( newPlaylist );
+ else
+ currentPlaylist.InsertPlaylist( newPlaylist, hitTest.iItem );
+
+ DragFinish( hDrop );
+ Changed();
+ SyncPlaylist();
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows( HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE )
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_BURN, IDC_ADD, IDC_REM, IDC_SEL, IDC_MISC, IDC_LIST, IDC_SAVE_PL, IDC_PLSTATUS,
+ GROUP_MAIN, IDC_PLAYLIST_EDITOR
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[ sizeof( controls ) / sizeof( controls[ 0 ] ) ], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect( hwnd, &rc );
+ if ( rc.right == rc.left || rc.bottom == rc.top )
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect( &rg, rc.left, rc.top, rc.right, rc.top );
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect( hwnd, NULL, TRUE );
+
+ for ( index = 0; index < sizeof( controls ) / sizeof( *controls ); index++ )
+ {
+ if ( controls[ index ] >= GROUP_MIN && controls[ index ] <= GROUP_MAX ) // group id
+ {
+ skipgroup = FALSE;
+
+ switch ( controls[ index ] )
+ {
+ case GROUP_STATUSBAR:
+ if ( opened )
+ {
+ wchar_t buffer[ 128 ] = { 0 };
+ GetDlgItemTextW( hwnd, IDC_PLAY, buffer, ARRAYSIZE( buffer ) );
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( GetDlgItem( hwnd, IDC_PLAY ), buffer );
+
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ), rc.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), rc.right, rc.bottom );
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY( 3 );
+ }
+ else
+ {
+ SetRect( &rg, rc.left, rc.top, rc.right, rc.bottom );
+ }
+ break;
+ case GROUP_MAIN:
+ if ( opened )
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ), rc.top, rc.right, rc.bottom );
+ else
+ skipgroup = TRUE;
+ break;
+ }
+
+ continue;
+ }
+
+ if (skipgroup)
+ continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if ( !pl->hwnd )
+ continue;
+
+ GetWindowRect( pl->hwnd, &ri );
+ MapWindowPoints( HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2 );
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch ( pl->id )
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ case IDC_BURN:
+ case IDC_ADD:
+ case IDC_SEL:
+ case IDC_REM:
+ case IDC_MISC:
+ case IDC_LIST:
+ case IDC_SAVE_PL:
+ if ( opened && ( IDC_CUSTOM != pl->id || customAllowed ) )
+ {
+ if ( groupBtn && pl->id == IDC_PLAY && enqueuedef == 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && pl->id == IDC_ENQUEUE && enqueuedef != 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowText( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
+ LONG width = LOWORD( idealSize ) + ( pl->id == IDC_PLAY || pl->id == IDC_ENQUEUE || pl->id == IDC_CUSTOM || pl->id == IDC_SAVE_PL ? WASABI_API_APP->getScaleX( 6 ) : 0 );
+
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+ pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if ( SWP_SHOWWINDOW & pl->flags )
+ rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
+ }
+ else
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ }
+ break;
+ case IDC_PLSTATUS:
+ if ( opened )
+ {
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowTextW( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize( pl->hwnd, buffer );
+
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), rg.right - rg.left, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+ if ( SWP_SHOWWINDOW & pl->flags )
+ rg.top = ( pl->y + pl->cy + WASABI_API_APP->getScaleY( 1 ) );
+ }
+ else
+ {
+ SETLAYOUTPOS( pl, rg.left + WASABI_API_APP->getScaleY( 20 ),
+ rg.top + WASABI_API_APP->getScaleY( 1 ),
+ rg.right - rg.left - WASABI_API_APP->getScaleY( 40 ),
+ rg.bottom - WASABI_API_APP->getScaleY( 2 ) );
+ }
+ break;
+ case IDC_PLAYLIST_EDITOR:
+ if ( opened )
+ {
+ pl->flags |= ( rg.top < rg.bottom ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ), rg.right - rg.left + WASABI_API_APP->getScaleY( 1 ), ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
+ }
+ else
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ }
+
+ break;
+ }
+
+ SETLAYOUTFLAGS( pl, ri );
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+
+ pl++;
+ }
+ else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
+ {
+ ValidateRect( hwnd, &ri );
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if ( pl != layout )
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos( (INT)( pl - layout ) );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ hdwp = DeferWindowPos( hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags );
+
+ if ( hdwp )
+ EndDeferWindowPos( hdwp );
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ switch ( pc->id )
+ {
+ case IDC_PLAYLIST_EDITOR:
+ PostMessage( hwnd, WM_APP + 100, 0, 0 );
+ break;
+ }
+ }
+
+ if ( fRedraw )
+ {
+ GetUpdateRgn( hwnd, rgn, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow( hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN );
+ }
+
+ if ( g_rgnUpdate )
+ {
+ GetUpdateRgn( hwnd, g_rgnUpdate, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR );
+ }
+ }
+ }
+
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ DeleteObject( pc->rgn );
+ }
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn( hwnd, NULL );
+}
+
+INT_PTR CALLBACK view_playlistDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ INT_PTR a = dialogSkinner.Handle( hwndDlg, uMsg, wParam, lParam );
+ if ( a )
+ return a;
+
+ switch (uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ sendTo.InitPopupMenu( wParam );
+ return 0;
+ case WM_MOUSEMOVE:
+ playlist_MouseMove( hwndDlg, MAKEPOINTS( lParam ) );
+ return 0;
+ case WM_LBUTTONUP:
+ playlist_LeftButtonUp( hwndDlg, wParam, MAKEPOINTS( lParam ) );
+ return 0;
+ case WM_PAINT:
+ playlist_Paint( hwndDlg );
+ return 0;
+ case WM_INITDIALOG:
+ playlist_Init( hwndDlg, lParam );
+ return TRUE;
+ case WM_DESTROY:
+ playlist_Destroy( hwndDlg );
+ return 0;
+ case WM_COMMAND:
+ playlist_Command( hwndDlg, wParam, lParam );
+ return 0;
+ case WM_NOTIFY:
+ return playlist_Notify( hwndDlg, wParam, lParam );
+ case WM_CONTEXTMENU:
+ playlist_ContextMenu( hwndDlg, (HWND)wParam, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
+ return 0;
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ case WM_PLAYLIST_RELOAD:
+ playlist_Reload();
+ return 0;
+ case WM_PLAYLIST_UNLOAD:
+ playlist_Unload( hwndDlg );
+ return 0;
+ case WM_DISPLAYCHANGE:
+ LayoutWindows( hwndDlg, TRUE );
+ return 0;
+ case WM_APP + 104:
+ {
+ playlist_UpdateButtonText( hwndDlg, wParam );
+ LayoutWindows( hwndDlg, TRUE );
+ return 0;
+ }
+ case WM_APP + 103:
+ {
+ int current = -1;
+ if ( wParam )
+ {
+ // TODO need to get this done in the background so as not to lock up the ui on large playlists
+ wchar_t fn[ 1024 ] = { 0 };
+ int v = playlist_list.GetCount();
+ for ( int x = 0; x < v; x++ )
+ {
+ currentPlaylist.GetItem( x, fn, 1024 );
+ if ( !lstrcmpi( fn, ( !lParam ? (LPWSTR)wParam : current_playing ) ) )
+ {
+ current = x;
+ break;
+ }
+ }
+ }
+
+ PostMessage( playlist_list.getwnd(), WM_ML_IPC, current, ML_IPC_SKINNEDLISTVIEW_SETCURRENT );
+ }
+ return 0;
+ case WM_DROPFILES: playlist_DropFiles((HDROP)wParam); return 0;
+ case WM_APP+102:
+ {
+ if ( cloud_avail )
+ {
+ int width = 27;
+ MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width );
+ playlist_list.SetColumnWidth( 1, width );
+ MLSkinnedHeader_SetCloudColumn( ListView_GetHeader( playlist_list.getwnd() ), 1 );
+ }
+ }
+ case WM_APP+100:
+ AutoSizePlaylistColumns();
+ if ( !loaded )
+ {
+ loaded = true;
+ SetWindowRedraw( playlist_list.getwnd(), TRUE );
+ }
+ return 0;
+ case WM_WINDOWPOSCHANGED:
+ if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE ) & ( (WINDOWPOS *)lParam )->flags ) || ( SWP_FRAMECHANGED & ( (WINDOWPOS *)lParam )->flags ) )
+ {
+ LayoutWindows( hwndDlg, !( SWP_NOREDRAW & ( (WINDOWPOS *)lParam )->flags ) );
+ }
+ return 0;
+ case WM_USER + 0x200:
+ SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, 1 ); // yes, we support no - redraw resize
+ return TRUE;
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD( wParam );
+ offsetY = (short)HIWORD( wParam );
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+ case WM_ML_CHILDIPC:
+ {
+ if ( lParam == ML_CHILDIPC_DROPITEM && wParam )
+ {
+ mlDropItemStruct *dis = (mlDropItemStruct *)wParam;
+ if ( dis )
+ {
+ switch ( dis->type )
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_ITEMRECORDLISTW:
+ case ML_TYPE_CDTRACKS:
+ dis->result = 1;
+ break;
+ default:
+ dis->result = -1;
+ break;
+ }
+ }
+
+ return 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static wchar_t entryFN[ FILENAME_SIZE ];
+static wchar_t titleFN[ FILETITLE_SIZE ];
+
+static INT_PTR CALLBACK entryProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ SendDlgItemMessage( hwndDlg, IDC_OLD, EM_SETLIMITTEXT, FILENAME_SIZE, 0 );
+ SendDlgItemMessage( hwndDlg, IDC_NEW, EM_SETLIMITTEXT, FILENAME_SIZE, 0 );
+
+ SendDlgItemMessage( hwndDlg, IDC_OLD_TITLE, EM_SETLIMITTEXT, FILETITLE_SIZE, 0 );
+ SendDlgItemMessage( hwndDlg, IDC_NEW_TITLE, EM_SETLIMITTEXT, FILETITLE_SIZE, 0 );
+
+ SetDlgItemTextW( hwndDlg, IDC_OLD, entryFN );
+ SetDlgItemTextW( hwndDlg, IDC_OLD_TITLE, titleFN );
+
+ SetDlgItemTextW( hwndDlg, IDC_NEW, entryFN );
+ SetDlgItemTextW( hwndDlg, IDC_NEW_TITLE, titleFN );
+
+ return TRUE;
+ }
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ GetDlgItemTextW( hwndDlg, IDC_NEW, entryFN, FILENAME_SIZE );
+ GetDlgItemTextW( hwndDlg, IDC_NEW_TITLE, titleFN, FILETITLE_SIZE );
+ EndDialog( hwndDlg, 1 );
+ return 0;
+ case IDCANCEL:
+ EndDialog( hwndDlg, 0 );
+ return 0;
+ case IDC_PLAYLIST_EDIT_ENTRY_BROWSE:
+ {
+ wchar_t buf[ FILENAME_SIZE ] = { 0 };
+ UINT len = GetDlgItemTextW( hwndDlg, IDC_NEW, buf, FILENAME_SIZE ) + 1;
+ OPENFILENAME of = { 0 };
+ of.lStructSize = sizeof( OPENFILENAME );
+ of.hwndOwner = hwndDlg;
+ of.nMaxFileTitle = 32;
+ of.lpstrFilter = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 1, IPC_GET_EXTLISTW );
+ of.nMaxCustFilter = 1024;
+ of.lpstrFile = buf;
+ of.nMaxFile = FILENAME_SIZE;
+ of.lpstrTitle = WASABI_API_LNGSTRINGW( IDS_BROWSE_FOR_PLEDIT_ENTRY );
+ of.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_ENABLESIZING;
+
+ if ( GetOpenFileName( &of ) )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ mediaLibrary.GetFileInfo( buf, title, FILETITLE_SIZE, NULL );
+
+ SetDlgItemTextW( hwndDlg, IDC_NEW_TITLE, title );
+ SetDlgItemTextW( hwndDlg, IDC_NEW, buf );
+ }
+
+ GlobalFree( (void *)of.lpstrFilter );
+ break;
+ }
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+void EditEntry( HWND parent )
+{
+ int i = playlist_list.GetCount();
+ while ( i-- )
+ {
+ if ( playlist_list.GetSelected( i ) )
+ {
+ currentPlaylist.GetItem( i, entryFN, FILENAME_SIZE );
+ currentPlaylist.GetItemTitle( i, titleFN, FILETITLE_SIZE );
+
+ INT_PTR res = WASABI_API_DIALOGBOXW( IDD_EDIT_FN, parent, entryProc );
+ if ( res == 1 )
+ {
+ // if the file has changed, force an update of the item'l_enqueue_file file time since we're not
+ // going to allow the view to automatically do such things since with the changes to
+ // support editing the title the automatic title lookup after an edit was disabled
+
+ wchar_t filepath[ FILENAME_SIZE ] = { 0 };
+ currentPlaylist.GetItem( i, filepath, FILENAME_SIZE );
+ if ( lstrcmpi( filepath, entryFN ) )
+ {
+ int length = -1;
+ mediaLibrary.GetFileInfo( entryFN, NULL, NULL, &length );
+ currentPlaylist.SetItemLengthMilliseconds( i, length * 1000 );
+ }
+
+ currentPlaylist.SetItemFilename( i, entryFN );
+ currentPlaylist.SetItemTitle( i, titleFN );
+
+ Changed();
+ }
+ else
+ break;
+ }
+ }
+
+ SyncPlaylist();
+}
+
+
+
+class DownloadEntry : public ifc_downloadManagerCallback
+{
+public:
+ DownloadEntry( const wchar_t *p_url, const wchar_t *p_destination_filepath, const wchar_t *p_title, const wchar_t *p_source )
+ {
+ this->_url = _wcsdup( p_url );
+ this->_destination_filepath = _wcsdup( p_destination_filepath );
+ this->_title = _wcsdup( p_title );
+ this->_source = _wcsdup( p_source );
+ }
+
+ void OnInit( DownloadToken p_token )
+ {
+ api_httpreceiver *l_http = WAC_API_DOWNLOADMANAGER->GetReceiver( p_token );
+ if ( l_http )
+ {
+ l_http->AllowCompression();
+ l_http->addheader( "Accept: */*" );
+ }
+ }
+
+ void OnConnect( DownloadToken p_token )
+ {
+ // ---- create file handle
+ _hFile = CreateFileW( _destination_filepath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if ( _hFile == INVALID_HANDLE_VALUE )
+ this->cancelDownload( p_token );
+
+ this->getContentLength( p_token );
+ }
+
+ void OnTick( DownloadToken p_token )
+ {
+ static bool l_was_renamed = false;
+
+ const char* l_file_extention = WAC_API_DOWNLOADMANAGER->GetExtention(p_token);
+
+ if (l_file_extention && *l_file_extention)
+ {
+ wa::strings::wa_string l_destination_filepath_old( _destination_filepath );
+ wa::strings::wa_string l_destination_filepath_new( _destination_filepath );
+
+ if ( !l_destination_filepath_new.contains(l_file_extention) )
+ {
+ l_destination_filepath_new.append( "." );
+ l_destination_filepath_new.append(l_file_extention);
+
+ if ( wa::files::file_exists( _destination_filepath.c_str() ) )
+ {
+ if ( _hFile != INVALID_HANDLE_VALUE )
+ CloseHandle( _hFile );
+ }
+
+ if ( std::rename( l_destination_filepath_old.GetA().c_str(), l_destination_filepath_new.GetA().c_str() ) )
+ {
+ _destination_filepath = l_destination_filepath_new.GetW();
+
+ _hFile = CreateFileW( _destination_filepath.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
+ }
+ }
+ }
+ }
+
+ void OnData( DownloadToken p_token, void *p_data, size_t p_data_size )
+ {
+ this->getContentLength( p_token );
+
+ if ( _hFile == INVALID_HANDLE_VALUE )
+ {
+ // ---- get the file extention
+ const char* l_file_extention = WAC_API_DOWNLOADMANAGER->GetExtention(p_token);
+
+ bool l_extension_found = false;
+
+ wa::strings::wa_string l_destination_filepath_old( _destination_filepath );
+ wa::strings::wa_string l_destination_filepath_new( _destination_filepath );
+
+ if ( l_file_extention && *l_file_extention )
+ {
+ if ( !l_destination_filepath_new.contains( l_file_extention ) )
+ {
+ l_destination_filepath_new.append( "." );
+ l_destination_filepath_new.append( l_file_extention );
+
+ l_extension_found = true;
+ }
+ }
+ else
+ {
+ std::string l_filepath(l_file_extention);
+
+ int l_position = l_filepath.find_first_of( "." );
+
+ if ( l_filepath.size() - l_position > 5 )
+ {
+ std::string l_url( WAC_API_DOWNLOADMANAGER->GetUrl( p_token ) );
+
+ l_position = l_url.find_last_of( "." );
+
+ if ( l_url.size() - l_position < 5 )
+ {
+ l_destination_filepath_new.append( "." );
+ l_destination_filepath_new.append( l_url.substr( l_position + 1 ) );
+
+ l_extension_found = true;
+ }
+ }
+ }
+
+ if ( l_extension_found )
+ {
+ if ( wa::files::file_exists( _destination_filepath.c_str() ) )
+ {
+ if ( _hFile != INVALID_HANDLE_VALUE )
+ CloseHandle( _hFile );
+ }
+
+ if ( std::rename( l_destination_filepath_old.GetA().c_str(), l_destination_filepath_new.GetA().c_str() ) )
+ {
+ _destination_filepath = l_destination_filepath_new.GetW();
+ }
+ }
+
+ // ---- create file handle
+ _hFile = CreateFileW( _destination_filepath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ // ---- OnConnect to be removed once dlmgr is fixed
+ }
+
+ // ---- OnData
+ // ---- if file handle is invalid, then cancel download
+ if ( _hFile == INVALID_HANDLE_VALUE )
+ {
+ this->cancelDownload( p_token );
+
+ return;
+ }
+
+ this->_downloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( p_token );
+
+ if ( p_data_size > 0 )
+ {
+ // ---- hFile is valid handle, and write to disk
+ DWORD numWritten = 0;
+ WriteFile( _hFile, p_data, (DWORD)p_data_size, &numWritten, FALSE );
+
+ // ---- failed writing the number of datalen characters, cancel download
+ if ( numWritten != p_data_size )
+ {
+ this->cancelDownload( p_token );
+
+ return;
+ }
+ }
+ }
+
+ void OnCancel( DownloadToken p_token )
+ {
+ if ( _hFile != INVALID_HANDLE_VALUE )
+ {
+ CloseHandle( _hFile );
+ DeleteFileW( _destination_filepath.c_str() );
+ }
+
+ this->resumeNextPendingDownload( p_token );
+
+ this->Release();
+ }
+
+ void OnError( DownloadToken p_token, int p_error )
+ {
+ wsprintfW( _log_message_w, L"An error occurs when downloading the file '%s' ( %d ) !", WAC_API_DOWNLOADMANAGER->GetTitle( p_token ), p_error );
+
+ LOG_ERROR( _log_message_w );
+
+ if ( _hFile != INVALID_HANDLE_VALUE )
+ {
+ CloseHandle( _hFile );
+ DeleteFileW( _destination_filepath.c_str() );
+ }
+
+ this->resumeNextPendingDownload( p_token );
+
+ this->Release();
+ }
+
+ void OnFinish( DownloadToken p_token )
+ {
+ if ( _hFile != INVALID_HANDLE_VALUE )
+ {
+ CloseHandle( _hFile );
+
+ _hFile = INVALID_HANDLE_VALUE;
+ }
+
+ // Update the playlist with the local path
+ const wchar_t *l_file_location = WAC_API_DOWNLOADMANAGER->GetLocation( p_token );
+
+ wa::strings::wa_string l_new_filename( l_file_location );
+
+ l_new_filename.append( "." );
+ l_new_filename.append( WAC_API_DOWNLOADMANAGER->GetExtention( p_token ) );
+
+ WAC_API_DOWNLOADMANAGER->SetLocation( p_token, l_new_filename.GetW().c_str() );
+
+ for ( pl_entry *l_pl_entry : currentPlaylist.entries )
+ {
+ if ( wcscmp( l_pl_entry->filetitle, WAC_API_DOWNLOADMANAGER->GetTitle( p_token ) ) == 0 )
+ {
+ l_pl_entry->SetFilename( l_file_location );
+
+ if ( AGAVE_API_PLAYLISTMANAGER->Save( currentPlaylistFilename, &currentPlaylist ) != PLAYLISTMANAGER_FAILED )
+ playlist_list.RefreshAll();
+
+ break;
+ }
+ }
+
+
+ this->resumeNextPendingDownload( p_token );
+
+ this->Release();
+ }
+
+
+ int GetSource( wchar_t *source, size_t source_cch )
+ {
+ if ( !this->_source.empty() )
+ return wcscpy_s( source, source_cch, this->_source.c_str() );
+ else
+ return 1;
+ }
+
+ int GetTitle( wchar_t *title, size_t title_cch )
+ {
+ return wcscpy_s( title, title_cch, _title.c_str() );
+ }
+
+ int GetLocation( wchar_t *location, size_t location_cch )
+ {
+ return wcscpy_s( location, location_cch, this->_destination_filepath.c_str() );
+ }
+
+
+ size_t AddRef()
+ {
+ return _ref_count.fetch_add( 1 );
+ }
+
+ size_t Release()
+ {
+ if ( _ref_count.load() == 0 )
+ return _ref_count.load();
+
+ std::size_t r = _ref_count.fetch_sub( 1 );
+ if ( r == 0 )
+ delete( this );
+
+ return r;
+ }
+
+
+protected:
+ RECVS_DISPATCH;
+
+private:
+ inline void resumeNextPendingDownload( DownloadToken p_token )
+ {
+ {
+ AutoLock lock( itemsPlaylistQueueLock );
+
+ size_t l_index = 0;
+ for ( DownloadToken &l_download_token : plDownloads )
+ {
+ if ( l_download_token == p_token )
+ {
+ plDownloads.erase( plDownloads.begin() + l_index );
+ break;
+ }
+
+ ++l_index;
+ }
+ }
+
+ for ( DownloadToken &l_download_token : plDownloads )
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) )
+ {
+ WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token );
+ break;
+ }
+ }
+ }
+
+ inline void cancelDownload( DownloadToken p_token )
+ {
+ wsprintfW( _log_message_w, L"The file '%s' was cancelled !", WAC_API_DOWNLOADMANAGER->GetTitle( p_token ) );
+
+ LOG_ERROR( _log_message_w );
+
+ WAC_API_DOWNLOADMANAGER->CancelDownload( p_token );
+
+ this->resumeNextPendingDownload( p_token );
+ }
+
+ inline void getContentLength( DownloadToken p_token )
+ {
+ if ( p_token == NULL )
+ return;
+
+ // ---- retrieve total size
+ api_httpreceiver* l_http = WAC_API_DOWNLOADMANAGER->GetReceiver(p_token);
+ if ( l_http )
+ this->_totalSize = l_http->content_length();
+ }
+
+
+ std::wstring _url;
+ std::wstring _destination_filepath;
+ std::wstring _title;
+ std::wstring _source;
+
+ HANDLE _hFile = INVALID_HANDLE_VALUE;
+ size_t _totalSize = 0;
+ size_t _downloaded = 0;
+
+ std::atomic<std::size_t> _ref_count = 1;
+};
+
+#define CBCLASS DownloadEntry
+START_DISPATCH;
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONINIT, OnInit )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCONNECT, OnConnect )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONTICK, OnTick )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONDATA, OnData )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCANCEL, OnCancel )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONFINISH, OnFinish )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONERROR, OnError )
+CB( IFC_DOWNLOADMANAGERCALLBACK_GETSOURCE, GetSource )
+CB( IFC_DOWNLOADMANAGERCALLBACK_GETTITLE, GetTitle )
+CB( IFC_DOWNLOADMANAGERCALLBACK_GETLOCATION, GetLocation )
+CB( ADDREF, AddRef )
+CB( RELEASE, Release )
+END_DISPATCH;
+#undef CBCLASS
+
+
+void DownloadSelectedEntries( HWND parent )
+{
+ int l_count = playlist_list.GetCount();
+
+ pl_entry *l_pl_entry = NULL;
+
+ for ( int i = 0; i < l_count; ++i )
+ {
+ if ( playlist_list.GetSelected( i ) )
+ {
+ if ( !currentPlaylist.IsLocal( i ) )
+ {
+ currentPlaylist.GetItem( i, entryFN, FILENAME_SIZE );
+ currentPlaylist.GetItemTitle( i, titleFN, FILETITLE_SIZE );
+
+ if ( WAC_API_DOWNLOADMANAGER )
+ {
+ wa::strings::wa_string l_url( entryFN );
+
+ TCHAR szPath[ MAX_PATH ];
+
+ if ( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_MYMUSIC, NULL, 0, szPath ) ) )
+ {
+ l_pl_entry = currentPlaylist.entries[ i ];
+
+ bool l_ext_is_find = false;
+
+ std::string l_s_url( l_url.GetA() );
+
+ wa::strings::wa_string l_full_local_filename( szPath );
+ l_full_local_filename.append( PLAYLIST_DOWNLOAD_SUBFOLDER );
+
+ if ( !wa::files::folder_exists( l_full_local_filename.GetA().c_str() ) )
+ _mkdir( l_full_local_filename.GetA().c_str() );
+
+ wa::strings::wa_string l_title_filename( titleFN );
+ l_title_filename.replaceAll( "/", "_" );
+ l_title_filename.replaceAll( "\\", "_" );
+ l_title_filename.replaceAll( ":", "_" );
+ l_title_filename.replaceAll( "*", "_" );
+ l_title_filename.replaceAll( "?", "_" );
+ l_title_filename.replaceAll( "\"", "_" );
+ l_title_filename.replaceAll( "<", "_" );
+ l_title_filename.replaceAll( ">", "_" );
+ l_title_filename.replaceAll( "|", "_" );
+
+ l_full_local_filename.append( l_title_filename.GetW() );
+
+ l_ext_is_find = ( l_pl_entry->_extended_infos.count( L"ext" ) == 1 );
+
+ if ( l_ext_is_find )
+ {
+ wa::strings::wa_string l_file_to_verify( l_full_local_filename.GetW() );
+
+ l_file_to_verify.append( "." );
+ l_file_to_verify.append( (*l_pl_entry->_extended_infos.find(L"ext")).second );
+
+ if ( wa::files::file_exists( l_file_to_verify.GetW().c_str() ) )
+ {
+ l_pl_entry->SetFilename( l_file_to_verify.GetW().c_str() );
+
+ if ( AGAVE_API_PLAYLISTMANAGER->Save( currentPlaylistFilename, &currentPlaylist ) != PLAYLISTMANAGER_FAILED )
+ playlist_list.RefreshAll();
+
+ return;
+ }
+ }
+
+ wa::strings::wa_string l_source( l_s_url );
+
+ l_source.replace( "http://", "" );
+ l_source.replace( "https://", "" );
+
+ l_source = l_source.mid( 0, l_source.findFirst( "/" ) );
+
+ wa::strings::wa_string l_redirect_url( WINAMP_REDIRECT_LINK_PROXY_FILE );
+ l_redirect_url.append( l_url.GetW() );
+
+ if ( !l_ext_is_find )
+ l_redirect_url = l_url.GetW();
+
+
+ DownloadEntry *_downloadEntry = new DownloadEntry( l_redirect_url.GetW().c_str(), l_full_local_filename.GetW().c_str(), titleFN, l_source.GetW().c_str() );
+
+ if ( plDownloads.size() < SIMULTANEOUS_DOWNLOADS )
+ {
+ DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( l_redirect_url.GetA().c_str(), _downloadEntry, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_UI );
+ plDownloads.push_back( dt );
+ }
+ else
+ {
+ DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( l_redirect_url.GetA().c_str(), _downloadEntry, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_PENDING | api_downloadManager::DOWNLOADEX_UI );
+ plDownloads.push_back( dt );
+ }
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/view_playlists.cpp b/Src/Plugins/Library/ml_playlists/view_playlists.cpp
new file mode 100644
index 00000000..b84b0f52
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/view_playlists.cpp
@@ -0,0 +1,1953 @@
+#include <windows.h>
+#include <shlwapi.h>
+#include <shlobj.h>
+#include <shellapi.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "main.h"
+#include "nu/listview.h"
+
+#include "resource.h"
+#include "Playlist.h"
+#include "replicant/nu/AutoChar.h"
+#include "../../General/gen_ml/ml_ipc.h"
+#include "SendTo.h"
+#include "api__ml_playlists.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#include "nu/menushortcuts.h"
+#include "ml_local/api_mldb.h"
+#include "ml_pmp/pmp.h"
+#include "replicant/nswasabi/ReferenceCounted.h"
+#include "replicant/nx/win/nxstring.h"
+
+void playlist_UpdateButtonText( HWND hwndDlg, int enqueuedef );
+BOOL playlist_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags = 0 );
+
+static std::vector<GUID> playlistGUIDs;
+using namespace Nullsoft::Utility;
+SendToMenu sendTo;
+static W_ListView m_playlistslist;
+
+int root_is_drag_and_dropping = 0;
+HINSTANCE cloud_hinst = 0;
+static int last_item1 = -1;
+int IPC_GET_CLOUD_HINST = -1;
+int IPC_GET_CLOUD_ACTIVE = -1;
+int cloud_avail = 0;
+int normalimage = 0;
+int cloudImage = 0;
+
+static void AutoSizePlaylistColumns()
+{
+ m_playlistslist.AutoSizeColumn( 2 );
+ m_playlistslist.AutoSizeColumn( 3 );
+
+ RECT channelRect;
+ GetClientRect( m_playlistslist.getwnd(), &channelRect );
+
+ ListView_SetColumnWidth( m_playlistslist.getwnd(), 0, channelRect.right - m_playlistslist.GetColumnWidth( 1 ) - m_playlistslist.GetColumnWidth( 2 ) - m_playlistslist.GetColumnWidth( 3 ) );
+}
+
+static bool opened = false, loaded = false;
+
+void RefreshPlaylistsList()
+{
+ if ( opened )
+ {
+ playlistGUIDs.clear();
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ playlistGUIDs.reserve( count );
+
+ for ( size_t i = 0; i < count; i++ )
+ playlistGUIDs.push_back( AGAVE_API_PLAYLISTS->GetGUID( i ) );
+
+ ListView_SetItemCount( m_playlistslist.getwnd(), playlistGUIDs.size() );
+ ListView_RedrawItems( m_playlistslist.getwnd(), 0, playlistGUIDs.size() - 1 );
+ }
+}
+
+void ImportPlaylist( const wchar_t *srcFilename, bool callback = false )
+{
+ wchar_t l_src_filename[ MAX_PATH ] = { 0 };
+ lstrcpynW( l_src_filename, srcFilename, MAX_PATH );
+
+ wchar_t filename[ MAX_PATH ] = { 0 };
+ wchar_t *filenameptr = ( !g_config->ReadInt( L"external", 0 ) ? createPlayListDBFileName( filename ) : 0 );
+ size_t numItems = AGAVE_API_PLAYLISTMANAGER->Copy( filename, l_src_filename );
+
+ // get the filename of the imported playlist
+ PathRemoveExtensionW( l_src_filename );
+ PathStripPathW( l_src_filename );
+
+ // just incase we've had external playlists added / imported
+ // we spin through and abort trying to re-add any that match
+ if ( g_config->ReadInt( L"external", 0 ) )
+ {
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ for ( size_t i = 0; i != count; i++ )
+ {
+ PlaylistInfo info( i );
+ if ( info.Valid() )
+ {
+ if ( !lstrcmpiW( srcFilename, info.GetFilename() ) )
+ {
+ wchar_t titleStr[ 96 ] = { 0 };
+ MessageBox( currentView, WASABI_API_LNGSTRINGW( IDS_EXTERNAL_ALREADY_ADDED ), WASABI_API_LNGSTRINGW_BUF( IDS_PL_FILE_MNGT, titleStr, 96 ), MB_OK | MB_ICONWARNING );
+
+ return;
+ }
+ }
+ }
+ }
+
+ if ( l_src_filename[ 0 ] )
+ AddPlaylist( ( !callback ? 1 : 2 ), l_src_filename, ( !g_config->ReadInt( L"external", 0 ) ? filenameptr : srcFilename ), 1, g_config->ReadInt( L"cloud", 1 ), numItems );
+ else
+ AddPlaylist( ( !callback ? 1 : 2 ), WASABI_API_LNGSTRINGW( IDS_IMPORTED_PLAYLIST ), ( !g_config->ReadInt( L"external", 0 ) ? filenameptr : srcFilename ), 1, g_config->ReadInt( L"cloud", 1 ), numItems );
+}
+
+void playlists_ImportExternalPrompt( HWND hwndDlg )
+{
+ // TODO decide if better to show the message on all changes or only once and
+ // then just leave the user to it in the future otherwise leave as it is
+ //if (!g_config->ReadInt("external_prompt", 0) && g_config->ReadInt("external", 0))
+ if ( !g_config->ReadInt( L"external", 0 ) )
+ {
+ wchar_t titleStr[ 96 ] = { 0 };
+ if ( MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_EXTERNAL_CHECKED ), WASABI_API_LNGSTRINGW_BUF( IDS_PL_FILE_MNGT, titleStr, 96 ), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 ) == IDYES )
+ {
+ g_config->WriteInt( L"external", ( IsDlgButtonChecked( hwndDlg, IDC_EXTERNAL ) == BST_CHECKED ) );
+ }
+ else
+ {
+ CheckDlgButton( hwndDlg, IDC_EXTERNAL, g_config->ReadInt( L"external", 0 ) );
+ }
+
+ g_config->WriteInt( L"external_prompt", 1 );
+ }
+ else
+ {
+ g_config->WriteInt( L"external", ( IsDlgButtonChecked( hwndDlg, IDC_EXTERNAL ) == BST_CHECKED ) );
+ }
+}
+
+UINT_PTR CALLBACK Playlist_OFNHookProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_INITDIALOG )
+ {
+ int cloud = playlists_CloudAvailable();
+ HWND cloudWnd = GetDlgItem( hwndDlg, IDC_CLOUD );
+ if ( IsWindow( cloudWnd ) )
+ {
+ ShowWindow( cloudWnd, cloud );
+ CheckDlgButton( hwndDlg, IDC_CLOUD, AddToCloud() );
+ }
+
+ CheckDlgButton( hwndDlg, IDC_EXTERNAL, g_config->ReadInt( L"external", 0 ) );
+
+ if ( !cloud )
+ {
+ HWND external = GetDlgItem( hwndDlg, IDC_EXTERNAL );
+ if ( IsWindow( external ) && IsWindow( cloudWnd ) )
+ {
+ RECT r = { 0 }, cl = { 0 };
+ GetWindowRect( external, &r );
+ GetWindowRect( cloudWnd, &cl );
+ ScreenToClient( hwndDlg, (LPPOINT)&r );
+ ScreenToClient( hwndDlg, (LPPOINT)&cl );
+ SetWindowPos( external, NULL, cl.left, r.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_SHOWWINDOW );
+ }
+ }
+ }
+ else if ( uMsg == WM_COMMAND )
+ {
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_CLOUD:
+ playlists_AddToCloudPrompt( hwndDlg );
+ return 1;
+
+ case IDC_EXTERNAL:
+ playlists_ImportExternalPrompt( hwndDlg );
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void Playlist_importFromFile( HWND dlgparent )
+{
+ wchar_t oldCurPath[ MAX_PATH ] = { 0 };
+ wchar_t newCurPath[ MAX_PATH ] = { 0 };
+ bool skipRes = false;
+
+ GetCurrentDirectoryW( MAX_PATH, oldCurPath );
+
+retry:
+ wchar_t temp[ 1024 ] = { 0 };
+ wchar_t filter[ 1024 ] = { 0 };
+ AGAVE_API_PLAYLISTMANAGER->GetFilterList( filter, 1024 );
+
+ OPENFILENAMEW l = { sizeof( l ), 0 };
+ l.hwndOwner = dlgparent;
+ l.lpstrFilter = filter;
+ l.lpstrFile = temp;
+ l.nMaxFile = 1024;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW( IDS_IMPORT_PLAYLIST );
+ l.lpstrDefExt = L"m3u";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
+ l.lpfnHook = Playlist_OFNHookProc;
+ l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING | OFN_ENABLETEMPLATE;
+ l.lpTemplateName = MAKEINTRESOURCE( IDD_IMPORT_PLFLD );
+ l.hInstance = ( !skipRes ? WASABI_API_LNG_HINST : WASABI_API_ORIG_HINST );
+
+ if ( GetOpenFileNameW( &l ) )
+ {
+ GetCurrentDirectoryW( MAX_PATH, newCurPath );
+ WASABI_API_APP->path_setWorkingPath( newCurPath );
+
+ ImportPlaylist( temp );
+ }
+ else
+ {
+ // deals with the extra child dialog not being present in language packs
+ // so we re-spin and try to load the native version before just failing
+ DWORD res = CommDlgExtendedError();
+ if ( res == CDERR_NOTEMPLATE || res == CDERR_FINDRESFAILURE )
+ {
+ if ( !skipRes )
+ {
+ skipRes = true;
+ goto retry;
+ }
+ }
+ }
+
+ SetCurrentDirectoryW( oldCurPath );
+}
+
+void Playlists_ReplaceBadPathChars( LPWSTR pszPath )
+{
+ if ( NULL == pszPath )
+ return;
+
+ while ( L'\0' != *pszPath )
+ {
+ switch ( *pszPath )
+ {
+ case L'?':
+ case L'/':
+ case L'\\':
+ case L':':
+ case L'*':
+ case L'\"':
+ case L'<':
+ case L'>':
+ case L'|':
+ *pszPath = L'_';
+ break;
+ default:
+ if ( *pszPath < 32 )
+ *pszPath = L'_';
+ break;
+ }
+
+ pszPath = CharNextW( pszPath );
+ }
+}
+
+void Playlist_export( HWND dlgparent, const wchar_t *name, const wchar_t *srcm3u )
+{
+ wchar_t oldCurPath[ MAX_PATH ] = { 0 };
+ GetCurrentDirectoryW( MAX_PATH, oldCurPath );
+
+ wchar_t temp[ MAX_PATH ] = { 0 };
+ OPENFILENAMEW l = { sizeof( OPENFILENAMEW ), 0 };
+ l.hwndOwner = dlgparent;
+ l.hInstance = plugin.hDllInstance;
+
+ lstrcpynW( temp, name, MAX_PATH );
+ Playlists_ReplaceBadPathChars( temp );
+
+ l.nFilterIndex = g_config->ReadInt( L"filter", 3 );
+ l.lpstrFilter = (LPCWSTR)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 3, IPC_GET_PLAYLIST_EXTLISTW );
+ l.lpstrFile = temp;
+ l.nMaxFile = MAX_PATH;
+ l.lpstrTitle = WASABI_API_LNGSTRINGW( IDS_EXPORT_PLAYLIST );
+ l.lpstrDefExt = L"m3u";
+ l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
+ l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_OVERWRITEPROMPT;
+
+ if ( GetSaveFileNameW( &l ) )
+ {
+ wchar_t newCurPath[ MAX_PATH ] = { 0 };
+ GetCurrentDirectoryW( MAX_PATH, newCurPath );
+ WASABI_API_APP->path_setWorkingPath( newCurPath );
+
+ AGAVE_API_PLAYLISTMANAGER->Copy( temp, srcm3u );
+ }
+
+ g_config->WriteInt( L"filter", l.nFilterIndex );
+ SetCurrentDirectoryW( oldCurPath );
+}
+
+void importPlaylistFolder( const wchar_t *path, int dorecurs )
+{
+ wchar_t tmppath[ MAX_PATH ] = { 0 };
+ PathCombineW( tmppath, path, L"*" );
+
+ WIN32_FIND_DATAW d;
+ HANDLE h = FindFirstFileW( tmppath, &d );
+ if ( h == INVALID_HANDLE_VALUE )
+ return;
+
+ do
+ {
+ wchar_t l_playlist_folder[ MAX_PATH ] = { 0 };
+ PathCombineW( l_playlist_folder, path, d.cFileName );
+
+ if ( d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && lstrcmpW( d.cFileName, L"." ) && lstrcmpW( d.cFileName, L".." ) && dorecurs )
+ {
+ importPlaylistFolder( l_playlist_folder, dorecurs );
+ continue;
+ }
+
+ if ( AGAVE_API_PLAYLISTMANAGER->CanLoad( l_playlist_folder ) )
+ {
+ ImportPlaylist( l_playlist_folder, true );
+ }
+ } while ( FindNextFileW( h, &d ) != 0 );
+
+ if ( h != INVALID_HANDLE_VALUE )
+ FindClose( h );
+}
+
+void Shell_Free( void *p )
+{
+ IMalloc *m;
+ SHGetMalloc( &m );
+
+ m->Free( p );
+}
+
+static INT_PTR CALLBACK browseCheckBoxProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_INITDIALOG )
+ {
+ int cloud = playlists_CloudAvailable();
+ HWND cloudWnd = GetDlgItem( hwndDlg, IDC_CLOUD );
+ if ( IsWindow( cloudWnd ) )
+ {
+ ShowWindow( cloudWnd, cloud );
+ CheckDlgButton( hwndDlg, IDC_CLOUD, AddToCloud() );
+ }
+
+ if ( g_config->ReadInt( L"importplfoldrecurs", 1 ) )
+ CheckDlgButton( hwndDlg, IDC_CHECK1, BST_CHECKED );
+
+ CheckDlgButton( hwndDlg, IDC_CLOUD, AddToCloud() );
+ CheckDlgButton( hwndDlg, IDC_EXTERNAL, g_config->ReadInt( L"external", 0 ) );
+ }
+
+ if ( uMsg == WM_COMMAND )
+ {
+ if ( LOWORD( wParam ) == IDC_CHECK1 )
+ {
+ g_config->WriteInt( L"importplfoldrecurs", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK1 ) );
+ }
+ else if ( LOWORD( wParam ) == IDC_CLOUD )
+ {
+ playlists_AddToCloudPrompt( hwndDlg );
+ }
+ else if ( LOWORD( wParam ) == IDC_EXTERNAL )
+ {
+ playlists_ImportExternalPrompt( hwndDlg );
+ }
+ }
+
+ return 0;
+}
+
+BOOL CALLBACK browseEnumProc( HWND hwnd, LPARAM lParam )
+{
+ wchar_t cl[ 32 ] = { 0 };
+ GetClassNameW( hwnd, cl, ARRAYSIZE( cl ) );
+ if ( !lstrcmpiW( cl, WC_TREEVIEW ) )
+ {
+ PostMessage( hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection( hwnd ) );
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI _bcp( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData )
+{
+ switch ( uMsg )
+ {
+ case BFFM_INITIALIZED:
+ {
+ SetWindowText( hwnd, WASABI_API_LNGSTRINGW( IDS_IMPORT_PLAYLIST_FROM_FOLDER ) );
+ SendMessageW( hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)WASABI_API_APP->path_getWorkingPath() );
+
+ HWND h2 = FindWindowEx( hwnd, NULL, NULL, L"__foo" );
+ if ( h2 )
+ ShowWindow( h2, SW_HIDE );
+
+ HWND h = WASABI_API_CREATEDIALOGW( IDD_BROWSE_PLFLD, hwnd, browseCheckBoxProc );
+ SetWindowPos( h, 0, 4, 4, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
+ ShowWindow( h, SW_SHOWNA );
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows( hwnd, browseEnumProc, 0 );
+ }
+ }
+
+ return 0;
+}
+
+void Playlist_importFromFolders( HWND dlgparent )
+{
+ BROWSEINFOW bi = { 0 };
+ wchar_t name[ MAX_PATH ] = { 0 };
+
+ bi.hwndOwner = dlgparent;
+ bi.pszDisplayName = name;
+ bi.lpszTitle = L"__foo";
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = _bcp;
+
+ ITEMIDLIST *idlist = SHBrowseForFolderW( &bi );
+ if ( idlist )
+ {
+ wchar_t path[ MAX_PATH ] = { 0 };
+ SHGetPathFromIDListW( idlist, path );
+ WASABI_API_APP->path_setWorkingPath( path );
+
+ Shell_Free( idlist );
+ MLNavCtrl_BeginUpdate( plugin.hwndLibraryParent, 0 );
+ importPlaylistFolder( path, g_config->ReadInt( L"importplfoldrecurs", 1 ) );
+
+ AGAVE_API_PLAYLISTS->Flush(); // REVIEW: save immediately? or only at the end?
+ MLNavCtrl_EndUpdate( plugin.hwndLibraryParent );
+ }
+}
+
+static void playlists_Save( HWND parent )
+{
+ for ( size_t i = 0; i < playlistGUIDs.size(); i++ )
+ {
+ if ( !m_playlistslist.GetSelected( i ) )
+ continue;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ i ] );
+
+ wchar_t str[ MAX_PATH ] = { 0 };
+ if ( PathIsFileSpecW( info.GetFilename() ) )
+ PathCombineW( str, g_path, info.GetFilename() );
+ else
+ lstrcpynW( str, info.GetFilename(), MAX_PATH );
+
+ Playlist_export( parent, info.GetName(), str );
+ }
+}
+
+void playlists_Import( HWND hwndDlg, LPARAM lParam )
+{
+ RECT r;
+ HMENU menu = GetSubMenu( g_context_menus, 2 );
+
+ GetWindowRect( (HWND)lParam, &r );
+
+ int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RETURNCMD, r.left, r.top, hwndDlg, NULL );
+ switch ( x )
+ {
+ case IDC_IMPORT_PLAYLIST_FROM_FILE:
+ Playlist_importFromFile( hwndDlg );
+ RefreshPlaylistsList();
+ break;
+ case IDC_IMPORT_WINAMP_PLAYLIST:
+ Playlist_importFromWinamp();
+ RefreshPlaylistsList();
+ break;
+ case ID_PLAYLISTSIMPORT_IMPORTPLAYLISTSFROMFOLDER:
+ Playlist_importFromFolders( hwndDlg );
+ RefreshPlaylistsList();
+ break;
+ }
+}
+
+void playlists_Add( HWND parent, bool callback )
+{
+ WASABI_API_DIALOGBOXPARAMW( ( playlists_CloudAvailable() ? IDD_ADD_CLOUD_PLAYLIST : IDD_ADD_PLAYLIST ), parent, AddPlaylistDialogProc, callback );
+}
+
+void DeletePlaylist( GUID _guid, HWND parent, bool confirm )
+{
+ wchar_t titleStr[ 32 ] = { 0 };
+ if ( confirm && MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_CONFIRM_DELETION ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRMATION, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) != IDYES )
+ {
+ SetFocus( parent );
+
+ return;
+ }
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ PlaylistInfo info( _guid );
+
+ wchar_t gs[ MAX_PATH + 1 ] = { 0 }, gs2[ MAX_PATH ] = { 0 };
+ if ( PathIsFileSpecW( info.GetFilename() ) )
+ PathCombineW( gs, g_path, info.GetFilename() );
+ else
+ lstrcpynW( gs, info.GetFilename(), MAX_PATH );
+
+ wchar_t l_node[ MAX_PATH ];
+ wchar_t l_dir[ MAX_PATH ];
+ wchar_t l_fname[ MAX_PATH ];
+ wchar_t l_ext[ MAX_PATH ];
+
+ lstrcpynW( gs2, gs, MAX_PATH );
+ _wsplitpath( gs2, l_node, l_dir, l_fname, l_ext );
+ _wmakepath( gs2, l_node, l_dir, L"", L"" );
+
+ AGAVE_API_PLAYLISTS->RemovePlaylist( info.GetIndex() );
+ // changed in 5.58 to resolve the issue reported at
+ // http://forums.winamp.com/showthread.php?l_plugin_message=2652001#post2652001
+ // delete the file after the removal and not before which
+ // fixes issues if removing the currently viewed playlist
+ //DeleteFileW(gs);
+
+ // changed in 5.64 to use SHFileOperation(..) instead of DeleteFile(..)
+ // so we're able to recover external playlists incase people messup...
+ SHFILEOPSTRUCTW fileOp = { 0 };
+ fileOp.hwnd = parent;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = gs;
+ fileOp.fFlags = ( lstrcmpi( g_path, gs2 ) ? FOF_ALLOWUNDO : 0 ) | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_SIMPLEPROGRESS | FOF_NORECURSION | FOF_NOERRORUI | FOF_SILENT;
+
+ SHFileOperationW( &fileOp );
+ SetFocus( parent );
+}
+
+static void playlists_Delete( HWND parent )
+{
+ if ( !m_playlistslist.GetSelectedCount() || m_playlistslist.GetSelectionMark() == -1 )
+ return;
+
+ wchar_t titleStr[ 32 ] = { 0 };
+ if ( MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_CONFIRM_DELETION ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRMATION, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) != IDYES )
+ return;
+
+ MLNavCtrl_BeginUpdate( plugin.hwndLibraryParent, 0 );
+ for ( int i = playlistGUIDs.size() - 1; i >= 0; i-- )
+ {
+ if ( !m_playlistslist.GetSelected( i ) )
+ continue;
+
+ DeletePlaylist( playlistGUIDs[ i ], parent, false );
+ }
+
+ AGAVE_API_PLAYLISTS->Flush(); // REVIEW: save immediately? or only at the end?
+ MLNavCtrl_EndUpdate( plugin.hwndLibraryParent );
+}
+
+static void playlists_Play( int enqueue )
+{
+ int deleted = 0;
+ for ( size_t i = 0; i < playlistGUIDs.size(); i++ )
+ {
+ if ( !m_playlistslist.GetSelected( i ) )
+ continue;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ i ] );
+
+ wchar_t str[ MAX_PATH ] = { 0 };
+ const wchar_t *fn;
+
+ if ( PathIsFileSpecW( info.GetFilename() ) )
+ {
+ PathCombineW( str, g_path, info.GetFilename() );
+ fn = str;
+ }
+ else
+ {
+ fn = info.GetFilename();
+ }
+
+ if ( !enqueue && !deleted )
+ {
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE );
+ deleted = 1;
+ }
+
+ enqueueFileWithMetaStructW s = { 0 };
+ s.filename = fn;
+ s.ext = NULL;
+ s.length = -1;
+
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW );
+ }
+
+ if ( !enqueue )
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY );
+}
+
+static void playlists_ManageButtons( HWND hwndDlg )
+{
+ int has_selection = m_playlistslist.GetSelectedCount();
+
+ const int buttonids[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_VIEWLIST, IDC_SAVE };
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ HWND controlHWND = GetDlgItem( hwndDlg, buttonids[ i ] );
+ EnableWindow( controlHWND, has_selection );
+ }
+}
+
+static void playlists_ViewList()
+{
+ int t = m_playlistslist.GetSelectionMark();
+ if ( t >= 0 )
+ {
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ t ] );
+ //if ( info.treeId == 0 ) // not created yet
+ //{
+ // // TODO: make a treeid for it
+ //}
+ mediaLibrary.SelectTreeItem( info.treeId );
+ }
+}
+
+static void playlists_Paint( HWND hwndDlg )
+{
+ int tab[] = { IDC_PLAYLIST_LIST | DCW_SUNKENBORDER, };
+ dialogSkinner.Draw( hwndDlg, tab, 1 );
+}
+
+LRESULT playlists_cloud_listview( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_NOTIFY )
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ switch ( l->code )
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = { 0 };
+ GetCursorPos( &lvh.pt );
+ ScreenToClient( hwnd, &lvh.pt );
+ ListView_SubItemHitTest( hwnd, &lvh );
+
+ if ( cloud_avail && lvh.iItem != -1 && lvh.iSubItem == 1 )
+ {
+ LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
+ RECT r = { 0 };
+ if ( lvh.iSubItem )
+ ListView_GetSubItemRect( hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r );
+ else
+ {
+ ListView_GetItemRect( hwnd, lvh.iItem, &r, LVIR_BOUNDS );
+ r.right = r.left + ListView_GetColumnWidth( hwnd, 1 );
+ }
+
+ MapWindowPoints( hwnd, HWND_DESKTOP, (LPPOINT)&r, 2 );
+ SetWindowPos( tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE );
+
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = { 0 };
+ GetCursorPos( &lvh.pt );
+ ScreenToClient( hwnd, &lvh.pt );
+ ListView_SubItemHitTest( hwnd, &lvh );
+
+ static wchar_t tt_buf1[ 256 ] = { L"" };
+ if ( cloud_avail && lvh.iItem != -1 && lvh.iSubItem == 1 )
+ {
+ LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
+
+ if ( last_item1 == lvh.iItem )
+ {
+ lpnmtdi->lpszText = tt_buf1;
+
+ return 0;
+ }
+
+ if ( lvh.iItem < 0 || lvh.iItem >= (int)playlistGUIDs.size() )
+ return 0;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ lvh.iItem ] );
+ if ( info.Valid() )
+ {
+ WASABI_API_LNGSTRINGW_BUF( ( !info.GetCloud() ? IDS_UPLOAD_TO_CLOUD : IDS_AVAILABLE_IN_CLOUD ), tt_buf1, ARRAYSIZE( tt_buf1 ) );
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_UPLOAD_TO_CLOUD, tt_buf1, ARRAYSIZE( tt_buf1 ) );
+ }
+
+ last_item1 = lvh.iItem;
+ lpnmtdi->lpszText = tt_buf1;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW( (WNDPROC)GetPropW( hwnd, L"cloud_list_proc" ), hwnd, uMsg, wParam, lParam );
+ }
+
+ return 0;
+ }
+ }
+
+ return CallWindowProcW( (WNDPROC)GetPropW( hwnd, L"cloud_list_proc" ), hwnd, uMsg, wParam, lParam );
+}
+
+static void playlists_InitDialog( HWND hwndDlg )
+{
+ HACCEL accel = WASABI_API_LOADACCELERATORSW( IDR_VIEW_PLS_ACCELERATORS );
+ if ( accel )
+ WASABI_API_APP->app_addAccelerators( hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD );
+
+ if ( !view.play )
+ SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view );
+
+ opened = true;
+ loaded = false;
+
+ cloud_avail = playlists_CloudAvailable();
+ groupBtn = g_config->ReadInt( L"groupbtn", 1 );
+ enqueuedef = ( g_config->ReadInt( L"enqueuedef", 0 ) == 1 );
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage l_plugin_message = { ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG( IDC_CUSTOM, IDC_ENQUEUE ), (INT_PTR)L"ml_playlists_root" };
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&l_plugin_message );
+ if ( pszTextW && pszTextW[ 0 ] != 0 )
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW( hwndDlg, IDC_CUSTOM, pszTextW );
+ }
+ else
+ customAllowed = FALSE;
+
+ /* skin dialog */
+ MLSKINWINDOW skinWindow = { 0 };
+ skinWindow.skinType = SKINNEDWND_TYPE_DIALOG;
+ skinWindow.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
+ skinWindow.hwndToSkin = hwndDlg;
+
+ MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
+
+ /* skin listview */
+ HWND hwndList = skinWindow.hwndToSkin = GetDlgItem( hwndDlg, IDC_PLAYLIST_LIST );
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.style = SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS | SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+
+ MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
+ MLSkinnedScrollWnd_ShowHorzBar( hwndList, FALSE );
+
+ /* skin buttons */
+ skinWindow.skinType = SKINNEDWND_TYPE_BUTTON;
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | ( groupBtn ? SWBS_SPLITBUTTON : 0 );
+
+ const int buttonidz[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM };
+ for ( size_t i = 0; i != sizeof( buttonidz ) / sizeof( buttonidz[ 0 ] ); i++ )
+ {
+ skinWindow.hwndToSkin = GetDlgItem( hwndDlg, buttonidz[ i ] );
+ if ( IsWindow( skinWindow.hwndToSkin ) )
+ MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
+ }
+
+ /* skin buttons */
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+
+ const int buttonids[] = { IDC_VIEWLIST, IDC_CREATENEWPL, IDC_SAVE };
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ skinWindow.hwndToSkin = GetDlgItem( hwndDlg, buttonids[ i ] );
+ if ( IsWindow( skinWindow.hwndToSkin ) )
+ MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
+ }
+
+ /* skin dropdown buttons */
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWBS_DROPDOWNBUTTON;
+ skinWindow.hwndToSkin = GetDlgItem( hwndDlg, IDC_IMPORT );
+
+ MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
+
+
+ HIMAGELIST imageList = ImageList_Create( 15, 15, ILC_COLOR24, 3, 0 );
+ if ( imageList != NULL )
+ {
+ HIMAGELIST prevList = (HIMAGELIST)SNDMSG( hwndList, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)imageList );
+ if ( prevList != NULL )
+ ImageList_Destroy( prevList );
+ }
+
+ m_playlistslist.setwnd( hwndList );
+ m_playlistslist.AddCol( WASABI_API_LNGSTRINGW( IDS_PLAYLIST_TITLE ), 400 );
+
+ int width = 27;
+ MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width );
+ m_playlistslist.AddCol( L"", ( cloud_avail ? width : 0 ) );
+
+ m_playlistslist.AddCol( WASABI_API_LNGSTRINGW( IDS_ITEMS ), 50 );
+ m_playlistslist.AutoColumnWidth( 3 );
+ m_playlistslist.JustifyColumn( 3, LVCFMT_RIGHT );
+ m_playlistslist.AddCol( WASABI_API_LNGSTRINGW( IDS_TIME ), 75 );
+ m_playlistslist.AutoSizeColumn( 4 );
+ m_playlistslist.JustifyColumn( 4, LVCFMT_RIGHT );
+
+ MLSkinnedHeader_SetCloudColumn( ListView_GetHeader( hwndList ), ( cloud_avail ? 1 : -1 ) );
+
+ if ( !GetPropW( hwndList, L"cloud_list_proc" ) )
+ SetPropW( hwndList, L"cloud_list_proc", (HANDLE)SetWindowLongPtrW( hwndList, GWLP_WNDPROC, (LONG_PTR)playlists_cloud_listview ) );
+
+ playlist_UpdateButtonText( hwndDlg, enqueuedef == 1 );
+ playlists_ManageButtons( hwndDlg );
+ RefreshPlaylistsList();
+
+ SetWindowRedraw( m_playlistslist.getwnd(), FALSE );
+}
+
+void playlists_Destroy( HWND hwndDlg )
+{
+ opened = false;
+ WASABI_API_APP->app_removeAccelerators( hwndDlg );
+ m_playlistslist.setwnd( NULL );
+ playlistGUIDs.clear();
+}
+
+BOOL playlists_GetDisplayInfo( NMLVDISPINFO *lpdi )
+{
+ size_t item = lpdi->item.iItem;
+
+ if ( item < 0 || item >= playlistGUIDs.size() )
+ return 0;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ item ] );
+ if ( lpdi->item.mask & LVIF_TEXT && info.Valid() )
+ {
+ switch ( lpdi->item.iSubItem )
+ {
+ case 0:
+ {
+ // TODO: this is going to be slow, we should investigate caching the title
+ lstrcpyn( lpdi->item.pszText, info.GetName(), lpdi->item.cchTextMax );
+ break;
+ }
+ case 1:
+ {
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d", info.GetCloud() );
+ break;
+ }
+ case 2:
+ {
+ StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d", info.GetSize() );
+ break;
+ }
+ case 3:
+ {
+ wchar_t str[ 64 ] = { 0 };
+ FormatLength( str, info.GetLength(), 64 );
+ lstrcpyn( lpdi->item.pszText, str, lpdi->item.cchTextMax );
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+void Playlists_RenameSelected( HWND hwndDlg )
+{
+ // TOOD: loop through selections
+ int s = m_playlistslist.GetSelectionMark();
+ if ( s != -1 )
+ RenamePlaylist( playlistGUIDs[ s ], hwndDlg );
+}
+
+BOOL playlists_OnCustomDraw( HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult )
+{
+ static BOOL bDrawFocus;
+ static RECT rcView;
+ static CLOUDCOLUMNPAINT cloudColumnPaint;
+
+ *pResult = CDRF_DODEFAULT;
+
+ switch ( plvcd->nmcd.dwDrawStage )
+ {
+ case CDDS_PREPAINT:
+ *pResult |= CDRF_NOTIFYITEMDRAW;
+ CopyRect( &rcView, &plvcd->nmcd.rc );
+
+ cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ cloudColumnPaint.hdc = plvcd->nmcd.hdc;
+ cloudColumnPaint.prcView = &rcView;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ *pResult |= CDRF_NOTIFYSUBITEMDRAW;
+ bDrawFocus = ( CDIS_FOCUS & plvcd->nmcd.uItemState );
+ if ( bDrawFocus )
+ {
+ plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
+ *pResult |= CDRF_NOTIFYPOSTPAINT;
+ }
+ return TRUE;
+
+ case CDDS_ITEMPOSTPAINT:
+ if ( bDrawFocus )
+ {
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+ SendMessageW( plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc );
+
+ rc.left += 3;
+ DrawFocusRect( plvcd->nmcd.hdc, &rc );
+
+ plvcd->nmcd.uItemState |= CDIS_FOCUS;
+
+ bDrawFocus = FALSE;
+ }
+
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+
+ case( CDDS_SUBITEM | CDDS_ITEMPREPAINT ):
+ // TODO need to have a map between column ids so we do this correctly
+ if ( plvcd->iSubItem == 1 )
+ {
+ if ( 0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right || playlistGUIDs.empty() )
+ break;
+
+ cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ cloudColumnPaint.iSubItem = plvcd->iSubItem;
+
+ int cloud_icon = 0;
+ size_t item = plvcd->nmcd.dwItemSpec;
+ if ( item >= 0 || item < playlistGUIDs.size() )
+ {
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ item ] );
+ if ( info.Valid() )
+ cloud_icon = info.GetCloud();
+ }
+
+ // TODO have this show an appropriate cloud icon for the playlist
+ // currently all we have is cloud or nothing as we'll only
+ // have files locally for this for the moment (need todo!!!)
+ cloudColumnPaint.value = cloud_icon;
+ cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
+ cloudColumnPaint.rgbBk = plvcd->clrTextBk;
+ cloudColumnPaint.rgbFg = plvcd->clrText;
+
+ if ( MLCloudColumn_Paint( plugin.hwndLibraryParent, &cloudColumnPaint ) )
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+BOOL playlists_Notify( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ LPNMHDR l = (LPNMHDR)lParam;
+ if ( l->idFrom == IDC_PLAYLIST_LIST )
+ {
+ switch ( l->code )
+ {
+ case LVN_ITEMCHANGED:
+ playlists_ManageButtons( hwndDlg );
+ break;
+ case LVN_BEGINDRAG:
+ root_is_drag_and_dropping = 1; SetCapture( hwndDlg );
+ break;
+ case LVN_GETDISPINFO:
+ return playlists_GetDisplayInfo( (NMLVDISPINFO *)lParam );
+ case NM_DBLCLK:
+ playlists_Play( enqueuedef == 1 );
+ break;
+ case NM_CLICK:
+ {
+ LPNMITEMACTIVATE pnmitem = (LPNMITEMACTIVATE)lParam;
+ if ( cloud_avail && pnmitem->iItem != -1 && pnmitem->iSubItem == 1 )
+ {
+ RECT itemRect = { 0 };
+ if ( pnmitem->iSubItem )
+ ListView_GetSubItemRect( pnmitem->hdr.hwndFrom, pnmitem->iItem, pnmitem->iSubItem, LVIR_BOUNDS, &itemRect );
+ else
+ {
+ ListView_GetItemRect( pnmitem->hdr.hwndFrom, pnmitem->iItem, &itemRect, LVIR_BOUNDS );
+ itemRect.right = itemRect.left + ListView_GetColumnWidth( pnmitem->hdr.hwndFrom, pnmitem->iSubItem );
+ }
+
+ MapWindowPoints( pnmitem->hdr.hwndFrom, HWND_DESKTOP, (POINT *)&itemRect, 2 );
+
+ size_t item = pnmitem->iItem;
+ if ( item < 0 || item >= playlistGUIDs.size() )
+ return 0;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ item ] );
+
+ HMENU cloud_menu = (HMENU)0x666;
+ ReferenceCountedNXString uid;
+ NXStringCreateWithFormatting( &uid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ (int)info.playlist_guid.Data1, (int)info.playlist_guid.Data2,
+ (int)info.playlist_guid.Data3, (int)info.playlist_guid.Data4[ 0 ],
+ (int)info.playlist_guid.Data4[ 1 ], (int)info.playlist_guid.Data4[ 2 ],
+ (int)info.playlist_guid.Data4[ 3 ], (int)info.playlist_guid.Data4[ 4 ],
+ (int)info.playlist_guid.Data4[ 5 ], (int)info.playlist_guid.Data4[ 6 ],
+ (int)info.playlist_guid.Data4[ 7 ] );
+
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)uid->string, (intptr_t)&cloud_menu );
+ if ( cloud_menu )
+ {
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, cloud_menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY, itemRect.right, itemRect.top, pnmitem->hdr.hwndFrom, NULL );
+ if ( r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER ) // deals with cloud specific menus
+ {
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+ int mode = -(int)info.GetIndex();
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode );
+ if ( mode > 0 )
+ {
+ info.SetCloud( ( mode == 1 ? 1 : 0 ) );
+ AGAVE_API_PLAYLISTS->Flush();
+
+ UpdatePlaylists();
+
+ last_item1 = -1;
+ }
+ }
+
+ DestroyMenu( cloud_menu );
+ }
+ }
+ }
+ break;
+ case LVN_KEYDOWN:
+ {
+ LPNMLVKEYDOWN pnkd = (LPNMLVKEYDOWN)lParam;
+ switch ( pnkd->wVKey )
+ {
+ case 0x2E: //Delete
+ playlists_Delete( hwndDlg );
+ break;
+ case VK_F2:
+ Playlists_RenameSelected( hwndDlg );
+ SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)l->hwndFrom, (LPARAM)TRUE );
+ break;
+ case 'A':
+ if ( !( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) && ( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) )
+ m_playlistslist.SelectAll();
+ break;
+ }
+ }
+ break;
+
+ case NM_CUSTOMDRAW:
+ {
+ LRESULT result = 0;
+ if ( cloud_avail && playlists_OnCustomDraw( hwndDlg, (NMLVCUSTOMDRAW *)lParam, &result ) )
+ {
+ SetWindowLongPtrW( hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result );
+
+ return 1;
+ }
+ break;
+ }
+ }
+ }
+
+ switch ( l->code )
+ {
+ case HDN_ITEMCHANGING:
+ {
+ LPNMHEADERW phdr = (LPNMHEADERW)lParam;
+ if ( phdr->pitem && ( HDI_WIDTH & phdr->pitem->mask ) && phdr->iItem == 1 )
+ {
+ if ( !cloud_avail )
+ phdr->pitem->cxy = 0;
+ else
+ {
+ INT width = phdr->pitem->cxy;
+ if ( MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width ) )
+ phdr->pitem->cxy = width;
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void playlists_MouseMove( HWND hwndDlg, LPARAM lParam )
+{
+ if ( root_is_drag_and_dropping && GetCapture() == hwndDlg )
+ {
+ POINT p = { GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) };
+ ClientToScreen( hwndDlg, &p );
+
+ mlDropItemStruct m = { 0 };
+ m.type = ML_TYPE_FILENAMES;
+ m.p = p;
+
+ pluginHandleIpcMessage( ML_IPC_HANDLEDRAG, (WPARAM)&m );
+ }
+}
+
+void playlists_LeftButtonUp( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ if ( root_is_drag_and_dropping && GetCapture() == hwndDlg )
+ {
+ ReleaseCapture();
+
+ POINT p = { GET_X_LPARAM( lParam ),GET_Y_LPARAM( lParam ) };
+ ClientToScreen( hwndDlg, &p );
+
+ mlDropItemStruct m = { 0 };
+ m.type = ML_TYPE_FILENAMES;
+ m.p = p;
+
+ pluginHandleIpcMessage( ML_IPC_HANDLEDRAG, (WPARAM)&m );
+
+ if ( m.result > 0 )
+ {
+ //std::vector<char> data;
+ std::string data;
+
+ for ( size_t i = 0; i < playlistGUIDs.size(); i++ )
+ {
+ if ( !m_playlistslist.GetSelected( i ) )
+ continue;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ i ] );
+
+ wchar_t str[ MAX_PATH ] = { 0 };
+ if ( PathIsFileSpecW( info.GetFilename() ) )
+ PathCombineW( str, g_path, info.GetFilename() );
+ else
+ lstrcpynW( str, info.GetFilename(), MAX_PATH );
+
+ AutoChar charStr( str );
+ // HAKAN: why (len + 1) ?
+ //data.append(charStr, lstrlenA( charStr + 1));
+ data.append(charStr, lstrlenA(charStr));
+ }
+ // HAKAN: No need to add trailing zero
+ //data.push_back( 0 );
+
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void *)data.c_str();
+
+ pluginHandleIpcMessage( ML_IPC_HANDLEDROP, (WPARAM)&m );
+ RefreshPlaylistsList();
+ }
+
+ root_is_drag_and_dropping = 0;
+ }
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+static void playlists_PlayEnqueue( HWND hwndDlg, HWND from, UINT idFrom )
+{
+ HMENU listMenu = GetSubMenu( g_context_menus3, 0 );
+ int count = GetMenuItemCount( listMenu );
+ if ( count > 2 )
+ {
+ for ( int i = 2; i < count; i++ )
+ DeleteMenu( listMenu, 2, MF_BYPOSITION );
+ }
+
+ UINT menuStatus;
+ if ( m_playlistslist.GetNextSelected( -1 ) == -1 )
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_INVERT_SELECTION, menuStatus );
+
+ if ( m_playlistslist.GetCount() > 0 )
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ else
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+
+ EnableMenuItem( listMenu, IDC_PLAYLIST_SELECT_ALL, menuStatus );
+ playlist_ButtonPopupMenu( hwndDlg, idFrom, listMenu, BPM_WM_COMMAND );
+}
+
+void playlists_Command( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_VIEWLIST:
+ playlists_ViewList();
+ break;
+ case IDC_IMPORT:
+ playlists_Import( hwndDlg, lParam );
+ break;
+ case IDC_CREATENEWPL:
+ case IDC_NEWPLAYLIST:
+ playlists_Add( hwndDlg );
+ break;
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ {
+ if ( HIWORD( wParam ) == MLBN_DROPDOWN )
+ {
+ playlists_PlayEnqueue( hwndDlg, (HWND)lParam, LOWORD( wParam ) );
+ }
+ else
+ {
+ int action;
+ if ( LOWORD( wParam ) == IDC_PLAY )
+ action = ( HIWORD( wParam ) == 1 ) ? enqueuedef == 1 : 0;
+ else if ( LOWORD( wParam ) == IDC_ENQUEUE )
+ action = ( HIWORD( wParam ) == 1 ) ? ( enqueuedef != 1 ) : 1;
+ else
+ // so custom can work with the menu item part
+ break;
+
+ playlists_Play( action );
+ }
+ break;
+ }
+ case IDC_SAVE:
+ playlists_Save( hwndDlg );
+ break;
+ case IDC_DELETE:
+ playlists_Delete( hwndDlg );
+ break;
+ case IDC_RENAME:
+ Playlists_RenameSelected( hwndDlg );
+ break;
+ }
+}
+
+void playlists_DropFiles( HDROP hDrop )
+{
+ wchar_t l_playlist_filename[ 2048 ] = { 0 };
+ int y = DragQueryFileW( hDrop, 0xffffffff, l_playlist_filename, 2048 );
+
+ for ( int x = 0; x < y; x++ )
+ {
+ Playlist currentPlaylist2;
+ DragQueryFileW( hDrop, x, l_playlist_filename, 2048 );
+ // make sure that we only add valid playlists and not normal files
+ if ( AGAVE_API_PLAYLISTMANAGER->CanLoad( l_playlist_filename ) )
+ {
+ ImportPlaylist( l_playlist_filename );
+ }
+ }
+}
+
+void playlists_Sort( size_t sort_type )
+{
+ int cur_sel = mediaLibrary.GetSelectedTreeItem();
+ GUID cur_guid = tree_to_guid_map[ cur_sel ];
+
+ // keep the old tree ids before sorting so we can then re-map
+ // without having to remove and re-add all of the tree items
+ std::vector<int> tree_ids;
+
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ size_t count = AGAVE_API_PLAYLISTS->GetCount();
+ for ( size_t i = 0; i != count; i++ )
+ {
+ PlaylistInfo info( i );
+ if ( info.Valid() )
+ tree_ids.push_back( info.treeId );
+ }
+
+ if ( AGAVE_API_PLAYLISTS->Sort( sort_type ) )
+ {
+ for ( size_t i = 0; i != count; i++ )
+ {
+ PlaylistInfo info( i );
+ UpdateTree( info, tree_ids[ i ] );
+ }
+
+ for ( size_t i = 0; i != count; i++ )
+ {
+ PlaylistInfo info( i );
+ if ( cur_guid == info.playlist_guid )
+ {
+ mediaLibrary.SelectTreeItem( info.treeId );
+ }
+ }
+
+ RefreshPlaylistsList();
+ }
+}
+
+void playlists_ContextMenu( HWND hwndDlg, HWND from, int x, int y )
+{
+ if ( from != m_playlistslist.getwnd() )
+ return;
+
+ POINT pt = { x,y };
+
+ if ( x == -1 || y == -1 ) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT channelRect = { 0 };
+ int selected = m_playlistslist.GetNextSelected();
+ if ( selected != -1 ) // if something is selected we'll drop the menu from there
+ {
+ m_playlistslist.GetItemRect( selected, &channelRect );
+ ClientToScreen( hwndDlg, (POINT *)&channelRect );
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect( hwndDlg, &channelRect );
+
+ HWND hHeader = (HWND)SNDMSG( from, LVM_GETHEADER, 0, 0L );
+ RECT headerRect;
+ if ( ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) && GetWindowRect( hHeader, &headerRect ) )
+ {
+ channelRect.top += ( headerRect.bottom - headerRect.top );
+ }
+ }
+
+ x = channelRect.left;
+ y = channelRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG( from, LVM_GETHEADER, 0, 0L );
+ RECT headerRect;
+ if ( 0 == ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) || FALSE == GetWindowRect( hHeader, &headerRect ) )
+ SetRectEmpty( &headerRect );
+
+ if ( FALSE != PtInRect( &headerRect, pt ) )
+ return;
+
+ HMENU menu = GetSubMenu( g_context_menus, 0 );
+ sendTo.AddHere( hwndDlg, GetSubMenu( menu, 2 ), ML_TYPE_FILENAMES, 1, ( ML_TYPE_PLAYLIST + 1 ) );
+
+ HMENU cloud_hmenu = (HMENU)0x666;
+ size_t index = 0, i = 0;
+ if ( playlists_CloudAvailable() )
+ {
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+ for ( ; i < playlistGUIDs.size(); i++ )
+ {
+ if ( !m_playlistslist.GetSelected( i ) )
+ continue;
+
+ PlaylistInfo info( playlistGUIDs[ i ] );
+
+ ReferenceCountedNXString uid;
+ NXStringCreateWithFormatting( &uid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ (int)info.playlist_guid.Data1, (int)info.playlist_guid.Data2,
+ (int)info.playlist_guid.Data3, (int)info.playlist_guid.Data4[ 0 ],
+ (int)info.playlist_guid.Data4[ 1 ], (int)info.playlist_guid.Data4[ 2 ],
+ (int)info.playlist_guid.Data4[ 3 ], (int)info.playlist_guid.Data4[ 4 ],
+ (int)info.playlist_guid.Data4[ 5 ], (int)info.playlist_guid.Data4[ 6 ],
+ (int)info.playlist_guid.Data4[ 7 ] );
+
+ index = info.GetIndex();
+
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)uid->string, (intptr_t)&cloud_hmenu );
+
+ if ( cloud_hmenu && cloud_hmenu != (HMENU)0x666 )
+ {
+ MENUITEMINFOW m = { sizeof( m ), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0 };
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ InsertMenuItemW( menu, 3, TRUE, &m );
+
+ wchar_t a[ 100 ] = { 0 };
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF( IDS_CLOUD_SOURCES, a, 100 );
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_hmenu;
+
+ InsertMenuItemW( menu, 4, TRUE, &m );
+ }
+ break;
+ }
+ }
+
+ UpdateMenuItems( hwndDlg, menu );
+
+ UINT menuStatus;
+ if ( m_playlistslist.GetNextSelected( -1 ) == -1 )
+ {
+ menuStatus = MF_BYCOMMAND | MF_GRAYED;
+ EnableMenuItem( menu, 2, MF_BYPOSITION | MF_GRAYED );
+ EnableMenuItem( menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND | MF_GRAYED );
+ }
+ else
+ {
+ menuStatus = MF_BYCOMMAND | MF_ENABLED;
+ EnableMenuItem( menu, 2, MF_BYPOSITION | MF_ENABLED );
+ EnableMenuItem( menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND | MF_ENABLED );
+ }
+
+ EnableMenuItem( menu, IDC_PLAY, menuStatus );
+ EnableMenuItem( menu, IDC_ENQUEUE, menuStatus );
+ EnableMenuItem( menu, IDC_DELETE, menuStatus );
+ EnableMenuItem( menu, ID_QUERYMENU_ADDNEWQUERY, menuStatus );
+ EnableMenuItem( menu, IDC_RENAME, menuStatus );
+ EnableMenuItem( menu, IDC_ENQUEUE, menuStatus );
+ EnableMenuItem( menu, IDC_VIEWLIST, menuStatus );
+
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL );
+ switch ( r )
+ {
+ case IDC_VIEWLIST:
+ playlists_ViewList();
+ break;
+ case IDC_NEWPLAYLIST:
+ playlists_Add( hwndDlg );
+ break;
+ case IDC_PLAY:
+ playlists_Play( 0 );
+ break;
+ case IDC_ENQUEUE:
+ playlists_Play( 1 );
+ break;
+ case IDC_DELETE:
+ playlists_Delete( hwndDlg );
+ SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
+ break;
+ case ID_QUERYMENU_ADDNEWQUERY:
+ playlists_Add( hwndDlg );
+ SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
+ break;
+ case IDC_RENAME:
+ Playlists_RenameSelected( hwndDlg );
+ SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
+ break;
+ default:
+ if ( sendTo.WasClicked( r ) )
+ {
+ bool playlist_type_worked = true;
+ int numPlaylists = m_playlistslist.GetSelectedCount();
+ if ( !numPlaylists )
+ break;
+
+ mlPlaylist **playlists = new mlPlaylist * [ numPlaylists + 1 ];
+ playlists[ numPlaylists ] = 0; // null terminate
+
+ // TODO: m_playlistslist.GetNextSelected()
+ for ( int i = 0, pl = 0; i < m_playlistslist.GetCount(); i++ )
+ {
+ if ( !m_playlistslist.GetSelected( i ) )
+ continue;
+
+ playlists[ pl ] = new mlPlaylist;
+ memset( playlists[ pl ], 0, sizeof( mlPlaylist ) );
+
+ { // scope for lock
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ PlaylistInfo info( playlistGUIDs[ i ] );
+ playlists[ pl ]->filename = _wcsdup( info.GetFilename() );
+ playlists[ pl ]->length = info.GetLength();
+ playlists[ pl ]->numItems = info.GetSize();
+ playlists[ pl ]->title = _wcsdup( info.GetName() );
+ }
+
+ pl++;
+ }
+
+ if ( sendTo.SendPlaylists( playlists ) != 1 )
+ {
+ for ( int i = 0; i < numPlaylists; i++ )
+ {
+ if ( sendTo.SendPlaylist( playlists[ i ] ) != 1 )
+ {
+ playlist_type_worked = false;
+ break;
+ }
+ }
+ }
+
+ for ( int i = 0; i < numPlaylists; i++ )
+ {
+ free( (void *)playlists[ i ]->filename );
+ free( (void *)playlists[ i ]->title );
+
+ delete playlists[ i ];
+ }
+
+ delete[] playlists;
+
+
+ if ( !playlist_type_worked )
+ {
+ //std::vector<wchar_t> data;
+ std::wstring data;
+
+ { // scope for lock
+ AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
+
+ for ( size_t ipl = 0; ipl < playlistGUIDs.size(); ipl++ )
+ {
+ if ( !m_playlistslist.GetSelected( ipl ) )
+ continue;
+ PlaylistInfo info( playlistGUIDs[ ipl ] );
+
+ wchar_t str[ MAX_PATH ] = { 0 };
+
+ if ( PathIsFileSpecW( info.GetFilename() ) )
+ PathCombineW( str, g_path, info.GetFilename() );
+ else
+ lstrcpynW( str, info.GetFilename(), MAX_PATH );
+
+ // HAKAN: why (len + 1) ?
+ //data.append( str, lstrlen(str) + 1);
+ data.append(str, lstrlen(str));
+ }
+ }
+
+ // HAKAN: No need to add trailing zero
+ //data.push_back( 0 );
+ //
+ // build my data.
+ sendTo.SendFilenames( data.c_str() );
+ }
+ }
+ else
+ {
+ if ( r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER ) // deals with cloud specific menus
+ {
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+ int mode = -(int)index;
+ WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode );
+ if ( mode > 0 )
+ {
+ PlaylistInfo info( playlistGUIDs[ i ] );
+ info.SetCloud( ( mode == 1 ? 1 : 0 ) );
+
+ AGAVE_API_PLAYLISTS->Flush();
+
+ UpdatePlaylists();
+
+ last_item1 = -1;
+ }
+ }
+ }
+ break;
+ }
+
+ sendTo.Cleanup();
+
+ if ( cloud_hmenu && cloud_hmenu != (HMENU)0x666 )
+ {
+ DeleteMenu( menu, CLOUD_SOURCE_MENUS - 1, MF_BYCOMMAND );
+ DeleteMenu( menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND );
+ DestroyMenu( cloud_hmenu );
+ }
+}
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0;
+static int offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows( HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE )
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_VIEWLIST, IDC_CREATENEWPL, IDC_IMPORT, IDC_SAVE,
+ GROUP_MAIN, IDC_PLAYLIST_LIST
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[ sizeof( controls ) / sizeof( controls[ 0 ] ) ], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect( hwnd, &rc );
+ if ( rc.right == rc.left || rc.bottom == rc.top )
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect( &rg, rc.left, rc.top, rc.right, rc.top );
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect( hwnd, NULL, TRUE );
+
+ for ( index = 0; index < sizeof( controls ) / sizeof( *controls ); index++ )
+ {
+ if ( controls[ index ] >= GROUP_MIN && controls[ index ] <= GROUP_MAX ) // group id
+ {
+ skipgroup = FALSE;
+ switch ( controls[ index ] )
+ {
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[ 128 ] = { 0 };
+ GetDlgItemTextW( hwnd, IDC_PLAY, buffer, ARRAYSIZE( buffer ) );
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( GetDlgItem( hwnd, IDC_PLAY ), buffer );
+
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ),
+ rc.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ),
+ rc.right, rc.bottom );
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY( 3 );
+ break;
+ }
+ case GROUP_MAIN:
+ SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ), rc.top, rc.right, rc.bottom );
+ break;
+ }
+ continue;
+ }
+
+ if ( skipgroup )
+ continue;
+
+ pl->id = controls[ index ];
+ pl->hwnd = GetDlgItem( hwnd, pl->id );
+ if ( !pl->hwnd )
+ continue;
+
+ GetWindowRect( pl->hwnd, &ri );
+ MapWindowPoints( HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2 );
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch ( pl->id )
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ case IDC_VIEWLIST:
+ case IDC_CREATENEWPL:
+ case IDC_IMPORT:
+ case IDC_SAVE:
+ if ( IDC_CUSTOM != pl->id || customAllowed )
+ {
+ if ( groupBtn && ( pl->id == IDC_PLAY ) && ( enqueuedef == 1 ) )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && ( pl->id == IDC_ENQUEUE ) && ( enqueuedef != 1 ) )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && ( pl->id == IDC_PLAY || pl->id == IDC_ENQUEUE ) && customAllowed )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowText( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
+ LONG width = LOWORD( idealSize ) + ( pl->id != IDC_IMPORT ? WASABI_API_APP->getScaleX( 6 ) : 0 );
+
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+
+ pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if ( SWP_SHOWWINDOW & pl->flags )
+ rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_PLAYLIST_LIST:
+ pl->flags |= ( rg.top < rg.bottom ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ), rg.right - rg.left + WASABI_API_APP->getScaleX( 1 ), ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
+ break;
+ }
+
+ SETLAYOUTFLAGS( pl, ri );
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+
+ pl++;
+ }
+ else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
+ {
+ ValidateRect( hwnd, &ri );
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if ( pl != layout )
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos( (INT)( pl - layout ) );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ hdwp = DeferWindowPos( hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags );
+
+ if ( hdwp )
+ EndDeferWindowPos( hdwp );
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ switch ( pc->id )
+ {
+ case IDC_PLAYLIST_LIST:
+ PostMessage( hwnd, WM_APP + 100, 0, 0 );
+ break;
+ }
+ }
+
+ if ( fRedraw )
+ {
+ GetUpdateRgn( hwnd, rgn, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow( hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN );
+ }
+
+ if ( g_rgnUpdate )
+ {
+ GetUpdateRgn( hwnd, g_rgnUpdate, FALSE );
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR );
+ }
+ }
+ }
+
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ if ( pc->rgn )
+ DeleteObject( pc->rgn );
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn( hwnd, NULL );
+}
+
+INT_PTR CALLBACK view_playlistsDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ INT_PTR a = dialogSkinner.Handle( hwndDlg, uMsg, wParam, lParam ); if ( a ) return a;
+ switch ( uMsg )
+ {
+ case WM_INITMENUPOPUP:
+ sendTo.InitPopupMenu( wParam );
+ return 0;
+ case WM_INITDIALOG:
+ playlists_InitDialog( hwndDlg );
+ return TRUE;
+ case WM_NOTIFY:
+ return playlists_Notify( hwndDlg, wParam, lParam );
+ case WM_MOUSEMOVE:
+ playlists_MouseMove( hwndDlg, lParam );
+ return 0;
+ case WM_LBUTTONUP:
+ playlists_LeftButtonUp( hwndDlg, wParam, lParam );
+ return 0;
+ case WM_COMMAND:
+ playlists_Command( hwndDlg, wParam, lParam );
+ break;
+ case WM_PAINT:
+ playlists_Paint( hwndDlg );
+ return 0;
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ case WM_DESTROY:
+ playlists_Destroy( hwndDlg );
+ break;
+ case WM_DROPFILES:
+ playlists_DropFiles( (HDROP)wParam );
+ return 0;
+ case WM_CONTEXTMENU:
+ playlists_ContextMenu( hwndDlg, (HWND)wParam, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
+ return 0;
+ case WM_DISPLAYCHANGE:
+ LayoutWindows( hwndDlg, TRUE );
+ return 0;
+ case WM_APP + 104:
+ {
+ playlist_UpdateButtonText( hwndDlg, wParam );
+ LayoutWindows( hwndDlg, TRUE );
+
+ return 0;
+ }
+ case WM_APP + 102:
+ {
+ if ( cloud_avail )
+ {
+ int width = 27;
+ MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width );
+ m_playlistslist.SetColumnWidth( 1, width );
+ MLSkinnedHeader_SetCloudColumn( ListView_GetHeader( m_playlistslist.getwnd() ), 1 );
+ }
+ }
+ case WM_APP + 101:
+ m_playlistslist.RefreshAll(); UpdateWindow( m_playlistslist.getwnd() );
+ case WM_APP + 100:
+ AutoSizePlaylistColumns();
+ if ( !loaded )
+ {
+ loaded = true;
+ SetWindowRedraw( m_playlistslist.getwnd(), TRUE );
+ }
+ return 0;
+ case WM_WINDOWPOSCHANGED:
+ if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE ) & ( (WINDOWPOS *)lParam )->flags ) ||
+ ( SWP_FRAMECHANGED & ( (WINDOWPOS *)lParam )->flags ) )
+ {
+ LayoutWindows( hwndDlg, !( SWP_NOREDRAW & ( (WINDOWPOS *)lParam )->flags ) );
+ }
+ return 0;
+ case WM_USER + 0x200:
+ SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, 1 ); // yes, we support no - redraw resize
+ return TRUE;
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD( wParam );
+ offsetY = (short)HIWORD( wParam );
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+ case WM_ML_CHILDIPC:
+ {
+ if ( lParam == ML_CHILDIPC_DROPITEM && wParam )
+ {
+ mlDropItemStruct *dis = (mlDropItemStruct *)wParam;
+ if ( dis )
+ {
+ switch ( dis->type )
+ {
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_STREAMNAMES:
+ case ML_TYPE_FILENAMESW:
+ case ML_TYPE_STREAMNAMESW:
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_ITEMRECORDLISTW:
+ case ML_TYPE_CDTRACKS:
+ // check we're not dropping back on ourself - not
+ // pretty but it prevents the new playlist prompt
+ if ( root_is_drag_and_dropping )
+ {
+ RECT r;
+ GetWindowRect( hwndDlg, &r );
+ dis->result = ( !PtInRect( &r, dis->p ) ? 1 : -1 );
+ }
+ // otherwise allow through as from external
+ else
+ dis->result = 1;
+ break;
+ default:
+ dis->result = -1;
+ break;
+ }
+ }
+
+ return 0;
+ }
+ }
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_playlists/wa_subclass.cpp b/Src/Plugins/Library/ml_playlists/wa_subclass.cpp
new file mode 100644
index 00000000..bcb1fd90
--- /dev/null
+++ b/Src/Plugins/Library/ml_playlists/wa_subclass.cpp
@@ -0,0 +1,90 @@
+#include "main.h"
+#include "../Winamp/wa_ipc.h"
+#include "replicant/nu/AutoLock.h"
+#include <algorithm>
+
+using namespace Nullsoft::Utility;
+
+static WNDPROC waProc = 0;
+extern HMENU wa_play_menu;
+static HMENU last_playlistsmenu = NULL;
+WORD waMenuID = 0;
+extern int IPC_LIBRARY_PLAYLISTS_REFRESH, IPC_CLOUD_ENABLED;
+
+LRESULT WINAPI WinampProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_COMMAND || msg == WM_SYSCOMMAND)
+ {
+ if (LOWORD(wParam) == WINAMP_MANAGEPLAYLISTS)
+ {
+ mediaLibrary.ShowMediaLibrary();
+ mediaLibrary.SelectTreeItem(playlistsTreeId);
+ return 1;
+ }
+ else if (msg == WM_COMMAND && wParam > 45000 && wParam < 55000)
+ {
+ INT_PTR treeId = wParam - 45000;
+ if (FindTreeItem(treeId))
+ {
+ mediaLibrary.SwitchToPluginView(treeId);
+ }
+ }
+ else if (msg == WM_COMMAND && wParam > 55000 && wParam < 65000)
+ {
+ if (PlayPlaylist(wParam - 55000))
+ return 0;
+ }
+ }
+ else if (msg == WM_INITMENUPOPUP)
+ {
+ HMENU hmenuPopup = (HMENU) wParam;
+ if (hmenuPopup == wa_play_menu)
+ {
+ if (last_playlistsmenu)
+ {
+ RemoveMenu(wa_play_menu, waMenuID, MF_BYCOMMAND);
+ DestroyMenu(last_playlistsmenu);
+ last_playlistsmenu = NULL;
+ }
+ mlGetTreeStruct mgts = { 3001, 55000, -1};
+ last_playlistsmenu = (HMENU)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) &mgts, ML_IPC_GETTREE);
+ if (last_playlistsmenu)
+ {
+ MENUITEMINFOW menuItem = {sizeof(MENUITEMINFOW), MIIM_SUBMENU | MIIM_ID | MIIM_TYPE, MFT_STRING,
+ MFS_ENABLED, waMenuID, last_playlistsmenu, NULL, NULL, NULL,
+ WASABI_API_LNGSTRINGW(IDS_PLAYLIST_FROM_ML), 0};
+ // if there's no playlists then let the user know this
+ if(!AGAVE_API_PLAYLISTS->GetCount())
+ {
+ wchar_t buf[64] = {0};
+ DestroyMenu(last_playlistsmenu);
+ menuItem.hSubMenu = last_playlistsmenu = CreateMenu();
+ InsertMenuW(menuItem.hSubMenu, 0, MF_BYPOSITION | MF_STRING | MF_GRAYED, 0, WASABI_API_LNGSTRINGW_BUF(IDS_NO_PLAYLIST_IN_LIBRARY,buf,64));
+ }
+ InsertMenuItemW(wa_play_menu, GetMenuItemCount(wa_play_menu), TRUE, &menuItem);
+ }
+ }
+ }
+ else if (msg == WM_WA_IPC && lParam == IPC_LIBRARY_PLAYLISTS_REFRESH)
+ {
+ // refresh the status of the tree items e.g. when made
+ // being made into a cloud playlist or remove from it
+ UpdatePlaylists();
+ }
+ else if (msg == WM_WA_IPC && lParam == IPC_CLOUD_ENABLED)
+ {
+ cloud_avail = 1;
+ if (IsWindow(currentView)) PostMessage(currentView, WM_APP + 102, 0, 0);
+ }
+
+ if (waProc)
+ return CallWindowProcW(waProc, hwnd, msg, wParam, lParam);
+ else
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+void Hook(HWND winamp)
+{
+ if (IsWindow(winamp))
+ waProc = (WNDPROC)SetWindowLongPtrW(winamp, GWLP_WNDPROC, (LONG_PTR)WinampProcedure);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/AddPlaylist.cpp b/Src/Plugins/Library/ml_plg/AddPlaylist.cpp
new file mode 100644
index 00000000..23334514
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/AddPlaylist.cpp
@@ -0,0 +1,139 @@
+#include <shlwapi.h>
+#include <strsafe.h> // Include this last
+#include <algorithm>
+
+#include "main.h"
+#include "resource.h"
+
+#include "../../General/gen_ml/config.h"
+#include "../nu/autolock.h"
+#include "../nu/autowide.h"
+//#include "../playlist/api_playlists.h"
+#include "../nu/MediaLibraryInterface.h"
+
+
+
+wchar_t *createPlayListDBFileName( wchar_t *filename ) // filename is ignored but used for temp space, make sure it's 1024+256 chars =)
+{
+ wchar_t g_path[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins\\ml", g_path, MAX_PATH );
+
+ wchar_t *filenameptr;
+ int x = 32;
+ for ( ;;)
+ {
+ GetTempFileNameW( g_path, L"plf", GetTickCount() + x * 5000, filename );
+
+ if ( lstrlenW( filename ) > 4 )
+ {
+ int length = lstrlenW( filename ); // Get length
+ StringCchCopyW( filename + length - 4, length, L".m3u" ); // Add the extension
+ }
+
+ HANDLE h = CreateFileW( filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW, 0, 0 );
+ if ( h != INVALID_HANDLE_VALUE )
+ {
+ filenameptr = filename + lstrlenW( g_path ) + 1;
+ CloseHandle( h );
+ break;
+ }
+ if ( ++x > 4096 )
+ {
+ filenameptr = L"error.m3u";
+ break;
+ }
+ }
+
+ // Cleanup
+
+ return filenameptr; // return pointer to just the base filename
+}
+
+void CreateAndAddPlaylist( const wchar_t *name )
+{
+ wchar_t filename[ MAX_PATH ] = { 0 }, dir[ MAX_PATH ] = { 0 };
+ GetTempPathW( MAX_PATH, dir );
+ GetTempFileNameW( dir, L"ml_playlist", 0, filename );
+ StringCchPrintfW( filename, MAX_PATH, L"%s.m3u8", filename );
+
+ int result = -1;
+
+ // See if we want to include the seed tracks in our playlist
+ if ( useSeed == TRUE )
+ {
+ seedPlaylist.AppendPlaylist( currentPlaylist );
+ result = AGAVE_API_PLAYLISTMGR->Save( filename, &seedPlaylist ); // Save the seed playlist contents since we appended the regular playlist to it
+ }
+ else
+ result = AGAVE_API_PLAYLISTMGR->Save( filename, &currentPlaylist ); // Save the current playlist contents to the file so they will be in ML view
+
+ if ( result == PLAYLISTMANAGER_SUCCESS )
+ {
+ mlAddPlaylist p = { sizeof( p ),name,filename, PL_FLAGS_IMPORT,-1,-1 };
+ SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_PLAYLIST_ADD );
+ DeleteFileW( filename );
+ }
+}
+
+void TimeStamp( wchar_t *buf, int cch )
+{
+ wchar_t time_str[ 32 ] = { 0 }, date_str[ 32 ] = { 0 };
+
+ GetDateFormat( 0, 0, 0, 0, date_str, 32 );
+ GetTimeFormat( 0, 0, 0, 0, time_str, 32 );
+
+ StringCchPrintfW( buf, cch, L"%s - %s", date_str, time_str );
+}
+
+INT_PTR CALLBACK AddPlaylistDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ Playlist *seedPlaylist = (Playlist *)lParam;
+
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ //Add the default name here:
+ wchar_t default_pl_name[ 1024 ] = { 0 }, timestamp_t[ 64 ] = { 0 }, title[ 256 ] = { 0 };
+ TimeStamp( timestamp_t, 64 ); // Get the timestamp
+
+ if ( seedPlaylist->entries[ 0 ] ) // Only grab the seed track if it is actually there
+ seedPlaylist->entries[ 0 ]->GetTitle( title, 256 );
+
+ StringCchPrintf( default_pl_name, 1024, L"%s'%s' @ %s", WASABI_API_LNGSTRINGW( IDS_PL_NAME_PREFIX ), title, timestamp_t );
+ SetDlgItemText( hwndDlg, IDC_EDIT_NAME, default_pl_name );
+
+ PostMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem( hwndDlg, IDC_EDIT_NAME ), TRUE );
+ break;
+ }
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK: // On OK create the playlist
+ {
+ wchar_t name[ 256 ] = { 0 };
+ GetDlgItemText( hwndDlg, IDC_EDIT_NAME, name, 255 );
+ if ( !name[ 0 ] ) // Error if a valid name is not provided
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_ENTER_A_NAME ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_ERROR, titleStr, 32 ), MB_OK );
+ break;
+ }
+
+ CreateAndAddPlaylist( name );
+ AGAVE_API_PLAYLISTS->Flush();
+
+ //lParam = IDOK; // Set the code to ok as a return to the calling function
+ EndDialog( hwndDlg, IDOK );
+ }
+ break;
+ case IDCANCEL:
+ //lParam = IDCANCEL; // Set the code to cancel as a return to the calling function
+ EndDialog( hwndDlg, IDCANCEL );
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/AlbumID.cpp b/Src/Plugins/Library/ml_plg/AlbumID.cpp
new file mode 100644
index 00000000..2fd6d14b
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/AlbumID.cpp
@@ -0,0 +1,45 @@
+#include "playlist.h"
+#include "main.h"
+#include "api__ml_plg.h"
+#include "../../General/gen_ml/ml.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+
+IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid)
+{
+ if (!punk)
+ return 0;
+
+ IConnectionPointContainer *pcpc;
+ IConnectionPoint *pcp = 0;
+
+ HRESULT hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **) & pcpc);
+ if (SUCCEEDED(hr))
+ {
+ pcpc->FindConnectionPoint(riid, &pcp);
+ pcpc->Release();
+ }
+ return pcp;
+}
+
+bool IDScanner::SetupMusicID()
+{
+ if (!SetupPlaylistSDK())
+ return false;
+
+ if (musicID)
+ return true;
+
+ musicID = AGAVE_API_GRACENOTE->GetMusicID();
+ if (musicID)
+ {
+ IConnectionPoint *icp = GetConnectionPoint(musicID, DIID__ICDDBMusicIDManagerEvents);
+ if (icp)
+ {
+ icp->Advise(static_cast<IDispatch *>(this), &m_dwCookie);
+ icp->Release();
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/Src/Plugins/Library/ml_plg/IDScanner.cpp b/Src/Plugins/Library/ml_plg/IDScanner.cpp
new file mode 100644
index 00000000..2345ab06
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/IDScanner.cpp
@@ -0,0 +1,698 @@
+#include "IDScanner.h"
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+#include "api__ml_plg.h"
+#include "playlist.h"
+#include <assert.h>
+#include <atlbase.h>
+#include <strsafe.h> // include this last
+
+//#define DEBUG_CALLBACKS
+
+IDScanner::IDScanner() : systemCallbacks(0)
+{
+ musicID=0;
+ killswitch=0;
+ filesComplete=0;
+ filesTotal=0;
+ state=STATE_IDLE;
+ m_dwCookie=0;
+ syscb_registered=false;
+
+ // Create the stack that will hold our batched up files for step 4 processing
+ //process_items;
+}
+
+IDScanner::~IDScanner()
+{
+ // ToDo: Make sure we clean up the processing stack here if we need to do that
+ //Shutdown();
+}
+
+void IDScanner::Shutdown()
+{
+ if (musicID)
+ {
+ IConnectionPoint *icp = GetConnectionPoint(musicID, DIID__ICDDBMusicIDManagerEvents);
+ if (icp)
+ {
+ icp->Unadvise(m_dwCookie);
+ icp->Release();
+ }
+ musicID->Shutdown();
+ musicID->Release();
+ }
+ musicID=0;
+
+ // Deregister the system callbacks
+ WASABI_API_SYSCB->syscb_deregisterCallback(this);
+}
+
+static HRESULT FillTag(ICddbFileInfo *info, BSTR filename)
+{
+ ICddbID3TagPtr infotag = NULL;
+ infotag.CreateInstance(CLSID_CddbID3Tag);
+ ICddbFileTag2_5Ptr tag2_5 = NULL;
+ infotag->QueryInterface(&tag2_5);
+ itemRecordW *record = AGAVE_API_MLDB->GetFile(filename);
+ if (record && infotag && tag2_5)
+ {
+ wchar_t itemp[64] = {0};
+ if (record->artist)
+ infotag->put_LeadArtist(record->artist);
+
+ if (record->album)
+ infotag->put_Album(record->album);
+
+ if (record->title)
+ infotag->put_Title(record->title);
+
+ if (record->genre)
+ infotag->put_Genre(record->genre);
+
+ if (record->track > 0)
+ infotag->put_TrackPosition(_itow(record->track, itemp, 10));
+
+// TODO: if (record->tracks > 0)
+
+ if (record->year > 0)
+ infotag->put_Year(_itow(record->year, itemp, 10));
+
+ if (record->publisher)
+ infotag->put_Label(record->publisher);
+
+ /*
+ if (GetFileInfo(filename, L"ISRC", meta, 512) && meta[0])
+ infotag->put_ISRC(meta);
+ */
+
+ if (record->disc > 0)
+ infotag->put_PartOfSet(_itow(record->disc, itemp, 10));
+
+ if (record->albumartist)
+ tag2_5->put_DiscArtist(record->albumartist);
+
+ if (record->composer)
+ tag2_5->put_Composer(record->composer);
+
+ if (record->length > 0)
+ tag2_5->put_LengthMS(_itow(record->length*1000, itemp, 10));
+
+ if (record->bpm > 0)
+ infotag->put_BeatsPerMinute(_itow(record->bpm, itemp, 10));
+
+ /*
+ if (GetFileInfo(filename, L"conductor", meta, 512) && meta[0])
+ tag2_5->put_Conductor(meta);
+ */
+ AGAVE_API_MLDB->FreeRecord(record);
+ }
+
+ if (info) info->put_Tag(infotag);
+
+ return S_OK;
+}
+
+void IDScanner::CommitFileInfo(ICddbFileInfo *match)
+{
+ ICddbFileTagPtr tag;
+ match->get_Tag(&tag);
+
+ ICddbDisc2Ptr disc1, disc;
+ match->get_Disc(&disc1);
+
+ ICddbDisc2_5Ptr disc2_5;
+ ICddbTrackPtr track;
+ ICddbTrack2_5Ptr track2;
+ if (disc1)
+ {
+ musicID->GetFullDisc(disc1, &disc);
+ if (disc == 0)
+ disc=disc1;
+ disc->QueryInterface(&disc2_5);
+ disc->GetTrack(1, &track);
+ if (track)
+ track->QueryInterface(&track2);
+ }
+
+ CComBSTR file, tagID, extData;
+ match->get_Filename(&file);
+ tag->get_FileId(&tagID);
+ playlistMgr->FileSetTagID(file, tagID, CDDB_UPDATE_NONE);
+
+ ICddbFileTag2_5Ptr tag2;
+ tag->QueryInterface(&tag2);
+ playlistMgr->FileSetFieldVal(file, gnpl_crit_field_xdev1, L"0"); // mark as done!
+
+ if (tag2) // try tag first
+ tag2->get_ExtDataSerialized(&extData);
+
+ if (!extData && track2 != 0) // WMA files don't get their tag object's extended data set correctly, so fallback to track extended data
+ track2->get_ExtDataSerialized(&extData);
+
+ if (!extData && disc2_5 != 0) // finally, fall back to disc extended data
+ disc2_5->get_ExtDataSerialized(&extData);
+
+ playlistMgr->FileSetExtDataSerialized(file, extData, CDDB_UPDATE_NONE);
+
+ if (tagID)
+ AGAVE_API_MLDB->SetField(file, "GracenoteFileID", tagID);
+ if (extData)
+ AGAVE_API_MLDB->SetField(file, "GracenoteExtData", extData);
+
+ // TODO: if we don't have an artist & album, we might as well grab this out of the tag now
+
+ // TODO: make thread-safe and optional
+ /*
+ updateFileInfo(file, L"GracenoteFileID", tagID);
+ updateFileInfo(file, L"GracenoteExtData", extData);
+ WriteFileInfo(file);
+ */
+}
+
+STDMETHODIMP STDMETHODCALLTYPE IDScanner::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, __uuidof(_ICDDBMusicIDManagerEvents)))
+ *ppvObject = (_ICDDBMusicIDManagerEvents *)this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE IDScanner::AddRef(void)
+{
+ return 1;
+}
+
+ULONG STDMETHODCALLTYPE IDScanner::Release(void)
+{
+ return 0;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case 1: // OnTrackIDStatusUpdate, params: CddbMusicIDStatus Status, BSTR filename, long* Abort
+ {
+ //long *abort = pdispparams->rgvarg[0].plVal;
+ // TODO: is this safe to put here? Or does this make us get partial results
+ }
+ break;
+ case 2: // OnAlbumIDStatusUpdate, params: CddbMusicIDStatus Status, BSTR filename, long current_file, long total_files, long* Abort
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ /*long total_files = pdispparams->rgvarg[1].lVal;
+ long current_file= pdispparams->rgvarg[2].lVal;*/
+ CddbMusicIDStatus status = (CddbMusicIDStatus)pdispparams->rgvarg[4].lVal;
+ BSTR filename = pdispparams->rgvarg[3].bstrVal;
+
+ // TODO: is this safe to put here? Or does this make us get partial results
+ if (killswitch)
+ *abort = 1;
+ }
+ break;
+ case 3: // OnTrackIDComplete, params: LONG match_code, ICddbFileInfo* pInfoIn, ICddbFileInfoList* pListOut
+ break;
+ case 4:
+ break;// OnAlbumIDComplete, params: LONG match_code, ICddbFileInfoList* pListIn, ICddbFileInfoLists* pListsOut
+ case 5:
+ break; // OnGetFingerprint
+ case 6:
+ break;
+ case 7://OnLibraryIDListStarted
+ break;
+ case 8: // OnLibraryIDListComplete
+ {
+
+ long *abort = pdispparams->rgvarg[0].plVal;
+ if (killswitch)
+ *abort = 1;
+ /*long FilesError =pdispparams->rgvarg[1].lVal;
+ long FilesNoMatch=pdispparams->rgvarg[2].lVal;
+ long FilesFuzzy=pdispparams->rgvarg[3].lVal;
+ long FilesExact=pdispparams->rgvarg[4].lVal;*/
+ filesTotal=pdispparams->rgvarg[5].lVal;
+ filesComplete=pdispparams->rgvarg[6].lVal;
+ IDispatch *disp = pdispparams->rgvarg[7].pdispVal;
+ if (disp)
+ {
+ ICddbFileInfoList* matchList=0;
+ disp->QueryInterface(&matchList);
+ if (matchList)
+ {
+ long matchcount;
+ matchList->get_Count(&matchcount);
+ for (int j = 1;j <= matchcount;j++)
+ {
+ ICddbFileInfoPtr match;
+ matchList->GetFileInfo(j, &match);
+ CommitFileInfo(match);
+ }
+
+ matchList->Release();
+ }
+ return S_OK;
+ }
+ else
+ return E_FAIL;
+
+ }
+ break;
+ case 9: //OnLibraryIDComplete
+ break;
+ case 10: // OnGetFingerprintInfo
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ IDispatch *disp = pdispparams->rgvarg[1].pdispVal;
+ BSTR filename = pdispparams->rgvarg[2].bstrVal;
+
+ ICddbFileInfoPtr info;
+ disp->QueryInterface(&info);
+ return AGAVE_API_GRACENOTE->CreateFingerprint(musicID, AGAVE_API_DECODE, info, filename, abort);
+ }
+ break;
+ case 11: // OnGetTagInfo
+ {
+ pdispparams->rgvarg[0].plVal;
+ IDispatch *disp = pdispparams->rgvarg[1].pdispVal;
+ BSTR filename = pdispparams->rgvarg[2].bstrVal;
+
+ ICddbFileInfoPtr info;
+ disp->QueryInterface(&info);
+ return FillTag(info, filename);
+ }
+ break;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+void IDScanner::SetGracenoteData(BSTR filename, BSTR tagID, BSTR extData)
+{
+ bool foundExt=false;
+ if (extData && extData[0])
+ {
+ playlistMgr->FileSetExtDataSerialized(filename, extData, CDDB_UPDATE_NONE);
+ CComBSTR test;
+ playlistMgr->FileGetExtDataSerialized(filename, &test, 0); // benski> 24 Jul 2007 - there is a currently a bug that makes this always E_FAIL
+ if (test)
+ foundExt=true;
+ }
+
+ if (!foundExt) // no Extended Data (or invalid), but we have a Tag ID, we'll ask the playlist SDK to do a quick lookup
+ {
+ playlistMgr->FileSetTagID(filename, tagID, CDDB_UPDATE_EXTENDED);
+
+ // write back to Media Library database
+ CComBSTR extData;
+ playlistMgr->FileGetExtDataSerialized(filename, &extData, 0); // benski> 24 Jul 2007 - there is a currently a bug that makes this always E_FAIL
+ if (extData)
+ AGAVE_API_MLDB->SetField(filename, "GracenoteExtData", extData);
+ }
+ else
+ playlistMgr->FileSetTagID(filename, tagID, CDDB_UPDATE_NONE);
+}
+
+/*
+//void IDScanner::ProcessDatabaseDifferences(Device * dev, C_ItemList * ml0,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML)
+void IDScanner::ProcessDatabaseDifferences(Device * dev, C_ItemList * ml0,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML)
+{
+ C_ItemList device2;
+ C_ItemList *device0=&device2;
+
+ int l = dev->getPlaylistLength(0);
+ for(int i=0; i<l; i++) device0->Add((void*)dev->getPlaylistTrack(0,i));
+
+
+ qsort(ml0->GetAll(),ml0->GetSize(),sizeof(void*),sortfunc_ItemRecords);
+ nu::qsort(device0->GetAll(), device0->GetSize(), sizeof(void*), dev, compareSongs);
+
+ C_ItemList *ml = new C_ItemList;
+ C_ItemList *device = new C_ItemList;
+
+ int i,j;
+
+ {
+ itemRecordW * lastice = NULL;
+ songid_t lastsong = NULL;
+ for(i=0; i<ml0->GetSize(); i++) {
+ itemRecordW * it = (itemRecordW*)ml0->Get(i);
+ if(lastice) if(compareItemRecords(lastice,it)==0) continue;
+ ml->Add(it);
+ lastice = it;
+ }
+ for(i=0; i<device0->GetSize(); i++) {
+ songid_t song = (songid_t)device0->Get(i);
+ if(lastsong) if(compareSongs((void*)&song,(void*)&lastsong, dev)==0) continue;
+ device->Add((void*)song);
+ lastsong = song;
+ }
+ }
+
+ i=0,j=0;
+ int li = device->GetSize();
+ int lj = ml->GetSize();
+ while(i<li && j<lj) {
+ itemRecordW * it = (itemRecordW*)ml->Get(j);
+ songid_t song = (songid_t)device->Get(i);
+
+ int cmp = compareItemRecordAndSongId(it,song, dev);
+ if(cmp == 0) { // song on both
+ if(itemRecordsOnDevice) itemRecordsOnDevice->Add(it);
+ if(songsInML) songsInML->Add((void*)song);
+ i++;
+ j++;
+ }
+ else if(cmp > 0) { //song in ml and not on device
+ if(itemRecordsNotOnDevice) itemRecordsNotOnDevice->Add(it);
+ j++;
+ }
+ else { // song on device but not in ML
+ if(songsNotInML) songsNotInML->Add((void*)song);
+ i++;
+ }
+ }
+ // any leftovers?
+ if(songsNotInML) while(i<li) {
+ songid_t song = (songid_t)device->Get(i++);
+
+ songsNotInML->Add((void*)song);
+ }
+ if(itemRecordsNotOnDevice) while(j<lj) {
+ itemRecordW * it = (itemRecordW *)ml->Get(j++);
+
+ itemRecordsNotOnDevice->Add(it);
+ }
+ delete ml; delete device;
+}
+*/
+
+/*
+2-pass strategy
+Pass 1: Find all tracks with Gracenote Extended Data
+Pass 2: Find File ID & extended data by fingerprint
+*/
+void IDScanner::ScanDatabase()
+{
+ filesComplete=0;
+ filesTotal=0;
+ state=STATE_INITIALIZING;
+ killswitch=0; // reset just in case
+
+ if (SetupPlaylistSDK())
+ {
+ // If this is our first time running then lets register the wasabi system callbacks for adding and removing tracks
+ if (!syscb_registered)
+ {
+ WASABI_API_SYSCB->syscb_registerCallback(this);
+ syscb_registered = true;
+ }
+
+ // Set up the MLDB manager
+ InitializeMLDBManager();
+
+ state=STATE_SYNC;
+ /* Get a list of files in the media library database */
+ itemRecordListW *results = AGAVE_API_MLDB->Query(L"type=0");
+ if (results)
+ {
+ filesTotal=results->Size;
+ for (int i=0;i<results->Size;i++)
+ {
+ if (killswitch)
+ break;
+ wchar_t * filename = results->Items[i].filename;
+ HRESULT hr=playlistMgr->AddEntry(filename); // Add entry to gracenote DB
+ assert(SUCCEEDED(S_OK));
+ if (hr == S_OK)
+ {
+ // Fill in Artist & Album info since we have it in the itemRecordList anyway
+ // TODO: probably want to use SKIP_THE_AND_WHITESPACE here
+ if (results->Items[i].album && results->Items[i].album[0])
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_album_name, results->Items[i].album);
+
+ if (results->Items[i].artist && results->Items[i].artist[0])
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_artist_name, results->Items[i].artist);
+
+ // Populate title information so that we have more complete data.
+ if (results->Items[i].title && results->Items[i].title[0])
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_name, results->Items[i].title);
+
+ wchar_t storage[64] = {0};
+ // Populate the file length in milliseconds
+ if (results->Items[i].length > 0)
+ {
+ _itow(results->Items[i].length,storage, 10);
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_length, storage);
+ }
+
+ // Populate the file size in kilobytes
+ if (results->Items[i].filesize > 0)
+ {
+ _itow(results->Items[i].filesize,storage, 10);
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_size, storage);
+ }
+
+ wchar_t *tagID = getRecordExtendedItem(&results->Items[i], L"GracenoteFileID");
+ if (tagID && tagID[0])
+ {
+ SetGracenoteData(filename, tagID, getRecordExtendedItem(&results->Items[i], L"GracenoteExtData"));
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"0"); // done with this file!
+ }
+ else
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"1"); // move to phase 1
+ }
+ filesComplete=i+1;
+ }
+
+ AGAVE_API_MLDB->FreeRecordList(results);
+ state=STATE_METADATA;
+ filesComplete=0;
+ if (!killswitch)
+ Pass1();
+ filesComplete=0;
+ state=STATE_MUSICID;
+ if (!killswitch)
+ Pass2();
+ state=STATE_DONE;
+ if (!killswitch)
+ AGAVE_API_MLDB->Sync();
+ }
+ // Set the pass 2 flag back so that on next generation we dont try to run it
+ run_pass2_flag = false;
+ }
+ else
+ state=STATE_ERROR;
+}
+
+bool IDScanner::GetStatus(long *pass, long *track, long *tracks)
+{
+ *pass = state;
+ *track = filesComplete;
+ *tracks = filesTotal;
+ return true;
+}
+
+// System callback handlers from WASABI
+
+FOURCC IDScanner::GetEventType()
+{
+ return api_mldb::SYSCALLBACK;
+}
+int IDScanner::notify(int msg, intptr_t param1, intptr_t param2)
+{
+ wchar_t *filename = (wchar_t *)param1;
+
+ switch (msg)
+ {
+ case api_mldb::MLDB_FILE_ADDED:
+ {
+
+ DebugCallbackMessage(param1, L"File Added: '%s'");
+
+ // Call the add/update function that needs to run on our lonesome playlist generator thread
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBFileAddedOnThread, _wcsdup(filename), (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ }
+ break;
+ case api_mldb::MLDB_FILE_REMOVED_PRE:
+ {
+ // We are not concerned with the PRE scenario
+ //DebugCallbackMessage(param1, L"File Removed PRE: '%s'");
+ }
+ break;
+ case api_mldb::MLDB_FILE_REMOVED_POST:
+ {
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBFileRemovedOnThread, _wcsdup(filename), (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ // We will only care about the post scenario since we just need to remove the file entry from gracenote.
+ //DebugCallbackMessage(param1, L"File Removed POST: '%s'");
+ }
+ break;
+ case api_mldb::MLDB_FILE_UPDATED:
+ {
+ // For now we call the add method even on an update
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBFileAddedOnThread, _wcsdup(filename), (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ //DebugCallbackMessage(param1, L"File Updated: '%s'");
+ }
+ break;
+ case api_mldb::MLDB_CLEARED:
+ {
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBClearedOnThread, 0, (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ //DebugCallbackMessage(param1, L"MLDB Cleared");
+ }
+ break;
+ default: return 0;
+ }
+ return 1;
+}
+
+// Outputs a messagebox with a filename to know when callbacks are being triggered
+inline void IDScanner::DebugCallbackMessage(const intptr_t text, const wchar_t *message)
+{
+//#ifdef DEBUG_CALLBACKS
+#if defined(DEBUG) && defined(DEBUG_CALLBACKS)
+ const int size = MAX_PATH + 256;
+ wchar_t *filename = (wchar_t *)text;
+ wchar_t buff[size] = {0};
+
+ //wsprintf(buff, size, message, filename);
+ StringCchPrintf(buff, size, message, filename);
+ MessageBox(0, buff, L"Wasabi Callback Debug", 0);
+#endif
+}
+
+int IDScanner::MLDBFileAddedOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ if (!playlistMgr) return 0;
+
+ // Variables to hold information about the file query
+ wchar_t *filename = (wchar_t *)user_data;
+ IDScanner *scanner = (IDScanner *)id;
+
+ wchar_t buff[1024] = {0};
+ _ltow(scanner->state, buff, 10);
+
+ itemRecordW *result = AGAVE_API_MLDB->GetFile(filename);
+
+ HRESULT hr=playlistMgr->AddEntry(filename); // Add the file entry to the gracenote DB
+
+ assert(SUCCEEDED(S_OK));
+
+ if (hr == S_OK /*&& results->Size == 1*/)
+ {
+ // Fill in Artist & Album info since we have it in the itemRecordList anyway
+ // TODO: probably want to use SKIP_THE_AND_WHITESPACE here
+ if (result->album && result->album[0])
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_album_name, result->album);
+
+ if (result->artist && result->artist[0])
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_artist_name, result->artist);
+
+ // Populate title, file size, and length information so that we have more complete data.
+ if (result->title && result->title[0])
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_name, result->title);
+
+
+ wchar_t storage[64] = {0};
+ // Populate the file length in milliseconds
+ if (result->length > 0)
+ {
+ _itow(result->length,storage, 10);
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_length, storage);
+ }
+
+ // Populate the file size in kilobytes
+ if (result->filesize > 0)
+ {
+ _itow(result->filesize,storage, 10);
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_size, storage);
+ }
+
+ wchar_t *tagID = getRecordExtendedItem(result, L"GracenoteFileID");
+ if (tagID && tagID[0])
+ {
+ scanner->SetGracenoteData(filename, tagID, getRecordExtendedItem(result, L"GracenoteExtData"));
+ // We have everything we need at this point in the gracenote DB
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"0"); // done with this file!
+ }
+ else // Set it to the final scan
+ {
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"2"); // move to phase 2, we can skip phase 1
+ // Add the current file to the step 4 processing stack
+ // TODOD is there a mem leak here??
+ ProcessItem *itemz = new ProcessItem();
+ itemz->filename = filename;
+ //scanner->process_items.push(*itemz); // Add the current item coming in to the queue
+
+ // Set the flag so that we know we will need to rerun step 4 (pass 2) on a playlist regeneration, this only needs to happen if there is an actual change.
+ run_pass2_flag = true;
+ }
+ }
+
+ if (result)
+ AGAVE_API_MLDB->FreeRecord(result);
+
+ // ToDo: We need to do this free when we pop it off of the processing stack later
+ free(filename); // Clean up the user data
+ return 0;
+}
+
+int IDScanner::MLDBFileRemovedOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ wchar_t *filename = (wchar_t *)user_data;
+
+ HRESULT hr = playlistMgr->DeleteFile(filename);
+
+ if (hr == S_OK)
+ return 0;
+ else
+ return 1;
+
+ free(filename); // Clean up the user data
+}
+
+int IDScanner::MLDBClearedOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ return ResetDB(false);
+}
+
+int IDScanner::ProcessStackItems(void)
+{
+ // ToDo: Run through the stack items and process stage 4 on them
+ //this->
+ return 1;
+}
+
+#define CBCLASS IDScanner
+START_DISPATCH;
+CB(SYSCALLBACK_GETEVENTTYPE, GetEventType);
+CB(SYSCALLBACK_NOTIFY, notify);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/IDScanner.h b/Src/Plugins/Library/ml_plg/IDScanner.h
new file mode 100644
index 00000000..951bcab1
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/IDScanner.h
@@ -0,0 +1,97 @@
+#ifndef NULLSOFT_ML_PLG_IDSCANNER_H
+#define NULLSOFT_ML_PLG_IDSCANNER_H
+
+#include "../gracenote/gracenote.h"
+
+#include <api/syscb/callbacks/svccb.h>
+#include <api/syscb/api_syscb.h>
+#include "../ml_local/api_mldb.h"
+
+//#include "../nu/lockfreestack.h"
+
+// Regular declarations
+
+struct ProcessItem
+{
+ wchar_t *filename;
+ ProcessItem *next;
+};
+
+class IDScanner : public _ICDDBMusicIDManagerEvents, public SysCallback
+{
+public:
+ IDScanner();
+ ~IDScanner();
+ void ScanDatabase();
+ bool GetStatus(long *state, long *track, long *tracks);
+ void Kill() { killswitch=1; }
+ void Shutdown();
+
+ // Processing data for step 4
+ //LockFreeStack<ProcessItem> process_items;
+
+ // Thread functions
+ static int MLDBFileAddedOnThread(HANDLE handle, void *user_data, intptr_t id);
+ static int MLDBFileRemovedOnThread(HANDLE handle, void *user_data, intptr_t id);
+ static int MLDBClearedOnThread(HANDLE handle, void *user_data, intptr_t id);
+ static int Pass2OnThread(HANDLE handle, void *user_data, intptr_t id);
+ int ProcessStackItems(void);
+
+ enum
+ {
+ STATE_ERROR = -1,
+ STATE_IDLE = 0,
+ STATE_INITIALIZING=1,
+ STATE_SYNC = 2,
+ STATE_METADATA = 3,
+ STATE_MUSICID = 4,
+ STATE_DONE = 5,
+ };
+private:
+ // *** IUnknown Methods ***
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ // *** Sys callback ***
+ api_syscb *systemCallbacks;
+ FOURCC GetEventType();
+ int notify(int msg, intptr_t param1, intptr_t param2);
+ static void DebugCallbackMessage(const intptr_t text, const wchar_t *message);
+
+
+
+ // *** ***
+ void Pass1();
+ void Pass2();
+
+
+ // *** Helper functions ***
+ bool SetupMusicID();
+ void CommitFileInfo(ICddbFileInfo *match);
+ void SetGracenoteData(BSTR filename, BSTR tagId, BSTR extData); // extData can be NULL
+
+
+ // *** Data ***
+ ICDDBMusicIDManager3 *musicID;
+ volatile int killswitch;
+ volatile long filesComplete, filesTotal;
+ volatile long state;
+ DWORD m_dwCookie;
+ bool syscb_registered;
+
+protected:
+ RECVS_DISPATCH;
+
+
+};
+
+IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp
new file mode 100644
index 00000000..e5ba2531
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp
@@ -0,0 +1,44 @@
+
+#include "PlaylistGeneratorAPI.h"
+#include <api/service/waservicefactory.h>
+
+#include "main.h"
+
+int PlaylistGeneratorAPI::GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList)
+{
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ {
+ MultipleInstancesWarning();
+ return DISPATCH_SUCCESS;
+ }
+
+ AddSeedTracks(selectedSeedRecordList);
+
+ if (SongsSelected())
+ return DISPATCH_SUCCESS;
+
+ return DISPATCH_FAILURE;
+}
+
+int PlaylistGeneratorAPI::AddSeedTracks(const itemRecordListW *recordList)
+{
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+
+ for (int i = 0; i < recordList->Size; i++)
+ {
+ itemRecordW *item = &recordList->Items[i];
+ GetTitleFormattingML(item->filename, item, winamp_title, MAX_TITLE_SIZE);
+
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ }
+
+ return true;
+}
+
+
+
+#define CBCLASS PlaylistGeneratorAPI
+START_DISPATCH;
+CB(API_PLAYLIST_GENERATOR_GENERATEPLAYLIST, GeneratePlaylist)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h
new file mode 100644
index 00000000..c494d9f9
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h
@@ -0,0 +1,14 @@
+#include "api_playlist_generator.h"
+
+class PlaylistGeneratorAPI : public api_playlist_generator
+{
+public:
+ // Exposed API functions
+ int GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList);
+
+ // Helper functions
+ int AddSeedTracks(const itemRecordListW *recordList);
+
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_plg/api__ml_plg.h b/Src/Plugins/Library/ml_plg/api__ml_plg.h
new file mode 100644
index 00000000..3a8bd5a8
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/api__ml_plg.h
@@ -0,0 +1,50 @@
+#ifndef NULLSOFT_ML_PLG_API_H
+#define NULLSOFT_ML_PLG_API_H
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManagerApi;
+#define AGAVE_API_PLAYLISTMGR playlistManagerApi
+
+#include "../Agave/Config/api_config.h"
+//extern api_config *agaveConfigApi;
+//#define AGAVE_API_CONFIG agaveConfigApi
+
+#include "../Winamp/api_decodefile.h"
+extern api_decodefile *decodeApi;
+#define AGAVE_API_DECODE decodeApi
+
+#include "../gracenote/api_gracenote.h"
+extern api_gracenote *gracenoteApi;
+#define AGAVE_API_GRACENOTE gracenoteApi
+
+#include "../Agave/Metadata/api_metadata.h"
+extern api_metadata *metadataApi;
+#define AGAVE_API_METADATA metadataApi
+
+#include "../ml_local/api_mldb.h"
+extern api_mldb *mldbApi;
+#define AGAVE_API_MLDB mldbApi
+
+#include <api/syscb/api_syscb.h>
+extern api_syscb *sysCallbackApi;
+#define WASABI_API_SYSCB sysCallbackApi
+
+#include "../nu/threadpool/api_threadpool.h"
+extern api_threadpool *threadPoolApi;
+#define WASABI_API_THREADPOOL threadPoolApi
+
+// (BigG) Added for playlist export support
+#include "../playlist/api_playlists.h"
+extern api_playlists *playlistsApi;
+#define AGAVE_API_PLAYLISTS playlistsApi
+
+// Added for Stat collection
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/api_playlist_generator.h b/Src/Plugins/Library/ml_plg/api_playlist_generator.h
new file mode 100644
index 00000000..58de6724
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/api_playlist_generator.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include <api/service/services.h>
+
+#include "..\..\General\gen_ml/ml.h"
+
+// {70B27610-D1C9-4442-ABF2-763AD041E458}
+static const GUID playlistGeneratorGUID =
+{ 0x70b27610, 0xd1c9, 0x4442, { 0xab, 0xf2, 0x76, 0x3a, 0xd0, 0x41, 0xe4, 0x58 } };
+
+class api_playlist_generator : public Dispatchable
+{
+protected:
+ api_playlist_generator() {}
+ ~api_playlist_generator() {}
+public:
+ static FOURCC getServiceType() { return WaSvc::UNIQUE; }
+ static const char *getServiceName() { return "Playlist Generator Service"; }
+ static GUID getServiceGuid() { return playlistGeneratorGUID; }
+
+ int GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList);
+
+ enum
+ {
+ API_PLAYLIST_GENERATOR_GENERATEPLAYLIST = 0,
+ };
+
+};
+
+inline int api_playlist_generator::GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList)
+{
+ return _call(API_PLAYLIST_GENERATOR_GENERATEPLAYLIST, (int)DISPATCH_FAILURE, parent, selectedSeedRecordList);
+}
+
+
diff --git a/Src/Plugins/Library/ml_plg/atltransactionmanager.h b/Src/Plugins/Library/ml_plg/atltransactionmanager.h
new file mode 100644
index 00000000..4294ec4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/atltransactionmanager.h
@@ -0,0 +1,697 @@
+// This is a part of the Active Template Library.
+// Copyright (C) Microsoft Corporation
+// All rights reserved.
+//
+// This source code is only intended as a supplement to the
+// Active Template Library Reference and related
+// electronic documentation provided with the library.
+// See these sources for detailed information regarding the
+// Active Template Library product.
+
+#ifndef __ATLTRANSACTIONMANAGER_H__
+#define __ATLTRANSACTIONMANAGER_H__
+
+#pragma once
+
+#include <atldef.h>
+
+#if !defined(_ATL_USE_WINAPI_FAMILY_DESKTOP_APP)
+#error This file is not compatible with the current WINAPI_FAMILY
+#endif
+
+#include <ktmw32.h>
+#include <tchar.h>
+
+extern "C" _VCRTIMP bool __cdecl __uncaught_exception();
+
+#pragma pack(push,_ATL_PACKING)
+namespace ATL
+{
+
+/// <summary>
+/// CAtlTransactionManager class provides a wrapper to Kernel Transaction Manager (KTM) functions.</summary>
+class CAtlTransactionManager
+{
+public:
+ /// <summary>
+ /// CAtlTransactionManager constructor</summary>
+ /// <param name="bFallback">TRUE - support fallback. If transacted function fails, the class automatically calls the "non-transacted" function. FALSE - no "fallback" calls.</param>
+ /// <param name="bAutoCreateTransaction">TRUE - auto-create transaction handler in constructor. FALSE - don't create</param>
+ explicit CAtlTransactionManager(_In_ BOOL bFallback = TRUE, _In_ BOOL bAutoCreateTransaction = TRUE) :
+ m_hTransaction(NULL), m_bFallback(bFallback)
+ {
+ if (bAutoCreateTransaction)
+ {
+ Create();
+ }
+ }
+
+ /// <summary>
+ /// CAtlTransactionManager destructor. In normal processing, the transaction is automatically committed and closed. If the destructor is called during an exception unwind, the transaction is rolled back and closed.</summary>
+ ~CAtlTransactionManager()
+ {
+ if (m_hTransaction != NULL)
+ {
+ if (__uncaught_exception())
+ {
+ Rollback();
+ }
+ else
+ {
+ Commit();
+ }
+
+ Close();
+ }
+ }
+
+private:
+ // Copy construction and copy are not supported, so make sure that the compiler does not generate
+ // implicit versions and that a compiler error is issued if someone attempts to use them.
+ CAtlTransactionManager(_In_ const CAtlTransactionManager &atm);
+ CAtlTransactionManager &operator=(_In_ const CAtlTransactionManager &atm);
+
+// Attributes:
+public:
+ /// <summary>
+ /// Returns transaction handle</summary>
+ /// <returns>
+ /// Returns the transaction handle for a class. Returns NULL if the CAtlTransactionManager is not attached to a handle.</returns>
+ HANDLE GetHandle() const
+ {
+ return m_hTransaction;
+ }
+
+ /// <summary>
+ /// Determines whether the fallback calls are enabled </summary>
+ /// <returns>
+ /// Returns TRUE is the class support fallback calls. FALSE - otherwise.</returns>
+ BOOL IsFallback() const
+ {
+ return m_bFallback;
+ }
+
+// Operattions:
+public:
+ /// <summary>
+ /// Creates transaction handle. This wrapper calls Windows CreateTransaction function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Create();
+
+ /// <summary>
+ /// Closes transaction handle. This wrapper calls Windows CloseHandle function. The method is automatically called in destructor</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Close();
+
+ /// <summary>
+ /// Requests that the transaction be committed. This wrapper calls Windows CommitTransaction function. The method is automatically called in destructor.</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Commit();
+
+ /// <summary>
+ /// Requests that the transaction be rolled back. This wrapper calls Windows RollbackTransaction function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Rollback();
+
+ /// <summary>
+ /// Creates or opens a file, file stream, or directory as a transacted operation. This wrapper calls Windows CreateFileTransacted function</summary>
+ /// <returns>
+ /// Returns a handle that can be used to access the object.</returns>
+ /// <param name="lpFileName">The name of an object to be created or opened.</param>
+ /// <param name="dwDesiredAccess">The access to the object, which can be summarized as read, write, both or neither (zero). The most commonly used values are GENERIC_READ, GENERIC_WRITE, or both (GENERIC_READ | GENERIC_WRITE).</param>
+ /// <param name="dwShareMode">The sharing mode of an object, which can be read, write, both, delete, all of these, or none: 0, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE</param>
+ /// <param name="lpSecurityAttributes">A pointer to a SECURITY_ATTRIBUTES structure that contains an optional security descriptor and also determines whether or not the returned handle can be inherited by child processes. The parameter can be NULL</param>
+ /// <param name="dwCreationDisposition">An action to take on files that exist and do not exist. This parameter must be one of the following values, which cannot be combined: CREATE_ALWAYS, CREATE_NEW, OPEN_ALWAYS, OPEN_EXISTING or TRUNCATE_EXISTING</param>
+ /// <param name="dwFlagsAndAttributes">The file attributes and flags. This parameter can include any combination of the available file attributes (FILE_ATTRIBUTE_*). All other file attributes override FILE_ATTRIBUTE_NORMAL. This parameter can also contain combinations of flags (FILE_FLAG_*) for control of buffering behavior, access modes, and other special-purpose flags. These combine with any FILE_ATTRIBUTE_* values.</param>
+ /// <param name="hTemplateFile">A valid handle to a template file with the GENERIC_READ access right. The template file supplies file attributes and extended attributes for the file that is being created. This parameter can be NULL.</param>
+ HANDLE CreateFile(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwDesiredAccess,
+ _In_ DWORD dwShareMode,
+ _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _In_ DWORD dwCreationDisposition,
+ _In_ DWORD dwFlagsAndAttributes,
+ _In_opt_ HANDLE hTemplateFile);
+
+ /// <summary>
+ /// Deletes an existing file as a transacted operation. This wrapper calls Windows DeleteFileTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpFileName">The name of the file to be deleted.</param>
+ BOOL DeleteFile(_In_z_ LPCTSTR lpFileName);
+
+ /// <summary>
+ /// Moves an existing file or a directory, including its children, as a transacted operation. This wrapper calls Windows MoveFileTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpOldFileName">The current name of the existing file or directory on the local computer.</param>
+ /// <param name="lpNewFileName">The new name for the file or directory. The new name must not already exist. A new file may be on a different file system or drive. A new directory must be on the same drive.</param>
+ BOOL MoveFile(
+ _In_z_ LPCTSTR lpOldFileName,
+ _In_z_ LPCTSTR lpNewFileName);
+
+ /// <summary>
+ /// Retrieves file system attributes for a specified file or directory as a transacted operation. This wrapper calls Windows GetFileAttributesTransacted function</summary>
+ /// <returns>
+ /// File attributes (see WIN32_FILE_ATTRIBUTE_DATA::dwFileAttributes desciption).</returns>
+ /// <param name="lpFileName">The name of the file or directory.</param>
+ DWORD GetFileAttributes(_In_z_ LPCTSTR lpFileName);
+
+ /// <summary>
+ /// Retrieves file system attributes for a specified file or directory as a transacted operation. This wrapper calls Windows GetFileAttributesTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpFileName">The name of the file or directory.</param>
+ /// <param name="fInfoLevelId">The level of attribute information to retrieve.</param>
+ /// <param name="lpFileInformation">A pointer to a buffer that receives the attribute information. The type of attribute information that is stored into this buffer is determined by the value of fInfoLevelId. If the fInfoLevelId parameter is GetFileExInfoStandard then this parameter points to a WIN32_FILE_ATTRIBUTE_DATA structure.</param>
+ _Success_(return != FALSE) BOOL GetFileAttributesEx(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ _Out_opt_ LPVOID lpFileInformation);
+
+ /// <summary>
+ /// Sets the attributes for a file or directory as a transacted operation. This wrapper calls Windows SetFileAttributesTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpFileName">The name of the file or directory.</param>
+ /// <param name="dwAttributes">The file attributes to set for the file. See SetFileAttributesTransacted function description</param>
+ BOOL SetFileAttributes(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwAttributes);
+
+ /// <summary>
+ /// Searches a directory for a file or subdirectory with a name that matches a specific name as a transacted operation. This wrapper calls Windows FindFirstFileTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose. If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE.</returns>
+ /// <param name="lpFileName">The directory or path, and the file name, which can include wildcard characters, for example, an asterisk (*) or a question mark (?).</param>
+ /// <param name="pNextInfo">A pointer to the WIN32_FIND_DATA structure that receives information about a found file or subdirectory.</param>
+ _Success_(return != INVALID_HANDLE_VALUE) HANDLE FindFirstFile(
+ _In_z_ LPCTSTR lpFileName,
+ _Out_opt_ WIN32_FIND_DATA* pNextInfo);
+
+ /// <summary>
+ /// Creates the specified registry key and associates it with a transaction. If the key already exists, the function opens it. This wrapper calls Windows RegCreateKeyTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in Winerror.h</returns>
+ /// <param name="hKey">A handle to an open registry key.</param>
+ /// <param name="lpSubKey">The name of a subkey that this function opens or creates.</param>
+ /// <param name="dwReserved">This parameter is reserved and must be zero</param>
+ /// <param name="ulOptions">This parameter can be one of the following values: REG_OPTION_BACKUP_RESTORE, REG_OPTION_NON_VOLATILE or REG_OPTION_VOLATILE.</param>
+ /// <param name="samDesired">A mask that specifies the access rights for the key</param>
+ /// <param name="lpSecurityAttributes"> pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpSecurityAttributes is NULL, the handle cannot be inherited</param>
+ /// <param name="phkResult">A pointer to a variable that receives a handle to the opened or created key. If the key is not one of the predefined registry keys, call the RegCloseKey function after you have finished using the handle</param>
+ /// <param name="lpdwDisposition">A pointer to a variable that receives one of the following disposition values: REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY</param>
+ LSTATUS RegCreateKeyEx(
+ _In_ HKEY hKey,
+ _In_z_ LPCTSTR lpSubKey,
+ _Reserved_ DWORD dwReserved,
+ _In_opt_z_ LPTSTR lpClass,
+ _In_ DWORD dwOptions,
+ _In_ REGSAM samDesired,
+ _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _Out_ PHKEY phkResult,
+ _Out_opt_ LPDWORD lpdwDisposition);
+ /// <summary>
+ /// Opens the specified registry key and associates it with a transaction. This wrapper calls Windows RegOpenKeyTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in Winerror.h</returns>
+ /// <param name="hKey">A handle to an open registry key.</param>
+ /// <param name="lpSubKey">The name of the registry subkey to be opened.</param>
+ /// <param name="ulOptions">This parameter is reserved and must be zero.</param>
+ /// <param name="samDesired">A mask that specifies the access rights for the key</param>
+ /// <param name="phkResult">A pointer to a variable that receives a handle to the opened or created key. If the key is not one of the predefined registry keys, call the RegCloseKey function after you have finished using the handle</param>
+ LSTATUS RegOpenKeyEx(
+ _In_ HKEY hKey,
+ _In_opt_z_ LPCTSTR lpSubKey,
+ _In_ DWORD ulOptions,
+ _In_ REGSAM samDesired,
+ _Out_ PHKEY phkResult);
+ /// <summary>
+ /// Deletes a subkey and its values from the specified platform-specific view of the registry as a transacted operation. This wrapper calls Windows RegDeleteKeyTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in Winerror.h</returns>
+ /// <param name="hKey">A handle to an open registry key.</param>
+ /// <param name="lpSubKey">The name of the key to be deleted.</param>
+ LSTATUS RegDeleteKey(
+ _In_ HKEY hKey,
+ _In_z_ LPCTSTR lpSubKey);
+
+protected:
+ /// <summary>
+ /// Transaction handle</summary>
+ HANDLE m_hTransaction;
+
+ /// <summary>
+ /// TRUE: if the fallback is supported; FALSE - otherwise.</summary>
+ BOOL m_bFallback;
+};
+
+inline BOOL CAtlTransactionManager::Create()
+{
+ if (m_hTransaction != NULL)
+ {
+ // Already created
+ ATLASSERT(FALSE);
+ return FALSE;
+ }
+
+ typedef HANDLE (WINAPI* PFNCREATETRANSACTION)(LPSECURITY_ATTRIBUTES, LPGUID, DWORD, DWORD, DWORD, DWORD, LPWSTR);
+ static bool bInitialized = false;
+ static PFNCREATETRANSACTION pfCreateTransaction = NULL;
+
+ if (!bInitialized)
+ {
+ HMODULE hKTM32 = AtlLoadSystemLibraryUsingFullPath(L"ktmw32.dll");
+ if (hKTM32 != NULL)
+ {
+ pfCreateTransaction = (PFNCREATETRANSACTION)GetProcAddress(hKTM32, "CreateTransaction");
+ }
+ bInitialized = true;
+ }
+
+ if (pfCreateTransaction == NULL)
+ {
+ return FALSE;
+ }
+
+ SECURITY_ATTRIBUTES sa;
+ ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
+
+ m_hTransaction = (*pfCreateTransaction)(&sa, 0, 0, 0, 0, 0, NULL);
+ return m_hTransaction != NULL;
+}
+
+inline BOOL CAtlTransactionManager::Close()
+{
+ if (m_hTransaction == NULL)
+ {
+ return FALSE;
+ }
+
+ if (!::CloseHandle(m_hTransaction))
+ {
+ return FALSE;
+ }
+
+ m_hTransaction = NULL;
+ return TRUE;
+}
+
+inline BOOL CAtlTransactionManager::Commit()
+{
+ if (m_hTransaction == NULL)
+ {
+ ATLASSERT(FALSE);
+ return FALSE;
+ }
+
+ typedef BOOL (WINAPI* PFNCOMMITTRANSACTION)(HANDLE);
+ static bool bInitialized = false;
+ static PFNCOMMITTRANSACTION pfCommitTransaction = NULL;
+
+ if (!bInitialized)
+ {
+ HMODULE hKTM32 = AtlLoadSystemLibraryUsingFullPath(L"ktmw32.dll");
+ if (hKTM32 != NULL)
+ {
+ pfCommitTransaction = (PFNCOMMITTRANSACTION)GetProcAddress(hKTM32, "CommitTransaction");
+ }
+ bInitialized = true;
+ }
+
+ if (pfCommitTransaction != NULL)
+ {
+ return (*pfCommitTransaction)(m_hTransaction);
+ }
+
+ return FALSE;
+}
+
+inline BOOL CAtlTransactionManager::Rollback()
+{
+ if (m_hTransaction == NULL)
+ {
+ ATLASSERT(FALSE);
+ return FALSE;
+ }
+
+ typedef BOOL (WINAPI* PFNROLLBACKTRANSACTION)(HANDLE);
+ static bool bInitialized = false;
+ static PFNROLLBACKTRANSACTION pfRollbackTransaction = NULL;
+
+ if (!bInitialized)
+ {
+ HMODULE hKTM32 = AtlLoadSystemLibraryUsingFullPath(L"ktmw32.dll");
+ if (hKTM32 != NULL)
+ {
+ pfRollbackTransaction = (PFNROLLBACKTRANSACTION)GetProcAddress(hKTM32, "RollbackTransaction");
+ }
+ bInitialized = true;
+ }
+
+ if (pfRollbackTransaction != NULL)
+ {
+ return (*pfRollbackTransaction)(m_hTransaction);
+ }
+
+ return FALSE;
+}
+
+inline HANDLE CAtlTransactionManager::CreateFile(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwDesiredAccess,
+ _In_ DWORD dwShareMode,
+ _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _In_ DWORD dwCreationDisposition,
+ _In_ DWORD dwFlagsAndAttributes,
+ _In_opt_ HANDLE hTemplateFile)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return INVALID_HANDLE_VALUE;
+ }
+
+#ifdef _UNICODE
+ typedef HANDLE (WINAPI* PFNCREATEFILETRANSACTED)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE, HANDLE, PUSHORT, PVOID);
+ PFNCREATEFILETRANSACTED pfCreateTransacted = (PFNCREATEFILETRANSACTED)GetProcAddress(hKernel32, "CreateFileTransactedW");
+#else
+ typedef HANDLE (WINAPI* PFNCREATEFILETRANSACTED)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE, HANDLE, PUSHORT, PVOID);
+ PFNCREATEFILETRANSACTED pfCreateTransacted = (PFNCREATEFILETRANSACTED)GetProcAddress(hKernel32, "CreateFileTransactedA");
+#endif
+ if (pfCreateTransacted != NULL)
+ {
+ return (*pfCreateTransacted)(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, m_hTransaction, NULL, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::CreateFile((LPCTSTR)lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, NULL);
+ }
+
+ return INVALID_HANDLE_VALUE;
+}
+
+inline BOOL CAtlTransactionManager::DeleteFile(_In_z_ LPCTSTR lpFileName)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNDELETEFILETRANSACTED)(LPCWSTR, HANDLE);
+ PFNDELETEFILETRANSACTED pfDeleteTransacted = (PFNDELETEFILETRANSACTED)GetProcAddress(hKernel32, "DeleteFileTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNDELETEFILETRANSACTED)(LPCSTR, HANDLE);
+ PFNDELETEFILETRANSACTED pfDeleteTransacted = (PFNDELETEFILETRANSACTED)GetProcAddress(hKernel32, "DeleteFileTransactedA");
+#endif
+ if (pfDeleteTransacted != NULL)
+ {
+ return (*pfDeleteTransacted)(lpFileName, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return DeleteFile((LPTSTR)lpFileName);
+ }
+
+ return FALSE;
+}
+
+inline BOOL CAtlTransactionManager::MoveFile(
+ _In_z_ LPCTSTR lpOldFileName,
+ _In_z_ LPCTSTR lpNewFileName)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNMOVEFILETRANSACTED)(LPCWSTR, LPCWSTR, LPPROGRESS_ROUTINE, LPVOID, DWORD, HANDLE);
+ PFNMOVEFILETRANSACTED pfMoveFileTransacted = (PFNMOVEFILETRANSACTED)GetProcAddress(hKernel32, "MoveFileTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNMOVEFILETRANSACTED)(LPCSTR, LPCSTR, LPPROGRESS_ROUTINE, LPVOID, DWORD, HANDLE);
+ PFNMOVEFILETRANSACTED pfMoveFileTransacted = (PFNMOVEFILETRANSACTED)GetProcAddress(hKernel32, "MoveFileTransactedA");
+#endif
+ if (pfMoveFileTransacted != NULL)
+ {
+ return (*pfMoveFileTransacted)(lpOldFileName, lpNewFileName, NULL, NULL, MOVEFILE_COPY_ALLOWED, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::MoveFile(lpOldFileName, lpNewFileName);
+ }
+
+ return FALSE;
+}
+
+inline _Success_(return != FALSE) BOOL CAtlTransactionManager::GetFileAttributesEx(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ _Out_opt_ LPVOID lpFileInformation)
+{
+ if (lpFileInformation == NULL)
+ {
+ return FALSE;
+ }
+
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNGETFILEATTRIBUTESTRANSACTED)(LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID, HANDLE);
+ PFNGETFILEATTRIBUTESTRANSACTED pfGetFileAttributesTransacted = (PFNGETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "GetFileAttributesTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNGETFILEATTRIBUTESTRANSACTED)(LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID, HANDLE);
+ PFNGETFILEATTRIBUTESTRANSACTED pfGetFileAttributesTransacted = (PFNGETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "GetFileAttributesTransactedA");
+#endif
+ if (pfGetFileAttributesTransacted != NULL)
+ {
+ return (*pfGetFileAttributesTransacted)(lpFileName, fInfoLevelId, lpFileInformation, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::GetFileAttributesEx((LPCTSTR)lpFileName, fInfoLevelId, lpFileInformation);
+ }
+
+ return FALSE;
+}
+
+inline DWORD CAtlTransactionManager::GetFileAttributes(_In_z_ LPCTSTR lpFileName)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fileAttributeData;
+ if (GetFileAttributesEx(lpFileName, GetFileExInfoStandard, &fileAttributeData))
+ {
+ return fileAttributeData.dwFileAttributes;
+ }
+
+ return 0;
+}
+
+inline BOOL CAtlTransactionManager::SetFileAttributes(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwAttributes)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNSETFILEATTRIBUTESTRANSACTED)(LPCWSTR, DWORD, HANDLE);
+ PFNSETFILEATTRIBUTESTRANSACTED pfSetFileAttributesTransacted = (PFNSETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "SetFileAttributesTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNSETFILEATTRIBUTESTRANSACTED)(LPCSTR, DWORD, HANDLE);
+ PFNSETFILEATTRIBUTESTRANSACTED pfSetFileAttributesTransacted = (PFNSETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "SetFileAttributesTransactedA");
+#endif
+ if (pfSetFileAttributesTransacted != NULL)
+ {
+ return (*pfSetFileAttributesTransacted)(lpFileName, dwAttributes, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::SetFileAttributes((LPCTSTR)lpFileName, dwAttributes);
+ }
+
+ return FALSE;
+}
+
+inline _Success_(return != INVALID_HANDLE_VALUE) HANDLE CAtlTransactionManager::FindFirstFile(
+ _In_z_ LPCTSTR lpFileName,
+ _Out_opt_ WIN32_FIND_DATA* pNextInfo)
+{
+ if (pNextInfo == NULL)
+ {
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return INVALID_HANDLE_VALUE;
+ }
+
+#ifdef _UNICODE
+ typedef HANDLE (WINAPI* PFNFINDFIRSTFILETRANSACTED)(LPCWSTR, FINDEX_INFO_LEVELS, LPVOID, FINDEX_SEARCH_OPS, LPVOID, DWORD, HANDLE);
+ PFNFINDFIRSTFILETRANSACTED pfFindFirstFileTransacted = (PFNFINDFIRSTFILETRANSACTED)GetProcAddress(hKernel32, "FindFirstFileTransactedW");
+#else
+ typedef HANDLE (WINAPI* PFNFINDFIRSTFILETRANSACTED)(LPCSTR, FINDEX_INFO_LEVELS, LPVOID, FINDEX_SEARCH_OPS, LPVOID, DWORD, HANDLE);
+ PFNFINDFIRSTFILETRANSACTED pfFindFirstFileTransacted = (PFNFINDFIRSTFILETRANSACTED)GetProcAddress(hKernel32, "FindFirstFileTransactedA");
+#endif
+ if (pfFindFirstFileTransacted != NULL)
+ {
+ return (*pfFindFirstFileTransacted)(lpFileName, FindExInfoStandard, pNextInfo, FindExSearchNameMatch, NULL, 0, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::FindFirstFile(lpFileName, pNextInfo);
+ }
+
+ return INVALID_HANDLE_VALUE;
+}
+
+inline LSTATUS CAtlTransactionManager::RegOpenKeyEx(
+ _In_ HKEY hKey,
+ _In_opt_z_ LPCTSTR lpSubKey,
+ _In_ DWORD ulOptions,
+ _In_ REGSAM samDesired,
+ _Out_ PHKEY phkResult)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hAdvAPI32 = ::GetModuleHandle(_T("Advapi32.dll"));
+ ATLASSERT(hAdvAPI32 != NULL);
+ if (hAdvAPI32 == NULL)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+#ifdef _UNICODE
+ typedef LSTATUS (WINAPI* PFNREGOPENKEYTRANSACTED)(HKEY, LPCWSTR, DWORD, REGSAM, PHKEY, HANDLE, PVOID);
+ PFNREGOPENKEYTRANSACTED pfRegOpenKeyTransacted = (PFNREGOPENKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegOpenKeyTransactedW");
+#else
+ typedef LSTATUS (WINAPI* PFNREGOPENKEYTRANSACTED)(HKEY, LPCSTR, DWORD, REGSAM, PHKEY, HANDLE, PVOID);
+ PFNREGOPENKEYTRANSACTED pfRegOpenKeyTransacted = (PFNREGOPENKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegOpenKeyTransactedA");
+#endif
+ if (pfRegOpenKeyTransacted != NULL)
+ {
+ return (*pfRegOpenKeyTransacted)(hKey, lpSubKey, ulOptions, samDesired, phkResult, m_hTransaction, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::RegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, phkResult);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+inline LSTATUS CAtlTransactionManager::RegCreateKeyEx(
+ _In_ HKEY hKey,
+ _In_z_ LPCTSTR lpSubKey,
+ _Reserved_ DWORD dwReserved,
+ _In_opt_z_ LPTSTR lpClass,
+ _In_ DWORD dwOptions,
+ _In_ REGSAM samDesired,
+ _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _Out_ PHKEY phkResult,
+ _Out_opt_ LPDWORD lpdwDisposition)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hAdvAPI32 = ::GetModuleHandle(_T("Advapi32.dll"));
+ ATLASSERT(hAdvAPI32 != NULL);
+ if (hAdvAPI32 == NULL)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+#ifdef _UNICODE
+ typedef LSTATUS (WINAPI* PFNREGCREATEKEYTRANSACTED)(HKEY, LPCWSTR, DWORD, LPWSTR, DWORD, REGSAM, CONST LPSECURITY_ATTRIBUTES, PHKEY, LPDWORD, HANDLE, PVOID);
+ PFNREGCREATEKEYTRANSACTED pfRegCreateKeyTransacted = (PFNREGCREATEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegCreateKeyTransactedW");
+#else
+ typedef LSTATUS (WINAPI* PFNREGCREATEKEYTRANSACTED)(HKEY, LPCSTR, DWORD, LPSTR, DWORD, REGSAM, CONST LPSECURITY_ATTRIBUTES, PHKEY, LPDWORD, HANDLE, PVOID);
+ PFNREGCREATEKEYTRANSACTED pfRegCreateKeyTransacted = (PFNREGCREATEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegCreateKeyTransactedA");
+#endif
+ if (pfRegCreateKeyTransacted != NULL)
+ {
+ return (*pfRegCreateKeyTransacted)(hKey, lpSubKey, dwReserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition, m_hTransaction, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::RegCreateKeyEx(hKey, lpSubKey, dwReserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+inline LSTATUS CAtlTransactionManager::RegDeleteKey(_In_ HKEY hKey, _In_z_ LPCTSTR lpSubKey)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hAdvAPI32 = ::GetModuleHandle(_T("Advapi32.dll"));
+ ATLASSERT(hAdvAPI32 != NULL);
+ if (hAdvAPI32 == NULL)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+#ifdef _UNICODE
+ typedef LSTATUS (WINAPI* PFNREGDELETEKEYTRANSACTED)(HKEY, LPCWSTR, REGSAM, DWORD, HANDLE, PVOID);
+ PFNREGDELETEKEYTRANSACTED pfRegDeleteKeyTransacted = (PFNREGDELETEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegDeleteKeyTransactedW");
+#else
+ typedef LSTATUS (WINAPI* PFNREGDELETEKEYTRANSACTED)(HKEY, LPCSTR, REGSAM, DWORD, HANDLE, PVOID);
+ PFNREGDELETEKEYTRANSACTED pfRegDeleteKeyTransacted = (PFNREGDELETEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegDeleteKeyTransactedA");
+#endif
+ if (pfRegDeleteKeyTransacted != NULL)
+ {
+ return (*pfRegDeleteKeyTransacted)(hKey, lpSubKey, 0, 0, m_hTransaction, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::RegDeleteKey(hKey, lpSubKey);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+} //namespace ATL
+#pragma pack(pop)
+
+#endif // __ATLTRANSACTIONMANAGER_H__
diff --git a/Src/Plugins/Library/ml_plg/generate.cpp b/Src/Plugins/Library/ml_plg/generate.cpp
new file mode 100644
index 00000000..ed392dda
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/generate.cpp
@@ -0,0 +1,766 @@
+#include "../gracenote/gracenote.h"
+#include "api__ml_plg.h"
+#include <windows.h>
+#include "resource.h"
+#include "../../General/gen_ml/ml.h"
+#include "../winamp/wa_ipc.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/ComboBox.h"
+
+#include "main.h"
+#include <shlwapi.h>
+#include <assert.h>
+#include "playlist.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+//#include "../Wasabi/bfc/util/timefmt.h"
+//#include <bfc/util/timefmt.h>
+
+#include <strsafe.h> // should be last
+
+HWND hwndDlgCurrent = 0;
+bool optionsVisible = true;
+bool isGenerating = false;
+int originalWidth = 877;
+//#define DIALOG_WIDTH_OPTIONS 877 // use originalWidth instead
+#define DIALOG_WIDTH_NO_OPTIONS 610
+#define DIALOG_HIDDEN_COLUMN_ID 4
+
+// Pass in 0 for width or height in order to preserve its current dimension
+void SizeWindow(HWND hwnd, int width, int height)
+{
+ if (width == 0 || height == 0) // Preserve only if one of the items is 0
+ {
+ RECT windowRect;
+ GetWindowRect(hwnd, &windowRect);
+
+ if (width == 0) // Preserve the width
+ width = windowRect.right - windowRect.left;
+ if (height == 0) // Preserve the height
+ height = windowRect.bottom - windowRect.top;
+ }
+ SetWindowPos(hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+void ClientResize(HWND hWnd, int nWidth, int nHeight)
+{
+ RECT rcClient, rcWind;
+ POINT ptDiff;
+ GetClientRect(hWnd, &rcClient);
+ GetWindowRect(hWnd, &rcWind);
+ ptDiff.x = (rcWind.right - rcWind.left) - rcClient.right;
+ ptDiff.y = (rcWind.bottom - rcWind.top) - rcClient.bottom;
+ MoveWindow(hWnd,rcWind.left, rcWind.top, nWidth + ptDiff.x, nHeight + ptDiff.y, TRUE);
+}
+
+void SetMarqueeProgress(bool isMarquee)
+{
+ HWND hwndProgress = GetDlgItem(hwndDlgCurrent,IDC_PROGRESS_GENERATE);
+ static long state = GetWindowLongW(hwndProgress, GWL_STYLE); // Capture the initial state of the progress bar
+
+
+ if (isMarquee) // Set it to marquee style
+ {
+ SetWindowLong (hwndProgress, GWL_STYLE, GetWindowLong(hwndProgress, GWL_STYLE) | PBS_MARQUEE);
+ //SendMessage(hwndProgress, PBM_SETMARQUEE, 1, 10);
+ SendMessage(hwndProgress, PBM_SETMARQUEE, 1, 30);
+ }
+ else // Restore the normal progress bar
+ {
+ SetWindowLong (hwndProgress, GWL_STYLE, state);
+ //SendMessage(hwndProgress, WM_PAINT, 0, 0);
+ InvalidateRect(hwndProgress, 0, 1); // Force a repaint of the marquee after turning it off because there are stuck pixels in XP
+ }
+}
+
+// Sets the query check state as well as enabling all the controls involved
+void SetMLQueryCheckState(HWND hwndDlg, unsigned int checked)
+{
+ // Get the handles to all the child controls we want to enable / disable
+ HWND hwndButtonMlQuery = GetDlgItem(hwndDlg, IDC_BUTTON_ML_QUERY);
+ HWND hwndEditMlQuery = GetDlgItem(hwndDlg, IDC_EDIT_ML_QUERY);
+ HWND hwndButtonRestoreQueryDefault = GetDlgItem(hwndDlg, IDC_BUTTON_RESTORE_QUERY_DEFAULT);
+
+ if (checked) // enable all the controls related to ML query
+ {
+ CheckDlgButton(hwndDlg, IDC_CHECK_ML_QUERY, TRUE);
+ EnableWindow (hwndButtonMlQuery, TRUE );
+ EnableWindow (hwndEditMlQuery, TRUE );
+ EnableWindow (hwndButtonRestoreQueryDefault, TRUE );
+ useMLQuery = true;
+ }
+ else // disable all the controls related to ML query
+ {
+ CheckDlgButton(hwndDlg, IDC_CHECK_ML_QUERY, FALSE);
+ EnableWindow (hwndButtonMlQuery, FALSE );
+ EnableWindow (hwndEditMlQuery, FALSE );
+ EnableWindow (hwndButtonRestoreQueryDefault, FALSE );
+ useMLQuery = false;
+ }
+}
+
+void SetButtonsEnabledState(bool enabled_flag)
+{
+ int itemIds[] =
+ {
+ IDC_BUTTON_PLAY_NOW,
+ IDC_BUTTON_ENQUEUE_NOW,
+ IDC_BUTTON_SAVEAS,
+ IDC_BUTTON_REGENERATE
+ };
+
+ for(int i = 0; i < sizeof(itemIds) / sizeof(itemIds[0]); i++)
+ EnableWindow(GetDlgItem(hwndDlgCurrent, itemIds[i]), enabled_flag);
+}
+
+void ToggleOptions(bool reset)
+{
+ if (reset)
+ optionsVisible = false;
+ else
+ optionsVisible = !optionsVisible; // Toggle the options visible state
+
+ // to resolve tabbing issues when in the collapsed
+ // state we need to disable some of the controls (dro)
+ int itemIds[] = {
+ IDC_RADIO_PLAYLIST_ITEMS,
+ IDC_RADIO_PLAYLIST_LENGTH,
+ IDC_RADIO_PLAYLIST_SIZE,
+ IDC_COMBO_LENGTH,
+ IDC_CHECK_USE_SEED,
+ IDC_CHECK_MULTIPLE_ARTISTS,
+ IDC_CHECK_MULTIPLE_ALBUMS,
+ IDC_CHECK_ML_QUERY,
+ IDC_EDIT_ML_QUERY,
+ IDC_BUTTON_ML_QUERY,
+ IDC_BUTTON_RESTORE_QUERY_DEFAULT
+ };
+ for(int i = 0; i < sizeof(itemIds) / sizeof(itemIds[0]); i++)
+ EnableWindow(GetDlgItem(hwndDlgCurrent, itemIds[i]), optionsVisible);
+
+ SetMLQueryCheckState(hwndDlgCurrent, useMLQuery);
+
+ if (optionsVisible)
+ {
+ SizeWindow(hwndDlgCurrent, originalWidth, 0); // Resize the window to the correct width
+ SetDlgItemText(hwndDlgCurrent, IDC_BUTTON_OPTIONS, WASABI_API_LNGSTRINGW(IDS_OPTIONS)); // Set the dialog button to show the correct options mode
+ }
+ else
+ {
+ SizeWindow(hwndDlgCurrent, DIALOG_WIDTH_NO_OPTIONS, 0);
+ SetDlgItemText(hwndDlgCurrent, IDC_BUTTON_OPTIONS, WASABI_API_LNGSTRINGW(IDS_NO_OPTIONS)); // Set the dialog button to show the correct options mode
+ }
+}
+
+// ToDo: Make this more human readable
+void FormatToMinutesAndSeconds(const int lengthInSeconds, wchar_t *buff, const size_t cchBuf)
+{
+ //StringCchPrintfW(buff, cchBuf, L"%d:%02d", lengthInSeconds / 60, lengthInSeconds % 60);
+
+ int total_length_s = lengthInSeconds;
+ int uncert = 0;
+
+ // Minutes and seconds
+ if (total_length_s < 60*60) StringCchPrintfW(buff, 64, L"%s%u:%02u", uncert ? L"~" : L"", total_length_s / 60, total_length_s % 60);
+ // Hours minutes and seconds
+ else if (total_length_s < 60*60*24) StringCchPrintfW(buff, 64, L"%s%u:%02u:%02u", uncert ? L"~" : L"", total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ else
+ {
+ wchar_t days[16] = {0};
+ int total_days = total_length_s / (60 * 60 * 24); // Calculate days
+ total_length_s -= total_days * 60 * 60 * 24; // Remove days from length
+ StringCchPrintfW(buff, 64,
+ //WASABI_API_LNGSTRINGW(IDS_LENGTH_DURATION_STRING),
+ L"%s%u %s+%u:%02u:%02u",
+ ((uncert) ? L"~" : L""), total_days, // Approximate
+ WASABI_API_LNGSTRINGW_BUF(total_days == 1 ? IDS_DAY : IDS_DAYS, days, 16), // Days
+ total_length_s / 60 / 60, // Hours
+ (total_length_s / 60) % 60, // Minutes
+ total_length_s % 60); // Seconds
+ }
+}
+
+// Refreashed the statistics about the generated playlist
+void UpdateStats(void)
+{
+ const int MAX_STATS = 512;
+ wchar_t stats[MAX_STATS] = {0};
+ wchar_t lengthText[MAX_STATS] = {0};
+ wchar_t sizeText[MAX_STATS] = {0};
+ int count = (int)currentPlaylist.GetNumItems();
+ uint64_t length = currentPlaylist.GetPlaylistLengthMilliseconds();
+ uint64_t size = currentPlaylist.GetPlaylistSizeBytes();
+
+ // Add the seed stats?
+ if (useSeed == TRUE)
+ {
+ count += (int)seedPlaylist.GetNumItems();
+ length += seedPlaylist.GetPlaylistLengthMilliseconds();
+ size += seedPlaylist.GetPlaylistSizeBytes();
+ }
+
+ FormatToMinutesAndSeconds((int)(length / 1000), lengthText, MAX_STATS); // / 1000 because we have it in milliseconds and not seconds
+ StrFormatByteSizeW(size, sizeText, MAX_STATS); // Get the human readable formatting for filesize
+
+ StringCchPrintf(stats, MAX_STATS, WASABI_API_LNGSTRINGW(IDS_STATS), count, lengthText, sizeText);
+
+ SetDlgItemText(hwndDlgCurrent, IDC_STATIC_STATS, stats); // Set the dialog button to show the correct options mode
+}
+
+// Update the progress to the current
+static void doProgressBar(HWND h, int x, int t=-1) {
+ h = GetDlgItem(h,IDC_PROGRESS_GENERATE);
+ if(t!=-1 && SendMessage(h,PBM_GETRANGE,0,0) != t)
+ SendMessage(h,PBM_SETRANGE32,0,t);
+ SendMessage(h,PBM_SETPOS,x,0);
+}
+
+// Update the status while id scanner is active
+static void FillStatus(HWND hwndDlg)
+{
+ long state, track, tracks;
+ if (scanner.GetStatus(&state, &track, &tracks))
+ {
+ static int x=0;
+ wchar_t *ticker;
+ switch (x++)
+ {
+ case 0: ticker=L""; break;
+ case 1: ticker=L"."; break;
+ case 2: ticker=L".."; break;
+ default: ticker=L"...";
+ }
+ x%=4;
+ wchar_t status[1024]=L"";
+ switch (state)
+ {
+ case IDScanner::STATE_ERROR:
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_INITIALIZING,status,1024);
+ KillTimer(hwndDlg, 1);
+ doProgressBar(hwndDlg,0,1);
+ ShowErrorDlg(hwndDlg);
+ break;
+ case IDScanner::STATE_IDLE:
+ WASABI_API_LNGSTRINGW_BUF(IDS_IDLE,status,1024);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_INITIALIZING:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_INITIALIZING), ticker);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_SYNC:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_SYNC), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_METADATA:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_METADATA), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_MUSICID:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_MUSICID), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_DONE:
+ if (!isGenerating) // Only set the done state if the gneeration has not started yet
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DONE,status,1024);
+ doProgressBar(hwndDlg,0,0); // Turn off the progress bar to 0
+ }
+
+ KillTimer(hwndDlg, 1);
+ break;
+ }
+ if (!isGenerating) // Only set the done state if the gneeration has not started yet
+ {
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_PROGRESS_STATE, status);
+ }
+ }
+}
+
+// Function calls appropriate items when a generation is requested
+void Regenerate(HWND hwndDlg)
+{
+ SendMessage(GetDlgItem(hwndDlgCurrent, IDC_LIST_RESULTS2),LVM_DELETEALLITEMS,0,0); // Clear the listview of all playlist items
+
+ SetTimer(hwndDlg, 1, 500, 0); // Set the progress timer for the scanner
+ StartScan();
+
+ MoreLikeTheseSongs(&seedPlaylist);
+}
+
+// Function draws in colors for the seed listview items
+LRESULT CustomDrawListViewColors(LPARAM lParam)
+{
+ LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
+
+ switch(lplvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT : //Before the paint cycle begins
+ return CDRF_NOTIFYITEMDRAW; //request notifications for individual listview items
+
+ case CDDS_ITEMPREPAINT: //Before an item is drawn
+ if (lplvcd->nmcd.dwItemSpec < seedPlaylist.entries.size()) // Check how many seeds we have, thats how we know which rows in the view to color paint
+ {
+ if (useSeed == TRUE)
+ {
+ lplvcd->clrText = RGB(0,0,255); // Color seed tracks blue
+ }
+ else
+ {
+ lplvcd->clrText = RGB(100,100,100); // Color seed tracks a faded grey
+ }
+ }
+ return CDRF_NEWFONT;
+ break;
+ }
+ return CDRF_DODEFAULT;
+}
+
+int SetRadioControlsState(HWND hwndDlg)
+{
+ // Set the radio buttons for playlist length type, items or minutes
+ if(plLengthType == PL_ITEMS)
+ {
+ CheckDlgButton(hwndDlg,IDC_RADIO_PLAYLIST_ITEMS,TRUE);
+ SendMessage(hwndDlg, WM_COMMAND, IDC_RADIO_PLAYLIST_ITEMS, 0);
+ SetPlLengthTypeComboToItems(hwndDlg, plItems);
+ }
+ else if(plLengthType == PL_MINUTES)
+ {
+ CheckDlgButton(hwndDlg,IDC_RADIO_PLAYLIST_LENGTH,TRUE);
+ SendMessage(hwndDlg, WM_COMMAND, IDC_RADIO_PLAYLIST_LENGTH, 0);
+ SetPlLengthTypeComboToMinutes(hwndDlg, plMinutes);
+ }
+ else if(plLengthType == PL_MEGABYTES)
+ {
+ CheckDlgButton(hwndDlg,IDC_RADIO_PLAYLIST_SIZE,TRUE);
+ SendMessage(hwndDlg, WM_COMMAND, IDC_RADIO_PLAYLIST_SIZE, 0);
+ SetPlLengthTypeComboToMegabytes(hwndDlg, plMegabytes);
+ }
+
+ return 0;
+}
+
+// Update the combo box contents depending on which lengthType we are using
+int UpdateComboLength(HWND hwndDlg)
+{
+ const int BUF_SIZE = 32;
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ wchar_t buf[BUF_SIZE] = {0};
+ combo.GetEditText(buf, BUF_SIZE);
+
+ switch(plLengthType)
+ {
+ case PL_ITEMS:
+ plItems = _wtoi(buf);
+ return 0;
+ break;
+ case PL_MINUTES:
+ plMinutes = _wtoi(buf);
+ return 0;
+ break;
+ case PL_MEGABYTES:
+ plMegabytes = _wtoi(buf);
+ return 0;
+ break;
+ }
+ return 1;
+}
+
+LRESULT tab_fix_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_CHAR)
+ {
+ if(wParam == VK_TAB)
+ {
+ SendMessage(hwndDlgCurrent, WM_NEXTDLGCTL, (GetAsyncKeyState(VK_SHIFT)&0x8000), FALSE);
+ return TRUE;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwndDlg, L"tab_fix_proc"), hwndDlg, uMsg, wParam, lParam);
+}
+
+// this will prevent the hidden column (for making the headers work better)
+// from appearing as sizeable / disabled (as it effectively is)
+LRESULT header_block_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_SETCURSOR)
+ {
+ HDHITTESTINFO hitTest;
+ GetCursorPos(&hitTest.pt);
+ ScreenToClient(hwndDlg, &hitTest.pt);
+ hitTest.flags = hitTest.iItem = 0;
+ SendMessage(hwndDlg, HDM_HITTEST, FALSE, (LPARAM)&hitTest);
+ if(hitTest.iItem == DIALOG_HIDDEN_COLUMN_ID || hitTest.iItem == -1)
+ {
+ SetCursor(LoadCursor(NULL, IDC_ARROW));
+ return TRUE;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwndDlg, L"header_block_proc"), hwndDlg, uMsg, wParam, lParam);
+}
+
+INT_PTR CALLBACK GenerateProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ GetWindowRect(hwndDlg, &r);
+ originalWidth = r.right - r.left;
+
+ // bit hacky but it will resolve issues with tabbing and the combobox in a
+ // dropdown style still not 100% sure why it's failing to work though (dro)
+ HWND combobox = GetWindow(GetDlgItem(hwndDlg, IDC_COMBO_LENGTH), GW_CHILD);
+ SetPropW(combobox, L"tab_fix_proc",(HANDLE)SetWindowLongPtrW(combobox, GWLP_WNDPROC, (LONG_PTR)tab_fix_proc));
+
+ hwndDlgCurrent = hwndDlg; // Set the global so that we have a window open
+
+ // this will make sure that we've got thr aacplus logo shown even when using a localised version
+ SendDlgItemMessage(hwndDlg,IDC_LOGO,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(IDB_GN_LOGO),IMAGE_BITMAP,0,0,LR_SHARED));
+
+ BoldStatusText(GetDlgItem(hwndDlg, IDC_STATIC_PROGRESS_STATE) );
+
+ SetRadioControlsState(hwndDlg); // Set the playlist length state
+
+ if(multipleArtists)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ARTISTS,TRUE);
+ if(multipleAlbums)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ALBUMS,TRUE);
+ if(useSeed)
+ CheckDlgButton(hwndDlg,IDC_CHECK_USE_SEED,TRUE);
+
+ // Set up the colums for the playlist listing
+ #define ListView_InsertColumnW(hwnd, iCol, pcol) \
+ (int)SNDMSG((hwnd), LVM_INSERTCOLUMNW, (WPARAM)(int)(iCol), (LPARAM)(const LV_COLUMNW *)(pcol))
+ //SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+
+ // Add the columns to the listbox
+ HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST_RESULTS2);
+ ListView_SetExtendedListViewStyle(hwndlist, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ LVCOLUMNW lvc = {0, };
+ lvc.mask = LVCF_TEXT|LVCF_WIDTH;
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_TITLE); // Initialize the columns of the listview
+ lvc.cx = 160;
+ ListView_InsertColumnW(hwndlist, 0, &lvc);
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_LENGTH);
+ lvc.cx = 80;
+ ListView_InsertColumnW(hwndlist, 1, &lvc);
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_SIZE);
+ lvc.cx = 80;
+ ListView_InsertColumnW(hwndlist, 2, &lvc);
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_SEED);
+ lvc.cx = 80;
+ ListView_InsertColumnW(hwndlist, 3, &lvc);
+ lvc.pszText = 0;
+ lvc.cx = 0;
+ ListView_InsertColumnW(hwndlist, DIALOG_HIDDEN_COLUMN_ID, &lvc);
+
+ // Autosize the columns taking the header into consideration
+ ListView_SetColumnWidth(hwndlist,0,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,2,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,3,LVSCW_AUTOSIZE_USEHEADER);
+
+ HWND hwndListHeader = ListView_GetHeader(hwndlist);
+ SetPropW(hwndListHeader, L"header_block_proc",(HANDLE)SetWindowLongPtrW(hwndListHeader, GWLP_WNDPROC, (LONG_PTR)header_block_proc));
+
+ // Background color for highlighting seed tracks.
+ //hbrBkcolor = CreateSolidBrush ( RGB(255,0,0) );
+
+ BoldStatusText(GetDlgItem(hwndDlg, IDC_STATIC_STATS) );
+
+ // Populate the query textbox
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_ML_QUERY, mlQuery); // Set the text for the query
+
+ // Disable the regenerate button because we will be scanning the library and generating on initialization
+ //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), FALSE ); // This is for initialization
+ SetButtonsEnabledState(false);
+
+ // Set up the window with the options hidden
+ ToggleOptions(true);
+
+ // Show the window since we are modeless
+ POINT pt = {(LONG)GetPrivateProfileInt(L"ml_plg", L"generate_x",-1, mediaLibrary.GetWinampIniW()),
+ (LONG)GetPrivateProfileInt(L"ml_plg", L"generate_y",-1, mediaLibrary.GetWinampIniW())};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOW);
+
+ Regenerate(hwndDlg);
+
+ if (WASABI_API_APP) // Add direct mousewheel support for the main tracklist view of seed and generated tracks
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(GetDlgItem(hwndDlg, IDC_LIST_RESULTS2), TRUE);
+
+ return TRUE;
+ }
+ break;
+
+ // Trying to change the background color of seed tracks here
+ /*case WM_CTLCOLORSTATIC:
+ {
+ HDC hdc = (HDC) wParam;
+ HWND hwndStatic = (HWND) lParam;
+
+ if ( hwndStatic == GetDlgItem ( hwndDlg, IDC_LIST_RESULTS2 ))
+ {
+ SetBkMode ( hdc, TRANSPARENT );
+ return (LRESULT) hbrBkcolor;
+ }
+ }
+ break;*/
+
+ case WM_TIMER:
+ FillStatus(hwndDlg);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_plg", "generate_x", buf, mediaLibrary.GetWinampIni());
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_plg", "generate_y", buf, mediaLibrary.GetWinampIni());
+
+ EndDialog(hwndDlg, 0);
+ hwndDlgCurrent = 0; // Set to null so new instance can be opened
+
+ WriteSettingsToIni(hwndDlg);
+
+ // We need to free up our seed tracks because we no longer require them
+ seedPlaylist.Clear(); // Clear the global seed list
+ }
+ break;
+ case IDC_BUTTON_CANCEL:
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0);
+ break;
+ case IDC_BUTTON_REGENERATE:
+ Regenerate(hwndDlg);
+ break;
+ case IDC_RADIO_PLAYLIST_ITEMS:
+ SetDlgItemText(hwndDlg, IDC_LENGTH_TYPE, WASABI_API_LNGSTRINGW(IDS_ITEMS));
+ plLengthType = PL_ITEMS; // Set to # of items
+ SetPlLengthTypeComboToItems(hwndDlg, plItems);
+ break;
+ case IDC_RADIO_PLAYLIST_LENGTH:
+ SetDlgItemText(hwndDlg, IDC_LENGTH_TYPE, WASABI_API_LNGSTRINGW(IDS_MINUTES));
+ plLengthType = PL_MINUTES; // Set to minutes
+ SetPlLengthTypeComboToMinutes(hwndDlg, plMinutes);
+ break;
+ case IDC_RADIO_PLAYLIST_SIZE:
+ SetDlgItemText(hwndDlg, IDC_LENGTH_TYPE, WASABI_API_LNGSTRINGW(IDS_MEGABYTES));
+ plLengthType = PL_MEGABYTES; // Set to megabytes
+ SetPlLengthTypeComboToMegabytes(hwndDlg, plMegabytes);
+ break;
+ case IDC_COMBO_LENGTH:
+ {
+ UpdateComboLength(hwndDlg);
+ }
+ break;
+ case IDC_BUTTON_OPTIONS:
+ ToggleOptions(false);
+ break;
+ case IDC_BUTTON_PLAY_NOW:
+ playPlaylist(currentPlaylist, false, 0, /*seed,*/ useSeed); // Play the current playlist taking the seed track into consideration
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); // Close up the dialog because we are done
+ break;
+ case IDC_BUTTON_ENQUEUE_NOW:
+ playPlaylist(currentPlaylist, true, 0, /*seed,*/ useSeed); // Enqueue the current playlist taking the seed track into consideration
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); // Close up the dialog because we are done
+ break;
+ case IDC_BUTTON_SAVEAS:
+ {
+ // ToDo spawn a dialog to save the current playlist as a ML playlist
+ int save_result = WASABI_API_DIALOGBOXPARAM(IDD_ADD_PLAYLIST, hwndDlg, AddPlaylistDialogProc, (LPARAM)&seedPlaylist/*seed*/);
+ if (save_result == IDOK) // If the user accepted that playlist dialog then go ahead and close up everything
+ {
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); // Close up the dialog because we are done
+ }
+ }
+ break;
+ case IDC_BUTTON_ML_QUERY:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_EDIT_ML_QUERY, temp, sizeof(temp) - 1); // Retreive the current custom ML query
+
+ ml_editview meq = {hwndDlg, (temp[0] == 0) ? DEFAULT_ML_QUERY : temp, "ML Query", -1}; // Create the editview
+ meq.name = WASABI_API_LNGSTRING(IDS_ML_QUERY); // Set a custom title
+ if(!(int)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (LPARAM)&meq, ML_IPC_EDITVIEW))
+ return 0; // Spawn the edit view
+ SetDlgItemTextA(hwndDlg, IDC_EDIT_ML_QUERY, meq.query); // Set the text back to the edited query
+ }
+ break;
+ case IDC_BUTTON_RESTORE_QUERY_DEFAULT:
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_ML_QUERY, _T(DEFAULT_ML_QUERY)); // Set the text back to the edited query
+ break;
+ case IDC_CHECK_USE_SEED:
+ useSeed = IsDlgButtonChecked(hwndDlg,IDC_CHECK_USE_SEED);
+ UpdateStats(); // Update the track stats, because the seed status can change them
+ RedrawWindow(GetDlgItem(hwndDlg,IDC_LIST_RESULTS2), 0, 0, RDW_INVALIDATE); // Refresh the colors in the list view
+ break;
+ case IDC_CHECK_MULTIPLE_ARTISTS: // Set the multiple tracks per artist option when checked
+ multipleArtists = IsDlgButtonChecked(hwndDlg, IDC_CHECK_MULTIPLE_ARTISTS);
+ break;
+ case IDC_CHECK_MULTIPLE_ALBUMS: // Set the multiple tracks per album option when checked
+ multipleAlbums = IsDlgButtonChecked(hwndDlg, IDC_CHECK_MULTIPLE_ALBUMS);
+ break;
+ case IDC_CHECK_ML_QUERY:
+ SetMLQueryCheckState(hwndDlg, IsDlgButtonChecked(hwndDlg, IDC_CHECK_ML_QUERY));
+ break;
+ case IDC_EDIT_ML_QUERY:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_ML_QUERY, mlQuery, MAX_ML_QUERY_SIZE); // Set the text back to the edited query
+ break;
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if(((LPNMHDR)lParam)->code == HDN_BEGINTRACKW || ((LPNMHDR)lParam)->code == HDN_BEGINTRACKA ||
+ ((LPNMHDR)lParam)->code == HDN_ITEMCHANGINGW || ((LPNMHDR)lParam)->code == HDN_ITEMCHANGINGA)
+ {
+ LPNMHEADER pNMHeader = (LPNMHEADER)lParam;
+ if(pNMHeader->iItem == DIALOG_HIDDEN_COLUMN_ID)
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)TRUE);
+ return TRUE;
+ }
+ }
+ else if(((LPNMHDR)lParam)->code == NM_CUSTOMDRAW) // Notify for List View custom redraw (seed track colors)
+ {
+#if defined(_WIN64)
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG)CustomDrawListViewColors(lParam));
+#else
+ SetWindowLong(hwndDlg, DWL_MSGRESULT, (LONG)CustomDrawListViewColors(lParam));
+#endif
+ return TRUE;
+ }
+
+ {
+ const int controls[] =
+ {
+ IDC_LIST_RESULTS2,
+ };
+ if (WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, msg, wParam, lParam, controls, ARRAYSIZE(controls)) != FALSE)
+ {
+ return TRUE;
+ }
+ }
+ break;
+ case WM_DESTROY:
+ {
+ if (WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(GetDlgItem(hwndDlg, IDC_LIST_RESULTS2), FALSE);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int AddResultListItem(Playlist *playlist, int index, int position, bool seed)
+{
+ const unsigned int MAX_INFO = 256;
+ wchar_t filename[MAX_INFO] = {0};
+ wchar_t info[MAX_INFO] = {0};
+ wchar_t *seedText = 0;
+ LVITEMW lvi={LVIF_TEXT, position, 0};
+
+ playlist->GetItem(index,filename,MAX_INFO);
+
+ // Add the title column
+ playlist->GetItemTitle(index, info, MAX_INFO);
+ lvi.pszText=info;
+ lvi.cchTextMax=sizeof(info) / sizeof(*info);
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_INSERTITEMW,0,(LPARAM)&lvi);
+
+ // Add the length column
+ int length = playlist->GetItemLengthMilliseconds(index);
+ if (length <= 0)
+ StringCchCopyW(info, MAX_INFO, WASABI_API_LNGSTRINGW(IDS_UNKNOWN));
+ else
+ FormatToMinutesAndSeconds(length / 1000, info, MAX_INFO); // / 1000 because we have it in milliseconds and not seconds
+
+ lvi.pszText=info;
+ lvi.cchTextMax=sizeof(info) / sizeof(*info);
+ lvi.iSubItem = 1;
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_SETITEMW,0,(LPARAM)&lvi);
+
+ // Add the size column
+ int size = playlist->GetItemSizeBytes(index);
+ if (size <= 0)
+ StringCchCopyW(info, MAX_INFO, WASABI_API_LNGSTRINGW(IDS_UNKNOWN));
+ else
+ StrFormatByteSizeW(size, info, MAX_INFO);
+ lvi.pszText=info;
+ lvi.cchTextMax=sizeof(info) / sizeof(*info);
+ lvi.iSubItem = 2;
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_SETITEMW,0,(LPARAM)&lvi);
+
+ // Add the seed track column
+ if (seed == true)
+ seedText = WASABI_API_LNGSTRINGW(IDS_YES);
+ else
+ seedText = WASABI_API_LNGSTRINGW(IDS_NO);
+ lvi.pszText=seedText;
+ lvi.cchTextMax=sizeof(seedText) / sizeof(*seedText);
+ lvi.iSubItem = 3;
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_SETITEMW,0,(LPARAM)&lvi);
+
+ return 0;
+}
+
+void CantPopulateResults(void)
+{
+ wchar_t message[256] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_EXCUSE_ME, message, 256);
+ MessageBoxW(hwndDlgCurrent, message, WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PLAYLIST_GENERATOR), MB_OK | MB_ICONINFORMATION);
+}
+
+void PopulateResults(Playlist *playlist)
+{
+ // Add all of the seed tracks to the listview
+ int listLength = (playlist) ? (int)playlist->GetNumItems() : 0;
+ int seedLength = (int)seedPlaylist.GetNumItems();
+ for (int i = 0; i < seedLength; i++)
+ {
+ AddResultListItem(&seedPlaylist, i, i, true);
+ }
+
+ // Add all of the generated tracks to the listview
+ for (int i = 0; i < listLength; i++)
+ {
+ AddResultListItem(playlist, i, seedLength + i, false);
+ }
+
+ // After we are done populating the data then we can size the columns accordingly
+ HWND hwndlist = GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2);
+ ListView_SetColumnWidth(hwndlist,0,(listLength ? LVSCW_AUTOSIZE : LVSCW_AUTOSIZE_USEHEADER));
+ ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,2,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,3,LVSCW_AUTOSIZE_USEHEADER);
+
+ // Refresh the playlist stats
+ UpdateStats();
+
+ // Change the progress status to read done 'generated'
+ SetDlgItemText(hwndDlgCurrent,IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_DONE));
+
+ SetMarqueeProgress(false); // Turn the marquee off because we are actually generating the tracks
+
+ //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), TRUE );
+ SetButtonsEnabledState(true); // Renable the buttons
+
+}
diff --git a/Src/Plugins/Library/ml_plg/gn_logo_88x83.bmp b/Src/Plugins/Library/ml_plg/gn_logo_88x83.bmp
new file mode 100644
index 00000000..bae676e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/gn_logo_88x83.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_plg/impl_playlist.cpp b/Src/Plugins/Library/ml_plg/impl_playlist.cpp
new file mode 100644
index 00000000..4183a583
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/impl_playlist.cpp
@@ -0,0 +1,328 @@
+#include "main.h"
+#include "impl_playlist.h"
+#include <algorithm>
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../Winamp/strutil.h"
+#include <shlwapi.h>
+
+void Playlist::Clear()
+{
+ for ( pl_entry *entry : entries )
+ delete entry;
+
+ entries.clear();
+}
+
+void Playlist::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInBytes, ifc_plentryinfo *info )
+{
+ entries.push_back( new pl_entry( filename, title, lengthInMS, sizeInBytes, info ) );
+}
+
+void Playlist::AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInBytes )
+{
+ entries.push_back( new pl_entry( filename, title, lengthInMS, sizeInBytes ) );
+}
+
+Playlist::~Playlist()
+{
+ Clear();
+}
+
+
+size_t Playlist::GetNumItems()
+{
+ return entries.size();
+}
+
+size_t Playlist::GetItem( size_t item, wchar_t *filename, size_t filenameCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetFilename( filename, filenameCch );
+}
+
+size_t Playlist::GetItemTitle( size_t item, wchar_t *title, size_t titleCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetTitle( title, titleCch );
+}
+
+const wchar_t *Playlist::ItemTitle( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filetitle;
+}
+
+const wchar_t *Playlist::ItemName( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filename;
+}
+
+int Playlist::GetItemLengthMilliseconds( size_t item )
+{
+ if ( item >= entries.size() )
+ return -1;
+
+ return entries[ item ]->GetLengthInMilliseconds();
+}
+
+int Playlist::GetItemSizeBytes( size_t item )
+{
+ if ( item >= entries.size() )
+ return -1;
+
+ return entries[ item ]->GetSizeInBytes();
+}
+
+size_t Playlist::GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetExtendedInfo( metadata, info, infoCch );
+}
+
+uint64_t Playlist::GetPlaylistSizeBytes( void )
+{
+ uint64_t size = 0;
+
+ for ( pl_entry *l_entry : entries )
+ size += l_entry->GetSizeInBytes();
+
+ return size;
+}
+
+uint64_t Playlist::GetPlaylistLengthMilliseconds( void )
+{
+ uint64_t length = 0;
+
+ for ( pl_entry *l_entry : entries )
+ length += l_entry->GetLengthInMilliseconds();
+
+
+ return length;
+}
+
+
+
+int Playlist::Reverse()
+{
+ // TODO: keep a bool flag and just do size-item-1 every time a GetItem* function is called
+ std::reverse( entries.begin(), entries.end() );
+
+ return PLAYLIST_SUCCESS;
+}
+
+int Playlist::Swap( size_t item1, size_t item2 )
+{
+ std::swap( entries[ item1 ], entries[ item2 ] );
+
+ return PLAYLIST_SUCCESS;
+}
+
+class RandMod
+{
+ public:
+ RandMod( int ( *_generator )( ) ) : generator( _generator ) {}
+ int operator()( int n ) { return generator() % n; }
+ int ( *generator )( );
+};
+
+int Playlist::Randomize( int ( *generator )( ) )
+{
+ RandMod randMod( generator );
+ std::random_shuffle( entries.begin(), entries.end(), randMod );
+
+ return PLAYLIST_SUCCESS;
+}
+
+void Playlist::Remove( size_t item )
+{
+ delete entries[ item ];
+ entries.erase( entries.begin() + item );
+}
+
+void Playlist::SetItemFilename( size_t item, const wchar_t *filename )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetFilename( filename );
+}
+
+void Playlist::SetItemTitle( size_t item, const wchar_t *title )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetTitle( title );
+}
+
+void Playlist::SetItemLengthMilliseconds( size_t item, int length )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetLengthMilliseconds( length );
+}
+
+void Playlist::SetItemSizeBytes( size_t item, int size )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetSizeBytes( size );
+}
+
+void GetTitle( pl_entry *&a )
+{
+ if ( !a->cached )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length = -1;
+ mediaLibrary.GetFileInfo( a->filename, title, FILETITLE_SIZE, &length );
+ a->SetLengthMilliseconds( length * 1000 );
+ a->SetTitle( title );
+ }
+}
+static bool PlayList_sortByTitle( pl_entry *&a, pl_entry *&b )
+{
+ GetTitle( a );
+ GetTitle( b );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE /*|NORM_IGNOREKANATYPE*/ | NORM_IGNOREWIDTH, a->filetitle, -1, b->filetitle, -1 );
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return CompareStringLogical(a.strTitle, b.strTitle)<0;
+}
+
+static bool PlayList_sortByFile( pl_entry *&a, pl_entry *&b ) //const void *a, const void *b)
+{
+ const wchar_t *file1 = PathFindFileNameW( a->filename );
+ const wchar_t *file2 = PathFindFileNameW( b->filename );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE |*/ NORM_IGNOREWIDTH, file1, -1, file2, -1 );
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return FileCompareLogical(file1, file2)<0;
+}
+
+static bool PlayList_sortByDirectory( pl_entry *&a, pl_entry *&b ) // by dir, then by title
+{
+ const wchar_t *directory1 = a->filename;
+ const wchar_t *directory2 = b->filename;
+
+ const wchar_t *directoryEnd1 = scanstr_backcW( directory1, L"\\", 0 );
+ const wchar_t *directoryEnd2 = scanstr_backcW( directory2, L"\\", 0 );
+
+ size_t dirLen1 = directoryEnd1 - directory1;
+ size_t dirLen2 = directoryEnd2 - directory2;
+
+ if ( !dirLen1 && !dirLen2 ) // both in the current directory?
+ return PlayList_sortByFile( a, b ); // not optimized, because the function does another scanstr_back, but easy for now :)
+
+ if ( !dirLen1 ) // only the first dir is empty?
+ return true; // sort it first
+
+ if ( !dirLen2 ) // only the second dir empty?
+ return false; // empty dirs go first
+
+#if 0 // TODO: grab this function from winamp
+ int comp = FileCompareLogicalN( directory1, dirLen1, directory2, dirLen2 );
+ if ( comp == 0 )
+ return PlayList_sortByFile( a, b );
+ else
+ return comp < 0;
+#endif
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE | */NORM_IGNOREWIDTH, directory1, (int)dirLen1, directory2, (int)dirLen2 );
+ if ( comp == CSTR_EQUAL ) // same dir
+ return PlayList_sortByFile( a, b ); // do second sort
+ else // different dirs
+ return comp == CSTR_LESS_THAN;
+}
+
+int Playlist::SortByTitle()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByTitle );
+
+ return 1;
+}
+
+int Playlist::SortByFilename()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByFile );
+
+ return 1;
+}
+
+int Playlist::SortByDirectory()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByDirectory );
+
+ return 1;
+}
+/*
+int Playlist::Move(size_t itemSrc, size_t itemDest)
+{
+ if (itemSrc < itemDest)
+ std::rotate(&entries[itemSrc], &entries[itemSrc], &entries[itemDest]);
+ else
+ if (itemSrc > itemDest)
+ std::rotate(&entries[itemDest], &entries[itemSrc], &entries[itemSrc]);
+ return 1;
+}*/
+
+bool Playlist::IsCached( size_t item )
+{
+ return entries[ item ]->cached;
+}
+
+void Playlist::ClearCache( size_t item )
+{
+ entries[ item ]->cached = false;
+}
+
+void Playlist::InsertPlaylist( Playlist &copy, size_t index )
+{
+ for ( pl_entry *l_entry : copy.entries )
+ {
+ entries.insert( entries.begin() + index, l_entry );
+ ++index;
+ }
+
+ copy.entries.clear();
+}
+
+void Playlist::AppendPlaylist( Playlist &copy )
+{
+ entries.insert( entries.end(), std::make_move_iterator( copy.entries.begin() ), std::make_move_iterator( copy.entries.end() ) );
+ //for ( pl_entry *l_entry : copy.entries )
+ // entries.push_back( l_entry );
+
+ copy.entries.clear();
+}
+
+
+#define CBCLASS Playlist
+START_MULTIPATCH;
+START_PATCH( patch_playlist )
+M_VCB( patch_playlist, ifc_playlist, IFC_PLAYLIST_CLEAR, Clear )
+//M_VCB(patch_playlist, ifc_playlist, IFC_PLAYLIST_APPENDWITHINFO, AppendWithInfo)
+//M_VCB(patch_playlist, ifc_playlist, IFC_PLAYLIST_APPEND, Append)
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETNUMITEMS, GetNumItems )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEM, GetItem )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMTITLE, GetItemTitle )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMEXTENDEDINFO, GetItemExtendedInfo )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_REVERSE, Reverse )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SWAP, Swap )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_RANDOMIZE, Randomize )
+M_VCB( patch_playlist, ifc_playlist, IFC_PLAYLIST_REMOVE, Remove )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYTITLE, SortByTitle )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYFILENAME, SortByFilename )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYDIRECTORY, SortByDirectory )
+NEXT_PATCH( patch_playlistloadercallback )
+M_VCB( patch_playlistloadercallback, ifc_playlistloadercallback, IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile );
+END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/impl_playlist.h b/Src/Plugins/Library/ml_plg/impl_playlist.h
new file mode 100644
index 00000000..589146af
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/impl_playlist.h
@@ -0,0 +1,65 @@
+#ifndef NULLSOFT_ML_PLG_IMPL_PLAYLIST_H
+#define NULLSOFT_ML_PLG_IMPL_PLAYLIST_H
+
+#include "../playlist/ifc_playlist.h"
+#include <vector>
+#include <windows.h> // for MAX_PATH
+#include "../playlist/pl_entry.h"
+#include <bfc/multipatch.h>
+#include <bfc/platform/types.h>
+#include "../playlist/ifc_playlistloadercallback.h"
+
+enum
+{
+ patch_playlist,
+ patch_playlistloadercallback
+};
+
+class Playlist : public MultiPatch<patch_playlist, ifc_playlist>, public MultiPatch<patch_playlistloadercallback, ifc_playlistloadercallback>
+{
+public:
+ ~Playlist();
+
+ void Clear();
+ void OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInKB, ifc_plentryinfo *info );
+ void AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInBytes );
+
+ size_t GetNumItems();
+
+ size_t GetItem( size_t item, wchar_t *filename, size_t filenameCch );
+ size_t GetItemTitle( size_t item, wchar_t *title, size_t titleCch );
+ const wchar_t *ItemTitle( size_t item );
+ const wchar_t *ItemName( size_t item );
+ int GetItemLengthMilliseconds( size_t item );
+ int GetItemSizeBytes( size_t item );
+ size_t GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch );
+ uint64_t GetPlaylistSizeBytes( void );
+ uint64_t GetPlaylistLengthMilliseconds( void );
+
+ bool IsCached( size_t item );
+ void ClearCache( size_t item );
+
+ void SetItemFilename( size_t item, const wchar_t *filename );
+ void SetItemTitle( size_t item, const wchar_t *title );
+ void SetItemLengthMilliseconds( size_t item, int length );
+ void SetItemSizeBytes( size_t item, int size );
+
+ int Reverse();
+ int Swap( size_t item1, size_t item2 );
+ int Randomize( int ( *generator )( ) );
+ void Remove( size_t item );
+
+ int SortByTitle();
+ int SortByFilename();
+ int SortByDirectory(); //sorts by directory and then by filename
+
+ void InsertPlaylist( Playlist &copy, size_t index );
+ void AppendPlaylist( Playlist &copy );
+
+ typedef std::vector<pl_entry*> PlaylistEntries;
+ PlaylistEntries entries;
+
+protected:
+ RECVS_MULTIPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/main.h b/Src/Plugins/Library/ml_plg/main.h
new file mode 100644
index 00000000..6a47e5d4
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/main.h
@@ -0,0 +1,87 @@
+#ifndef NULLSOFT_ML_PLG_MAIN_H
+#define NULLSOFT_ML_PLG_MAIN_H
+#include <windows.h>
+#include "playlist.h"
+#include "../../General/gen_ml/ml.h"
+#include "IDScanner.h"
+#include "api__ml_plg.h"
+#include "../winamp/wa_ipc.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/threadpool/api_threadpool.h"
+#include <api/service/waservicefactory.h>
+#include "impl_playlist.h"
+
+#define DEFAULT_ML_QUERY "playcount = \"0\" OR lastplay < [1 month ago] AND rating != \"1\" AND rating != \"2\""
+#define MAX_ML_QUERY_SIZE 8192
+#define MAX_TITLE_SIZE 512
+
+extern winampMediaLibraryPlugin plugin;
+
+//extern int plLength;
+extern int plItems;
+extern int plMinutes;
+extern int plMegabytes;
+extern int plLengthType;
+extern int multipleArtists;
+extern int multipleAlbums;
+extern int useSeed;
+
+extern int useMLQuery;
+//extern wchar_t *customMLQuery;
+extern wchar_t mlQuery[];
+extern Playlist seedPlaylist;
+extern bool isGenerating;
+
+extern IDScanner scanner;
+
+extern ThreadID *plg_thread;
+extern bool reset_db_flag;
+extern bool run_full_scan_flag;
+extern volatile bool run_pass2_flag;
+
+extern HWND hwndDlgCurrent;
+
+INT_PTR CALLBACK PrefsProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK GenerateProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK ViewProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK BGScanProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK AddPlaylistDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+// util.cpp
+int GetFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *dest, int len);
+int updateFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *data);
+void WriteFileInfo(const wchar_t *filename);
+
+void Pass1(int *killswitch);
+void Pass2(int *killswitch);
+
+bool StartScan();
+void StopScan();
+
+int ShutdownScanner(HANDLE handle, void *user_data, intptr_t id);
+int ResetDBOnThread(bool silent); // Goes onto the plg dedicated thread when called
+int ResetDB(bool silent); // For calling functions that are already on the plg dedicated thread
+int NukeDB(void); // For nuking the DB old skool (deleting all the files by force)
+
+
+// ml_plg.cpp
+//void SongSelected(const wchar_t * fn, HWND parent);
+void MultipleInstancesWarning(void);
+HWND SongsSelected(void);
+void WriteSettingsToIni(HWND hwndDlg);
+
+// Dialog manipulation methods
+// prefs.cpp & generate.cpp
+void ShowErrorDlg(HWND parent);
+void SetPlLengthTypeComboToItems(HWND hwndDlg, int value);
+void SetPlLengthTypeComboToMinutes(HWND hwndDlg, int value);
+void SetPlLengthTypeComboToMegabytes(HWND hwndDlg, int value);
+int SetRadioControlsState(HWND hwndDlg);
+void BoldStatusText(HWND hwndDlg);
+void PopulateResults(Playlist *playlist);
+void CantPopulateResults(void);
+void SetMarqueeProgress(bool isMarquee);
+void SetButtonsEnabledState(bool enabled_flag);
+BOOL windowOffScreen(HWND hwnd, POINT pt);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.cpp b/Src/Plugins/Library/ml_plg/ml_plg.cpp
new file mode 100644
index 00000000..da519db3
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.cpp
@@ -0,0 +1,719 @@
+#define PLUGIN_VER L"1.81"
+#define FORCED_REBUILD_VERSION 1 // When changing this be sure to update 'forcedRebuildVersion' logic if no breaking changes are introduced in the new version
+
+#include "api__ml_plg.h"
+#include "../../General/gen_ml/ml.h"
+#include "resource.h"
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/ipc_pe.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/ns_wc.h"
+#include <api/service/waservicefactory.h>
+#include "playlist.h"
+#include "../Agave/Language/api_language.h"
+#include "resource.h"
+//#include "mldbcallbacks.h"
+#include "../../General/gen_ml/menufucker.h"
+#include "../nu/ServiceWatcher.h"
+//#include "api_playlist_generator.h"
+#include "../nu/Singleton.h"
+#include "PlaylistGeneratorApi.h"
+
+
+//#include "wacmldbcallbacks.h"
+#include <strsafe.h> // make sure this always gets #include'd last
+
+// For the playlist generator API
+static PlaylistGeneratorAPI playlistGeneratorAPI;
+static SingletonServiceFactory<api_playlist_generator, PlaylistGeneratorAPI> playlistGeneratorFactory;
+
+
+api_threadpool *WASABI_API_THREADPOOL = 0;
+api_application *WASABI_API_APP = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMGR = 0;
+api_language *WASABI_API_LNG = 0;
+api_config *AGAVE_API_CONFIG=0;
+api_gracenote *AGAVE_API_GRACENOTE=0;
+api_decodefile *AGAVE_API_DECODE=0;
+api_metadata *AGAVE_API_METADATA=0;
+api_mldb *AGAVE_API_MLDB = 0;
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+api_stats *AGAVE_API_STATS = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+api_syscb *WASABI_API_SYSCB=0;
+
+class MLDBWatcher : public ServiceWatcherSingle
+{
+public:
+ void OnDeregister()
+ {
+ StopScan();
+ }
+};
+
+static MLDBWatcher mldbWatcher;
+
+extern winampMediaLibraryPlugin plugin;
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+static HWND winampPlaylist;
+
+static int ML_IPC_MENUFUCKER_BUILD;
+static int ML_IPC_MENUFUCKER_RESULT;
+
+bool pluginEnabled = true;
+int scanMode = 0; // 0 = not inited, 1 = on start, 2 = on use
+int plLengthType = PL_ITEMS; // 0 = not inited, 1 = items (# of), 2 = length (minutes), 3 = size (kilobytes)
+//int plLength = 20; // Length of playlist, used for either # of items or for minutes target of the playlist
+int plItems = 20; // Number of desired items in the playlist
+int plMinutes = 60; // Number of desired minutes in the playlist
+int plMegabytes = 650; // Size of the desired playlist in kilobytes
+int multipleArtists = FALSE; // Generate tracks from the same artists in a single playlist
+int multipleAlbums = FALSE; // Generate tracks from the same albums in a single playlist
+int useSeed = TRUE; // Put the seed track into the generated playlist
+int useMLQuery = FALSE; // Use a custom query against the media library database to post process the results
+wchar_t mlQuery[MAX_ML_QUERY_SIZE] = {0}; // Storage for the custom query
+int forcedRebuildVersion = 0; // Stores the ID of a forced rebuild when upgrading, 0 is never reset, 1 reset with ml_plg rewrite, 2+ and on are reserved for future scenarios
+
+ThreadID *plg_thread=0; // Thread ID for the single gracenote thread that we always make API calls on.
+bool reset_db_flag = false; // Flag that gets set whenever the DB needs to be reset before a scan.
+bool run_full_scan_flag = true; // Flag that gets set whenever there are media library changes so step 4 (pass 2) can be rerun for any changed files
+volatile bool run_pass2_flag = false; // Flag that gets set whenever there are media library changes so step 4 (pass 2) can be rerun for any changed files
+
+
+void WriteIntToIni(const char *key, const int value)
+{
+ char buf[32] = {0};
+ _itoa(value, buf, 10);
+ WritePrivateProfileStringA("ml_plg", key, buf, mediaLibrary.GetWinampIni());
+}
+
+// BE CAREFULL! Using this could potentially internationalize floats on some versions of windows eg. '1,6' instead of '1.6'
+void WriteFloatToIni(const char *key, const float value)
+{
+ char buf[32] = {0};
+ StringCchPrintfA(buf, 32, "%.2f", value);
+ WritePrivateProfileStringA("ml_plg", key, buf, mediaLibrary.GetWinampIni());
+}
+
+int Init()
+{
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ ServiceBuild(WASABI_API_SYSCB, syscbApiServiceGuid);
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(AGAVE_API_PLAYLISTMGR, api_playlistmanagerGUID);
+ ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
+ ServiceBuild(AGAVE_API_DECODE, decodeFileGUID);
+ ServiceBuild(AGAVE_API_GRACENOTE, gracenoteApiGUID);
+ ServiceBuild(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceBuild(AGAVE_API_PLAYLISTS, api_playlistsGUID);
+ ServiceBuild(AGAVE_API_STATS, AnonymousStatsGUID);
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ playlistGeneratorFactory.Register(plugin.service, &playlistGeneratorAPI);
+
+ if (WASABI_API_THREADPOOL)
+ plg_thread = WASABI_API_THREADPOOL->ReserveThread(api_threadpool::FLAG_REQUIRE_COM_STA);
+
+ if (!plg_thread)
+ return ML_INIT_FAILURE; // if we weren't able to get a thread from the threadpool, bail out
+
+ // no guarantee that AGAVE_API_MLDB will be available yet, so we'll start a watcher for it
+ mldbWatcher.WatchWith(plugin.service);
+ mldbWatcher.WatchFor(&AGAVE_API_MLDB, mldbApiGuid);
+ WASABI_API_SYSCB->syscb_registerCallback(&mldbWatcher);
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlPlgLangGUID);
+
+ ML_IPC_MENUFUCKER_BUILD = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_build", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ ML_IPC_MENUFUCKER_RESULT = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_result", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ winampPlaylist = (HWND)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,IPC_GETWND_PE,IPC_GETWND);
+
+ static wchar_t szDescription[256];
+ StringCchPrintf(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PLAYLIST_GENERATOR), PLUGIN_VER);
+ plugin.description = (char*)szDescription;
+
+ // Load variables from winamp.ini
+ scanMode = GetPrivateProfileInt(L"ml_plg", L"scanmode", 0, mediaLibrary.GetWinampIniW());
+ pluginEnabled = GetPrivateProfileInt(L"ml_plg", L"enable", 1, mediaLibrary.GetWinampIniW())!=0;
+ multipleArtists = GetPrivateProfileInt(L"ml_plg", L"multipleArtists", multipleArtists, mediaLibrary.GetWinampIniW());
+ multipleAlbums = GetPrivateProfileInt(L"ml_plg", L"multipleAlbums", multipleAlbums, mediaLibrary.GetWinampIniW());
+
+ plLengthType = GetPrivateProfileInt(L"ml_plg", L"plLengthType", plLengthType, mediaLibrary.GetWinampIniW());
+ plItems = GetPrivateProfileInt(L"ml_plg", L"plItems", plItems, mediaLibrary.GetWinampIniW());
+ plMinutes = GetPrivateProfileInt(L"ml_plg", L"plMinutes", plMinutes, mediaLibrary.GetWinampIniW());
+ plMegabytes = GetPrivateProfileInt(L"ml_plg", L"plMegabytes", plMegabytes, mediaLibrary.GetWinampIniW());
+ useSeed = GetPrivateProfileInt(L"ml_plg", L"useSeed", useSeed, mediaLibrary.GetWinampIniW());
+ useMLQuery = GetPrivateProfileInt(L"ml_plg", L"useMLQuery", useMLQuery, mediaLibrary.GetWinampIniW());
+
+ char temp[MAX_ML_QUERY_SIZE] = {0};
+ GetPrivateProfileStringA("ml_plg", "mlQuery", DEFAULT_ML_QUERY, temp, sizeof(temp), mediaLibrary.GetWinampIni());
+ MultiByteToWideCharSZ(CP_UTF8, 0, temp, -1, mlQuery, sizeof(mlQuery)/sizeof(mlQuery[0]));
+ //GetPrivateProfileStringA("ml_plg", "forcedRebuildVersion", "", temp, sizeof(temp), mediaLibrary.GetWinampIni());
+ //forcedRebuildVersion = (float)atof(temp);
+ forcedRebuildVersion = GetPrivateProfileIntA("ml_plg","forcedRebuildVersion", forcedRebuildVersion, mediaLibrary.GetWinampIni());
+
+ // Here we check if the person is upgrading from the old ml_plg, if that value is less than our current version then we need to force a rebuild
+ if (forcedRebuildVersion < FORCED_REBUILD_VERSION/*atof(PLUGIN_VER)*/) // NOTE: Hard code this to a version if no breaking changes were made
+ { // Otherwise there will be a forced reset every time version is incremented
+ reset_db_flag = true;
+ //ResetDBOnThread(true);
+ }
+ forcedRebuildVersion = FORCED_REBUILD_VERSION; //(float)atof(PLUGIN_VER);
+
+ if(scanMode == 1) // If scanmode is set to rescan on winamp launch
+ WASABI_API_CREATEDIALOGPARAMW(IDD_NAG, plugin.hwndWinampParent, BGScanProcedure, 1); // 1 means silent!
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ StopScan();
+ HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, ShutdownScanner, (void *)wait_event, 0, api_threadpool::FLAG_REQUIRE_COM_STA);
+ WaitForSingleObject(wait_event, INFINITE);
+
+ mldbWatcher.StopWatching();
+ WASABI_API_THREADPOOL->ReleaseThread(plg_thread);
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(AGAVE_API_PLAYLISTMGR, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
+ ServiceRelease(AGAVE_API_DECODE, decodeFileGUID);
+ ServiceRelease(AGAVE_API_GRACENOTE, gracenoteApiGUID);
+ ServiceRelease(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceRelease(AGAVE_API_MLDB, mldbApiGuid);
+ ServiceRelease(AGAVE_API_PLAYLISTS, api_playlistsGUID);
+ ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID);
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ playlistGeneratorFactory.Deregister(plugin.service);
+ //WASABI_API_SYSCB->syscb_deregisterCallback(&IDscanner);
+}
+
+static void FixAmps(wchar_t *str, size_t len)
+{
+ size_t realSize = 0;
+ size_t extra = 0;
+ wchar_t *itr = str;
+ while (itr && *itr)
+ {
+ if (*itr == L'&')
+ extra++;
+ itr++;
+ realSize++;
+ }
+
+ extra = min(len - (realSize + 1), extra);
+
+ while (extra)
+ {
+ str[extra+realSize] = str[realSize];
+ if (str[realSize] == L'&')
+ {
+ extra--;
+ str[extra+realSize] = L'&';
+ }
+ realSize--;
+ }
+}
+
+static void FixStrForMenu(wchar_t *str, size_t len)
+{
+ FixAmps(str,len);
+}
+
+// Triggered once some seed tracks are selected and added by the user
+HWND SongsSelected(void)
+{
+ // I know this function is a one-liner but it may not be the case forever
+ //WASABI_API_CREATEDIALOG(IDD_GENERATE, GetDesktopWindow(), GenerateProcedure);
+ return WASABI_API_CREATEDIALOGW(IDD_GENERATE, plugin.hwndLibraryParent, GenerateProcedure);
+}
+
+// Display the warning message that the current file is not in ML so it cannot be used as a seed track
+void NotInMLWarning(const wchar_t *filename)
+{
+ wchar_t message[MAX_PATH + 256] = {0};
+ StringCchPrintfW(message, MAX_PATH + 256, WASABI_API_LNGSTRINGW(IDS_CANT_USE_SEED), filename);
+ MessageBoxW(plugin.hwndLibraryParent, message, (LPWSTR)plugin.description, MB_OK| MB_ICONINFORMATION);
+}
+
+void MultipleInstancesWarning(void)
+{
+ MessageBoxW(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_THERE_CAN_BE_ONLY_ONE), (LPWSTR)plugin.description, MB_OK | MB_ICONINFORMATION);
+}
+
+// Add seed tracks from main media library view
+int AddSeedTracks(menufucker_t *mf)
+{
+ const int count = mf->extinf.mediaview.items->Size;
+ int position = ListView_GetNextItem(mf->extinf.mediaview.list, -1, LVNI_SELECTED); // Start the search from -1 so that we dont ignore the 0th selection
+
+ while (position >= 0 && position < count)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ itemRecordW *item = &mf->extinf.mediaview.items->Items[position];
+ if (item)
+ {
+ GetTitleFormattingML(item->filename, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ position = ListView_GetNextItem(mf->extinf.mediaview.list, position, LVNI_SELECTED);
+ }
+
+ return true;
+}
+
+// Add seed tracks from a media library playlist
+int AddSeedTracksMlPlaylist(menufucker_t *mf)
+{
+ int position = ListView_GetNextItem(mf->extinf.mlplaylist.list, -1, LVNI_SELECTED); // Start the search from -1 so that we dont ignore the 0th selection
+
+ while (position >= 0)
+ {
+ wchar_t filename[MAX_PATH] = {0};
+ mf->extinf.mlplaylist.pl->GetItem(position, filename, MAX_PATH);
+ itemRecordW *item = AGAVE_API_MLDB->GetFile(filename);
+ if (item)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ GetTitleFormattingML(item->filename, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ position = ListView_GetNextItem(mf->extinf.mlplaylist.list, position, LVNI_SELECTED);
+ }
+
+ return true;
+}
+
+// Add tracks from the winamp playlist
+int AddSeedTracksPlaylist(menufucker_t *mf, int first_selection)
+{
+ bool isSuccess = true;
+
+ int position = first_selection;
+ while (position >= 0)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ fileinfoW inf={0};
+ inf.index = position;
+
+ SendMessage(winampPlaylist,WM_WA_IPC,IPC_PE_GETINDEXINFOW_INPROC,(LPARAM)&inf);
+ itemRecordW *item = AGAVE_API_MLDB->GetFile(inf.file);
+
+ if (item)
+ {
+ GetTitleFormattingML(inf.file, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ else
+ {
+ NotInMLWarning(inf.file); // Popup to warn that its not in the ML
+ }
+
+ position = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, position, IPC_PLAYLIST_GET_NEXT_SELECTED);
+ }
+
+ if (seedPlaylist.GetNumItems() == 0)
+ isSuccess = false;
+
+ return isSuccess;
+}
+
+// Add a single seed track from the now playing song ticker
+int AddSeedTrack(const wchar_t *filename)
+{
+ bool isSuccess = true;
+
+ if (filename)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ itemRecordW *item = AGAVE_API_MLDB->GetFile(filename);
+ if (item)
+ {
+ GetTitleFormattingML(filename, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ else
+ {
+ NotInMLWarning(filename); // Popup to warn that its not in the ML
+ }
+ }
+ else
+ {
+ NotInMLWarning(filename); // Popup to warn that its not in the ML
+ }
+
+ if (seedPlaylist.GetNumItems() == 0)
+ isSuccess = false;
+
+ return isSuccess;
+}
+
+void WriteSettingsToIni(HWND hwndDlg)
+{
+ /*char buf[32] = {0};
+
+ StringCchPrintfA(buf, 32, "%d", plLengthType);
+ WritePrivateProfileStringA("ml_plg","plLengthType",buf,mediaLibrary.GetWinampIni());*/
+
+ WriteIntToIni("plLengthType", plLengthType);
+ WriteIntToIni("plItems", plItems);
+ WriteIntToIni("plMinutes", plMinutes);
+ WriteIntToIni("plMegabytes", plMegabytes);
+ WriteIntToIni("forcedRebuildVersion", forcedRebuildVersion);
+ //WriteFloatToIni("forcedRebuildVersion", forcedRebuildVersion);
+
+ WriteIntToIni("multipleArtists", multipleArtists);
+ WriteIntToIni("multipleAlbums", multipleAlbums);
+ WriteIntToIni("useSeed", useSeed);
+ WriteIntToIni("useMLQuery", useMLQuery);
+
+ /*multipleArtists = IsDlgButtonChecked(hwndDlg,IDC_CHECK_MULTIPLE_ARTISTS);
+ WritePrivateProfileStringA("ml_plg","multipleArtists",multipleArtists?"1":"0",mediaLibrary.GetWinampIni());
+
+ multipleAlbums = IsDlgButtonChecked(hwndDlg,IDC_CHECK_MULTIPLE_ALBUMS);
+ WritePrivateProfileStringA("ml_plg","multipleAlbums",multipleAlbums?"1":"0",mediaLibrary.GetWinampIni());
+
+ useSeed = IsDlgButtonChecked(hwndDlg,IDC_CHECK_USE_SEED);
+ WritePrivateProfileStringA("ml_plg","useSeed",useSeed?"1":"0",mediaLibrary.GetWinampIni());
+
+ useMLQuery = IsDlgButtonChecked(hwndDlg,IDC_CHECK_ML_QUERY);
+ WritePrivateProfileStringA("ml_plg","useMLQuery", useMLQuery ? "1" : "0",mediaLibrary.GetWinampIni());*/
+
+ //WritePrivateProfileStringW(L"ml_plg",L"mlQuery", mlQuery ,mediaLibrary.GetWinampIniW());
+ WritePrivateProfileStringA("ml_plg", "mlQuery", AutoChar(mlQuery, CP_UTF8), mediaLibrary.GetWinampIni());
+}
+
+static bool IsInternetAvailable()
+{
+ return !!SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_INETAVAILABLE);
+}
+
+INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ static int mymenuid=0;
+
+ if(message_type == ML_IPC_MENUFUCKER_BUILD && pluginEnabled && IsInternetAvailable())
+ {
+ menufucker_t* mf = (menufucker_t*)param1;
+ wchar_t str[100] = {0}, str2[64] = {0};
+ MENUITEMINFOW mii =
+ {
+ sizeof(MENUITEMINFOW),
+ MIIM_TYPE | MIIM_ID,
+ MFT_STRING,
+ MFS_ENABLED,
+ (UINT)mf->nextidx,
+ 0
+ };
+
+ mymenuid = mf->nextidx;
+ mf->nextidx++;
+
+ if(mf->type == MENU_MEDIAVIEW)
+ {
+ int n = ListView_GetSelectionMark(mf->extinf.mediaview.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ itemRecordW * ice = &mf->extinf.mediaview.items->Items[n];
+ if(!ice->title || !ice->title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ int len = lstrlenW(ice->title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", ice->title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), ice->title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+
+ if(!InsertMenuItem(mf->menu,0xdeadbeef,FALSE,&mii))
+ {
+ InsertMenuItem(mf->menu,40012,FALSE,&mii);
+ mii.wID = 0xdeadbeef;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mf->menu,40012,FALSE,&mii);
+ }
+ }
+ else if(mf->type == MENU_MLPLAYLIST)
+ {
+ int n = ListView_GetSelectionMark(mf->extinf.mlplaylist.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+ wchar_t filename[MAX_PATH] = {0}, title[75] = {0};
+ mf->extinf.mlplaylist.pl->GetItem(n,filename,MAX_PATH);
+ AGAVE_API_METADATA->GetExtendedFileInfo(filename, L"title", title, 75);
+ if(!title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ int len = lstrlenW(title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+ InsertMenuItem(mf->menu,3,TRUE,&mii);
+ mii.wID = 0xdeadc0de;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mf->menu,3,TRUE,&mii);
+ }
+ else if(mf->type == MENU_PLAYLIST)
+ {
+ int n = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,-1,IPC_PLAYLIST_GET_NEXT_SELECTED);
+ if(n == -1) {
+ mymenuid=0;
+ return 0;
+ }
+
+ fileinfoW inf={0};
+ inf.index = n;
+ SendMessage(winampPlaylist,WM_WA_IPC,IPC_PE_GETINDEXINFOW_INPROC,(LPARAM)&inf);
+ wchar_t title[75] = {0};
+ AGAVE_API_METADATA->GetExtendedFileInfo(inf.file, L"title", title, 75);
+ if(!title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+
+ int len = lstrlenW(title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+
+ InsertMenuItem(mf->menu,40470/*40208*/,FALSE,&mii);
+ mii.wID = 0xdeadc0de;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mf->menu,40470/*40208*/,FALSE,&mii);
+ }
+ else if (mf->type == MENU_SONGTICKER)
+ {
+ wchar_t * file = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME);
+ wchar_t title[75] = {0};
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"title", title, 75);
+ if(!title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ int len = lstrlenW(title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+
+ InsertMenuItem(mf->menu,0,TRUE,&mii);
+ }
+ }
+ else if(message_type == ML_IPC_MENUFUCKER_RESULT && mymenuid != 0 && pluginEnabled)
+ {
+ menufucker_t* mf = (menufucker_t*)param1;
+ DeleteMenu(mf->menu,mymenuid,MF_BYCOMMAND);
+ if(mf->type == MENU_PLAYLIST || mf->type == MENU_MLPLAYLIST) DeleteMenu(mf->menu,0xdeadc0de,MF_BYCOMMAND);
+
+ if(param2 == mymenuid && mymenuid != 0)
+ {
+ if(mf->type == MENU_MEDIAVIEW) // Main Media Library View
+ {
+ int n = ListView_GetSelectionMark(mf->extinf.mediaview.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTracks(mf)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ else if(mf->type == MENU_MLPLAYLIST) // Media library playlist view
+ {
+ // Check to see if anything is selected
+ int n = ListView_GetSelectionMark(mf->extinf.mlplaylist.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTracksMlPlaylist(mf)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ else if(mf->type == MENU_PLAYLIST) // Main window playlist
+ {
+ // Check to see if anything is selected
+ int n = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,-1,IPC_PLAYLIST_GET_NEXT_SELECTED);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTracksPlaylist(mf, n)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ else if(mf->type == MENU_SONGTICKER) // Current playing track in the song ticker
+ {
+ wchar_t * file = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME);
+ if (file)
+ {
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTrack(file)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ }
+ }
+ mymenuid=0;
+ }
+ else switch (message_type)
+ {
+ case ML_MSG_CONFIG:
+ {
+ HWND parent = (HWND)param1;
+ WASABI_API_DIALOGBOXW(IDD_PREFS, parent, PrefsProcedure);
+ return TRUE;
+ }
+ break;
+ }
+ return 0;
+}
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_plg.dll)", // name filled in later
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+extern "C"
+{
+ __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // prompt to remove our settings with default as no (just incase)
+ static wchar_t title[256];
+ StringCchPrintf(title, ARRAYSIZE(title),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PLAYLIST_GENERATOR), PLUGIN_VER);
+
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ title,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ WritePrivateProfileStringW(L"ml_plg",0,0,mediaLibrary.GetWinampIniW());
+ }
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.rc b/Src/Plugins/Library/ml_plg/ml_plg.rc
new file mode 100644
index 00000000..0423ed7c
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.rc
@@ -0,0 +1,312 @@
+// 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_PREFS DIALOGEX 0, 0, 329, 228
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Winamp Playlist Generator"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Scanner",IDC_STATIC,5,5,319,163
+ LTEXT "",IDC_BLURB,12,18,305,57
+ CONTROL "",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,12,79,305,14
+ GROUPBOX "Status",IDC_GROUP_STATUS,12,99,305,36
+ LTEXT "Idle",IDC_STATUS,18,112,294,18
+ PUSHBUTTON "Scan",IDC_TEST,254,172,70,14
+ PUSHBUTTON "Reset Database...",IDC_RESETDB,254,191,70,14
+ DEFPUSHBUTTON "Close",IDOK,254,209,70,14
+ CONTROL "Automatically start background scanning when I use this feature",IDC_SCANONUSE,
+ "Button",BS_AUTORADIOBUTTON,12,139,222,10
+ CONTROL "Automatically start background scanning when Winamp launches",IDC_SCANLAUNCH,
+ "Button",BS_AUTORADIOBUTTON,12,152,222,10
+ CONTROL 108,IDC_LOGO,"Static",SS_BITMAP,5,172,59,51
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_FAIL$(DISABLED) DIALOGEX 0, 0, 220, 65
+#else
+IDD_FAIL DIALOGEX 0, 0, 220, 65
+#endif
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_CHILD | WS_CAPTION | WS_SYSMENU
+CAPTION "Playlist Generation"
+FONT 8, "MS Shell Dlg", 400, 0, 0x0
+BEGIN
+ CONTROL 108,IDC_STATIC,"Static",SS_BITMAP,7,7,59,51
+ LTEXT "Generating Playlist...",IDC_STATIC_GENERATING,72,18,68,8
+ PUSHBUTTON "Cancel",IDC_BUTTON_GENERATE_CANCEL,93,44,50,14
+END
+#endif
+
+IDD_GENERATE DIALOGEX 0, 0, 580, 275
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "Generate Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "",IDC_STATIC_STATS,5,7,328,8
+ PUSHBUTTON "Options >>>",IDC_BUTTON_OPTIONS,337,4,60,14
+ CONTROL "",IDC_LIST_RESULTS2,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,5,22,392,194
+ CONTROL 108,IDC_LOGO,"Static",SS_BITMAP,5,220,59,51
+ GROUPBOX "Status",IDC_GROUP_STATUS,69,217,264,36
+ LTEXT "Generating Playlist...",IDC_STATIC_PROGRESS_STATE,75,230,252,18
+ PUSHBUTTON "Save Playlist...",IDC_BUTTON_SAVEAS,337,220,60,14,BS_MULTILINE
+ PUSHBUTTON "Regenerate",IDC_BUTTON_REGENERATE,337,238,60,14
+ CONTROL "",IDC_PROGRESS_GENERATE,"msctls_progress32",WS_BORDER,69,257,136,14
+ PUSHBUTTON "Play Now",IDC_BUTTON_PLAY_NOW,209,257,60,14
+ PUSHBUTTON "Enqueue Now",IDC_BUTTON_ENQUEUE_NOW,273,257,60,14
+ PUSHBUTTON "Close",IDC_BUTTON_CANCEL,337,257,60,14
+ GROUPBOX "Options",IDC_STATIC,403,4,172,267
+ GROUPBOX "Playlist Entries",IDC_GROUP_PL_ENTRIES,409,15,160,68
+ CONTROL "Number of playlist items",IDC_RADIO_PLAYLIST_ITEMS,
+ "Button",BS_AUTORADIOBUTTON,415,28,92,10
+ CONTROL "Playlist length",IDC_RADIO_PLAYLIST_LENGTH,"Button",BS_AUTORADIOBUTTON,415,40,60,10
+ CONTROL "Playlist size",IDC_RADIO_PLAYLIST_SIZE,"Button",BS_AUTORADIOBUTTON,415,52,52,10
+ LTEXT "(up to)",IDC_STATIC_UP_TO,415,67,23,8
+ COMBOBOX IDC_COMBO_LENGTH,441,65,50,92,CBS_DROPDOWN | WS_TABSTOP
+ LTEXT "items",IDC_LENGTH_TYPE,494,67,70,8
+ GROUPBOX "Seed Settings",IDC_GROUP_SEED,409,87,160,55
+ CONTROL "Add seed track to playlist",IDC_CHECK_USE_SEED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,100,97,10
+ CONTROL "Add multiple tracks from the same artist",IDC_CHECK_MULTIPLE_ARTISTS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,114,143,10
+ CONTROL "Add multiple tracks from the same album",IDC_CHECK_MULTIPLE_ALBUMS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,127,145,10
+ GROUPBOX "Library Query",IDC_GROUP_ML_QUERY,409,146,160,120
+ CONTROL "Apply custom Library query:",IDC_CHECK_ML_QUERY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,159,125,10
+ EDITTEXT IDC_EDIT_ML_QUERY,416,173,147,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit ML Query...",IDC_BUTTON_ML_QUERY,416,191,72,14
+ PUSHBUTTON "Restore Default",IDC_BUTTON_RESTORE_QUERY_DEFAULT,492,191,71,14
+ LTEXT "Default Query:\n\t- Never played\n\t\tOR\n\t- Played over 1 month ago\n\t\tbut rated a 3 or above.",IDC_STATIC_QUERY_DESCRIPTION,416,209,147,50
+END
+
+IDD_ADD_PLAYLIST DIALOGEX 0, 0, 230, 44
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,121,25,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,175,25,50,14
+ LTEXT "Name:",IDC_STATIC_NAME,5,7,29,8
+ EDITTEXT IDC_EDIT_NAME,33,5,192,14,ES_AUTOHSCROLL
+END
+
+IDD_NAG DIALOGEX 0, 0, 178, 61
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Scanning in Progress..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Close",IDCANCEL,123,42,50,14
+ CONTROL 108,IDC_STATIC,"Static",SS_BITMAP,5,5,59,51
+ LTEXT "The Winamp Playlist Generator is now scanning new files...",IDC_STATIC,69,5,99,33
+ PUSHBUTTON "Details...",IDC_BUTTON1,69,42,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_PREFS, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 324
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 223
+ END
+
+ "IDD_FAIL$(DISABLED)", DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 213
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 58
+ END
+
+ IDD_GENERATE, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 575
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 271
+ END
+
+ IDD_ADD_PLAYLIST, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 225
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 39
+ END
+
+ IDD_NAG, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 173
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 57
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.K.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_GN_LOGO BITMAP "gn_logo_88x83.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_PLAYLIST_GENERATOR "Nullsoft Playlist Generator v%s"
+ 65535 "{0CE0174D-8334-479e-B322-9D80D48FC74D}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PLAY_TRACKS_SIMILAR_TO "View tracks similar to ""%s"""
+ IDS_SCANNING_NEEDED " (scanning needed)"
+ IDS_SCANNER_BLURB "Give the Winamp Playlist Generator a song that you love and it will pick out a playlist of similar songs. In the mood for some rocking tunes? Just pick the rockingest, then use the ""View tracks similar to"" menu option and we'll sort you out with a selection of great tunes.\n\nBut to do this, we first need to scan your music library. This may take a little while, but it's totally worth it. Click ""Scan"" to begin."
+ IDS_ERROR_INITIALIZING "Error Initializing"
+ IDS_IDLE "Idle"
+ IDS_INITIALIZING "Step 1/4: Initializing%s"
+ IDS_SYNC "Step 2/4: Scanning Local Media database.\n%d of %d files complete%s"
+ IDS_METADATA "Step 3/4: Reading metadata.\n%d of %d files complete%s"
+ IDS_MUSICID "Step 4/4: Analyzing files.\n%d of %d files complete%s"
+ IDS_DONE "Done"
+ IDS_PAUSE "Stop"
+ IDS_SCAN "Scan"
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings for this plug-in?"
+ IDS_INITFAILMSG "Playlist Generator failed to initialize. If you suspect that the database could be corrupt please reset it from the 'Nullsoft Playlist Generator' configuration options."
+ IDS_RESETDB "Reset Database"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RESETDB_TEXT "This will reset the database, the playlist generator will need to scan your whole library again. Proceed?"
+ IDS_MINUTES "minutes"
+ IDS_ITEMS "items"
+ IDS_ARTIST "Artist"
+ IDS_ALBUM "Album"
+ IDS_TRACK "Track"
+ IDS_TITLE "Track Title"
+ IDS_SIZE "Size"
+ IDS_LENGTH "Length"
+ IDS_MEGABYTES "megabytes"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_EXCUSE_ME "Sorry.\nNo tracks could be generated with your current option settings.\nPlease try different settings or different seed track(s). Or try again once the size of your library has increased."
+ IDS_SEED "Seed?"
+ IDS_YES "Yes"
+ IDS_NO " "
+ IDS_OPTIONS "Options <<"
+ IDS_NO_OPTIONS "Options >>"
+ IDS_STATS "Items: [%i] Length: [%s] Size: [%s]"
+ IDS_ML_QUERY "Library Query for Gracenote"
+ IDS_ENTER_A_NAME "Please enter a name."
+ IDS_ERROR "Error"
+ IDS_PL_NAME_PREFIX "GP - "
+END
+
+STRINGTABLE
+BEGIN
+ IDS_GENERATING "Generating a playlist..."
+ IDS_CANT_USE_SEED "Sorry, but the file '%s' cannot be used as a seed track because it is not in the Library."
+ IDS_UNKNOWN "Unknown"
+ IDS_THERE_CAN_BE_ONLY_ONE
+ "Please close the current playlist generator window before opening a new one."
+ IDS_DAYS "days"
+ IDS_DAY "day"
+ IDS_ERROR_RESET "Failed to reset the Gracenote DB. Please restart Winamp and try to reset again."
+ IDS_CANNOT_SHUT_DOWN "Unexpected Error. Cannot shut down the playlist generator engine."
+END
+
+#endif // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.sln b/Src/Plugins/Library/ml_plg/ml_plg.sln
new file mode 100644
index 00000000..57b4aa31
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_plg", "ml_plg.vcxproj", "{4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}"
+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
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|Win32.Build.0 = Debug|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|x64.ActiveCfg = Debug|x64
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|x64.Build.0 = Debug|x64
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|Win32.ActiveCfg = Release|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|Win32.Build.0 = Release|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|x64.ActiveCfg = Release|x64
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C70730C8-94A1-4059-8966-8FD679D682D9}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.vcxproj b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj
new file mode 100644
index 00000000..d97866f3
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj
@@ -0,0 +1,331 @@
+<?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>{4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}</ProjectGuid>
+ <RootNamespace>ml_plg</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)'=='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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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)'=='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|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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <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>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(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>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>
+ </Link>
+ <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\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\playlist\plstring.cpp" />
+ <ClCompile Include="..\..\..\playlist\pl_entry.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="AddPlaylist.cpp" />
+ <ClCompile Include="AlbumID.cpp" />
+ <ClCompile Include="generate.cpp" />
+ <ClCompile Include="IDScanner.cpp" />
+ <ClCompile Include="impl_playlist.cpp" />
+ <ClCompile Include="ml_plg.cpp" />
+ <ClCompile Include="pass1.cpp" />
+ <ClCompile Include="pass2.cpp" />
+ <ClCompile Include="playlist.cpp" />
+ <ClCompile Include="PlaylistGeneratorAPI.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="util.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\ComboBox.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\sort.h" />
+ <ClInclude Include="..\..\..\playlist\plstring.h" />
+ <ClInclude Include="..\..\..\playlist\pl_entry.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="api__ml_plg.h" />
+ <ClInclude Include="api_playlist_generator.h" />
+ <ClInclude Include="IDScanner.h" />
+ <ClInclude Include="impl_playlist.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="playlist.h" />
+ <ClInclude Include="PlaylistGeneratorAPI.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="gn_logo_88x83.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_plg.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_plg/ml_plg.vcxproj.filters b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj.filters
new file mode 100644
index 00000000..8ca6cc4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj.filters
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="AddPlaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumID.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="generate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="IDScanner.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="impl_playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_plg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pass1.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pass2.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistGeneratorAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\playlist\pl_entry.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\playlist\plstring.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistGeneratorAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="impl_playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="IDScanner.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_plg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_playlist_generator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ComboBox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\playlist\plstring.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\sort.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\playlist\pl_entry.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{5c57af84-bce1-4236-9a99-d6af4b649140}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{8c2d8c3b-a651-4079-87b0-6631b6b0cced}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{79665634-6d8e-44ea-9748-ce739b474314}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{f007a32d-06d5-409a-9574-daaa6d1dacbd}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_plg.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="gn_logo_88x83.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/pass1.cpp b/Src/Plugins/Library/ml_plg/pass1.cpp
new file mode 100644
index 00000000..c1240519
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/pass1.cpp
@@ -0,0 +1,101 @@
+#include "main.h"
+#include "playlist.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+#include "api__ml_plg.h"
+
+static long PlaylistManagerCount()
+{
+ ICddbPL2FindDataPtr pFindData;
+ ICddbDisc2Ptr pDisc;
+ long count =0;
+ HRESULT hr = pFindData.CreateInstance(CLSID_CddbPL2FindData);
+ if (FAILED(hr))
+ return count;
+
+ // empty FindData iterator means ALL FILES
+ hr = playlistMgr->FindOpen(pFindData);
+ if (FAILED(hr))
+ return count;
+
+ while (SUCCEEDED(playlistMgr->FindNext(pFindData, &pDisc)))
+ {
+ count++;
+ }
+ playlistMgr->FindClose(pFindData);
+ return count;
+
+}
+/*
+Pass 1 Algorithm
+
+Find all files with gnpl_crit_field_xdev1 == "1"
+for each:
+GetFileInfo "GracenoteFileID"
+if success
+{
+xdev1 = "2"
+GetFileInfo "GracenoteExtData"
+if success
+xdev1 = "done"
+}
+else
+xdev="3"
+*/
+
+void IDScanner::Pass1()
+{
+ // benski> this function REALLY SUCKS but there's not much we can do about it unfortunately.
+ // because Gracenote's Playlist SDK doesn't give us a good way to run queries like this
+
+ ICddbPL2FindDataPtr pFindData;
+ ICddbDisc2Ptr pDisc;
+
+ HRESULT hr;
+ filesTotal = PlaylistManagerCount(); // super slow, but hey this whole function is slow, anyway
+
+ hr = pFindData.CreateInstance(CLSID_CddbPL2FindData);
+ if (FAILED(hr))
+ return ;
+
+ // empty FindData iterator means ALL FILES
+ hr = playlistMgr->FindOpen(pFindData);
+ if (FAILED(hr))
+ return;
+
+ while (SUCCEEDED(playlistMgr->FindNext(pFindData, &pDisc)))
+ {
+ if (killswitch)
+ break;
+ CComBSTR path,filespec;
+ wchar_t file[MAX_PATH] = {0};
+ CComBSTR phase;
+
+ pDisc->GetProperty(PROP_Default, PLM_Filename, &filespec);
+ pDisc->GetProperty(PROP_Default, PLM_Pathname, &path);
+ PathCombineW(file, path, filespec);
+ playlistMgr->FileGetFieldVal(file, gnpl_crit_field_xdev1, &phase);
+ if (phase && phase[0] && phase[0]=='1')
+ {
+ wchar_t gracenoteFileId[256]=L"";
+ if (GetFileInfo(file, L"GracenoteFileID", gracenoteFileId, 256) && gracenoteFileId[0])
+ {
+ wchar_t gracenoteExtData[65536]=L"";
+ GetFileInfo(file, L"GracenoteExtData", gracenoteExtData, 65536);
+
+ // write back to Media Library database (since if we got here, it wasn't in the itemRecordList)
+ AGAVE_API_MLDB->SetField(file, "GracenoteFileID", gracenoteFileId);
+ if (gracenoteExtData[0]) AGAVE_API_MLDB->SetField(file, "GracenoteExtData", gracenoteExtData);
+
+ SetGracenoteData(file, gracenoteFileId, gracenoteExtData);
+ playlistMgr->FileSetFieldVal(file, gnpl_crit_field_xdev1, L"0"); // mark as done!
+ }
+ else // no Tag ID, so we'll have to use AlbumID
+ {
+ playlistMgr->FileSetFieldVal(file, gnpl_crit_field_xdev1, L"2"); // move to phase 2
+ }
+ }
+ filesComplete++;
+ }
+ playlistMgr->FindClose(pFindData);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/pass2.cpp b/Src/Plugins/Library/ml_plg/pass2.cpp
new file mode 100644
index 00000000..6a4f10b2
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/pass2.cpp
@@ -0,0 +1,72 @@
+#include "IDScanner.h"
+#include "playlist.h"
+#include <atlbase.h>
+#include "main.h"
+#include "api__ml_plg.h"
+
+static ICddbFileInfoList *CreateScanList(volatile int *killswitch)
+{
+ // benski> this function REALLY SUCKS but there's not much we can do about it unfortunately.
+ // because Gracenote's Playlist SDK doesn't give us a good way to run queries like this
+ ICddbPL2FindDataPtr pFindData;
+ ICddbDisc2Ptr pDisc;
+
+ if (FAILED(pFindData.CreateInstance(CLSID_CddbPL2FindData)))
+ return 0;
+
+ if (FAILED(playlistMgr->FindOpen(pFindData)))
+ return 0;
+
+ ICddbFileInfoListPtr infoList;
+ infoList.CreateInstance(CLSID_CddbFileInfoList);
+
+ while (SUCCEEDED(playlistMgr->FindNext(pFindData, &pDisc)))
+ {
+ if (*killswitch)
+ return 0;
+ CComBSTR path,filespec;
+ wchar_t file[MAX_PATH] = {0};
+ CComBSTR phase;
+
+ pDisc->GetProperty(PROP_Default, PLM_Filename, &filespec);
+ pDisc->GetProperty(PROP_Default, PLM_Pathname, &path);
+ PathCombineW(file, path, filespec);
+ playlistMgr->FileGetFieldVal(file, gnpl_crit_field_xdev1, &phase);
+ if (!phase || !phase[0] || phase[0]!='2')
+ continue;
+
+ ICddbFileInfoPtr info;
+ info.CreateInstance(CLSID_CddbFileInfo);
+ info->put_Filename(file);
+ infoList->AddFileInfo(info);
+ }
+
+ playlistMgr->FindClose(pFindData);
+ infoList->AddRef();
+ return infoList;
+}
+
+
+/*
+Pass 2 Algorithm
+Find all files with gnpl_crit_field_xdev1 == "2" and run them through MusicID
+*/
+void IDScanner::Pass2()
+{
+ if (SetupMusicID())
+ {
+ ICddbFileInfoList *infoList = CreateScanList(&killswitch);
+ if (infoList)
+ {
+ musicID->LibraryID(infoList, MUSICID_RETURN_EXACT_ONLY | MUSICID_GET_FP_FROM_APP | MUSICID_GET_TAG_FROM_APP | MUSICID_PREFER_WF_MATCHES);
+ infoList->Release();
+ }
+ }
+}
+
+int IDScanner::Pass2OnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ IDScanner *scanner = (IDScanner *)id;
+ scanner->Pass2();
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_plg/playlist.cpp b/Src/Plugins/Library/ml_plg/playlist.cpp
new file mode 100644
index 00000000..ac459ba3
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/playlist.cpp
@@ -0,0 +1,635 @@
+#include "playlist.h"
+#include <shlwapi.h>
+#include "main.h"
+#include "api__ml_plg.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "impl_playlist.h"
+//#import "../gracenote/CDDBControlWinamp.dll" no_namespace, named_guids, raw_interfaces_only
+#include "../gracenote/cddbcontrolwinamp.tlh"
+#include "../winamp/ipc_pe.h"
+#include "resource.h"
+
+#include <strsafe.h>
+
+//extern Playlist currentPlaylist;
+ICddbPlaylist25Mgr *playlistMgr;
+ICddbMLDBManager *mldbMgr;
+
+Playlist currentPlaylist;
+Playlist seedPlaylist;
+
+class PlaylistEventHandler : public DPlaylist2Events
+{
+ STDMETHODIMP STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObject)
+ {
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, __uuidof(DPlaylist2Events)))
+ *ppvObject = (DPlaylist2Events *)this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef(void)
+ {
+ return 1;
+ }
+
+ ULONG STDMETHODCALLTYPE Release(void)
+ {
+ return 0;
+ }
+
+ HRESULT STDMETHODCALLTYPE Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+ {
+ switch (dispid)
+ {
+ case 1:
+ break;
+ case 2:
+ break;
+ case 3:
+ break;
+ case 4:
+ break;
+ case 5:
+ break;
+
+ }
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+ {
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR * pctinfo)
+ {
+ return E_NOTIMPL;
+ }
+
+};
+
+static IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid)
+{
+ if (!punk)
+ return 0;
+
+ IConnectionPointContainer *pcpc;
+ IConnectionPoint *pcp = 0;
+
+ HRESULT hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **) & pcpc);
+ if (SUCCEEDED(hr))
+ {
+ pcpc->FindConnectionPoint(riid, &pcp);
+ pcpc->Release();
+ }
+ return pcp;
+}
+
+
+static PlaylistEventHandler events;
+static DWORD m_dwCookie = 0;
+//static DWORD m_dwCookie_MldbMgr = 0;
+bool SetupPlaylistSDK()
+{
+ if (!playlistMgr)
+ {
+ //playlistMgr = AGAVE_API_GRACENOTE->GetPlaylistManager();
+ //AGAVE_API_GRACENOTE->GetPlaylistManagerWithMLDBManager(&playlistMgr, &mldbMgr);
+ AGAVE_API_GRACENOTE->GetPlaylistManager(&playlistMgr, &mldbMgr);
+
+ IConnectionPoint *icp = GetConnectionPoint(playlistMgr, DIID_DPlaylist2Events);
+ if (icp)
+ {
+ icp->Advise(static_cast<IDispatch *>(&events), &m_dwCookie);
+ icp->Release();
+ }
+ }
+
+ return !!playlistMgr;
+}
+
+void ShutdownPlaylistSDK()
+{
+ if (playlistMgr)
+ {
+ if (mldbMgr)
+ {
+ mldbMgr->Detach(playlistMgr); // Detach the mldb manager from the playlist manager if it is not null
+ }
+
+ IConnectionPoint *icp = GetConnectionPoint(playlistMgr, DIID_DPlaylist2Events);
+ if (icp)
+ {
+ icp->Unadvise(m_dwCookie);
+ icp->Release();
+ }
+
+ if (mldbMgr)
+ mldbMgr->Release(); // Release the mldb manager if its not null
+
+ playlistMgr->Shutdown();
+ playlistMgr->Release();
+ }
+ mldbMgr=0;
+ playlistMgr=0;
+}
+
+// Caller must cleanup the BSTR
+BSTR SetAndCreatePath(/*wchar_t *path_to_create,*/ const wchar_t *node)
+{
+ wchar_t path_to_create[MAX_PATH] = {0};
+
+ BSTR bPath = 0;
+
+ PathCombineW(path_to_create, WASABI_API_APP->path_getUserSettingsPath(), L"Plugins");
+ CreateDirectoryW(path_to_create, 0);
+ PathAppendW(path_to_create, node);
+ CreateDirectoryW(path_to_create, 0);
+
+ bPath = SysAllocString(path_to_create);
+ return bPath;
+ // modified path as return value
+}
+
+// IMPORTANT: Make sure to call this on the gracenote dedicated thread.
+int RestoreGracenoteMLDB(void)
+{
+ long restoreFlags = PL_MLDB_RESTORE_BASE | PL_MLDB_RESTORE_INDEX;
+ //wchar_t backupPath[MAX_PATH] = {0};
+ BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH);
+ BSTR bBackupPath = SetAndCreatePath(GRACENOTE_DB_BACKUP_PATH);
+
+ // Restore the db files.
+ mldbMgr->RestoreDBFiles(restoreFlags, bDataPath, bBackupPath);
+
+ SysFreeString(bDataPath);
+ SysFreeString(bBackupPath);
+ return NErr_Success;
+}
+
+// Backs up the gracenote MLDB so that it can be restored on corruption
+// IMPORTANT: Make sure to call this on the gracenote dedicated thread.
+int BackupGracenoteMLDB(void)
+{
+ long backupFlags = PL_MLDB_BACKUP_BASE | PL_MLDB_BACKUP_INDEX;
+ //wchar_t backupPath[MAX_PATH] = {0};
+ BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH);
+ BSTR bBackupPath = SetAndCreatePath(GRACENOTE_DB_BACKUP_PATH);
+
+ // Backup the db files.
+ mldbMgr->BackupDBFiles(backupFlags, bDataPath, bBackupPath);
+
+ SysFreeString(bDataPath);
+ SysFreeString(bBackupPath);
+ return NErr_Success;
+}
+
+/*BOOL DeleteGracenoteFile(char *filename)
+{
+ BOOL result;
+ char path[MAX_PATH] = {0};
+ //PathCombineA(path,mediaLibrary.GetIniDirectory(),"Plugins\\Gracenote");
+ PathCombineA(path,"C:\\Users\\bigg\\AppData\\Roaming\\Winamp\\","Plugins\\Gracenote");
+ PathAppendA(path, filename);
+ result = DeleteFileA(path);
+ return result;
+}*/
+
+void CheckForResetError(HRESULT error)
+{
+ if (error != S_OK)
+ {
+ MessageBoxW(0, WASABI_API_LNGSTRINGW(IDS_ERROR_RESET), (LPWSTR)plugin.description, MB_OK| MB_ICONERROR);
+ }
+}
+
+void CheckForShutdownError(HRESULT error)
+{
+ if (error != S_OK)
+ {
+ MessageBoxW(plugin.hwndWinampParent, WASABI_API_LNGSTRINGW(IDS_CANNOT_SHUT_DOWN), (LPWSTR)plugin.description, MB_OK| MB_ICONERROR);
+ }
+}
+
+// Deprecated: Currently not used, remove at some point
+/*
+INT_PTR CALLBACK ResettingProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ //ShowWindow(hwndDlg,SW_HIDE);
+ ShowWindow(hwndDlg, SW_SHOW);
+ return 0;
+ break;
+ case WM_QUIT:
+ EndDialog(hwndDlg,0);
+ return 0;
+ break;
+ }
+ return 0;
+}*/
+
+// IMPORTANT: Make sure to call this on the gracenote dedicated thread.
+int DeleteGracenoteMLDB(bool silent)
+{
+ long deleteFlags = PL_MLDB_DELETE_INDEX | PL_MLDB_DELETE_BASE | PL_MLDB_DELETE_OTHER | PL_MLDB_DELETE_BACKUPS;
+ //long deleteFlags = PL_MLDB_DELETE_INDEX | PL_MLDB_DELETE_BASE | PL_MLDB_DELETE_OTHER; // | PL_MLDB_DELETE_BACKUPS;
+ BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH);
+
+ HRESULT error = 0;
+
+ if (playlistMgr)
+ error = playlistMgr->Shutdown();
+
+ if (!silent)
+ CheckForShutdownError(error);
+
+ // Spawn the working window
+ //HWND hwndResetWorking = WASABI_API_CREATEDIALOG(IDD_NAG, plugin.hwndWinampParent, ResettingProcedure);
+ if (mldbMgr)
+ error = mldbMgr->DeleteDBFiles(deleteFlags, bDataPath);
+ //SendMessage(hwndResetWorking, WM_QUIT, 0, 0);
+
+ if (!silent)
+ CheckForResetError(error);
+
+ SysFreeString(bDataPath);
+ return NErr_Success; // NOT the HRESULT so that non zero values return as false
+}
+
+int InitializeMLDBManager(void)
+{
+ // Other initializations for the MLDB manager can go here
+ ///BackupGracenoteMLDB();
+
+ return NErr_Success;
+}
+
+static void ConfigureGeneratorPrefs(ICddbPLMoreLikeThisCfg *config)
+{
+ // ToDo: (BigG) Consider using some of the different algorithms
+ // GNPL_MORELIKETHIS_ALG_20 (Playlist SDK 2.0)
+ // GNPL_MORELIKETHIS_ALG_25 (Playlist SDK 2.5)
+ // GNPL_MORELIKETHIS_ALG_DSP_1 (Playlist SDK 2.6)
+ // GNPL_MORELIKETHIS_ALG_DSP_25 (Playlist SDK 2.6)
+
+ config->put_Algorithm(GNPL_MORELIKETHIS_ALG_DEFAULT);
+ if(!multipleAlbums) config->put_MaxPerAlbum(1);
+ if(!multipleArtists) config->put_MaxPerArtist(1);
+ //config->put_TrackLimit(plLength);
+ config->put_TrackLimit(0); // Dont put a limit on gracenote (return all tracks)
+}
+
+//void playPlaylist(Playlist &pl, bool enqueue=false, int startplaybackat=0/*-1 for don't start playback*/, const wchar_t *seedfn=NULL, int useSeed=FALSE)
+void playPlaylist(Playlist &pl, bool enqueue=false, int startplaybackat=0/*-1 for don't start playback*/, int useSeed=FALSE)
+{
+ extern winampMediaLibraryPlugin plugin;
+
+ const size_t number_of_seeds = seedPlaylist.GetNumItems();
+ wchar_t seedFilename[MAX_PATH] = {0};
+ seedFilename[0] = 0;
+
+ if(!enqueue)
+ mediaLibrary.ClearPlaylist();
+
+ // Enqueue the seed tracks first
+ if(useSeed)
+ {
+ for (size_t i = 0; i < number_of_seeds; i++)
+ {
+ seedPlaylist.GetItem(i, seedFilename, MAX_PATH); // Get the playlist filename
+
+ enqueueFileWithMetaStructW s={seedFilename,NULL,PathFindExtensionW(seedFilename),-1};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+ }
+
+ size_t listLength = pl.GetNumItems();
+ for(size_t i=0; i<listLength; i++)
+ {
+ wchar_t filename[MAX_PATH] = {0};
+ pl.GetItem(i,filename,MAX_PATH);
+
+ //if(seedfn && !_wcsicmp(seedfn,filename)) // Not really sure this is necessary... just making sure that the same file doesnt get added twice
+ // continue;
+ enqueueFileWithMetaStructW s={filename,NULL,PathFindExtensionW(filename),-1};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+
+ if(!enqueue && startplaybackat != -1)
+ { //play item startplaybackat
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startplaybackat,IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play
+ }
+}
+
+
+// Tests wether an item can be added to a playlist and still stay under the current target
+bool PlaylistIsFull(Playlist *playlist, /*uint64_t*/ unsigned int currentItemLength, /*uint64_t*/ unsigned int currentItemSize)
+{
+#define POWER_2_TO_20 1048576 // 1024 * 1024
+ uint64_t measurement = 0;
+
+ // See what type we care about items, size, or length
+ switch (plLengthType)
+ {
+ case PL_ITEMS: // Check for number of items
+ {
+ measurement = currentPlaylist.GetNumItems() + ( (useSeed) ? seedPlaylist.GetNumItems() : 0 ); // Add the data from the seed track if we are using it
+
+ if ( (measurement + 1) > plItems ) // See if an extra item will put us over.
+ return true;
+ else
+ return false;
+ }
+ break;
+
+ case PL_MINUTES: // Check for minutes used
+ {
+ measurement = currentPlaylist.GetPlaylistLengthMilliseconds() + ( (useSeed) ? seedPlaylist.GetPlaylistLengthMilliseconds() : 0 );
+
+ if ( (measurement + (currentItemLength) ) > (plMinutes * 60 * 1000) )
+ return true;
+ else
+ return false;
+ }
+ break;
+ case PL_MEGABYTES: // Check for megabytes used
+ {
+ measurement = currentPlaylist.GetPlaylistSizeBytes() + ( (useSeed) ? seedPlaylist.GetPlaylistSizeBytes() : 0 );
+
+ if ( (measurement + (uint64_t)currentItemSize) > ((uint64_t)plMegabytes * POWER_2_TO_20) )
+ return true;
+ else
+ return false;
+ }
+ break;
+ }
+
+ return true;
+}
+
+bool MatchesQuery(const wchar_t *filename, const wchar_t *user_query)
+{
+ // Get an item that mathces both the filename and the query
+ itemRecordW *result = AGAVE_API_MLDB->GetFileIf(filename, user_query);
+
+ if (result)
+ {
+ AGAVE_API_MLDB->FreeRecord(result); // Clean up the records list since we are done using it
+ return true;
+ }
+ else
+ {
+ AGAVE_API_MLDB->FreeRecord(result); // Clean up the records list since we are done using it
+ return false;
+ }
+}
+
+
+// Callback for getting a tag for gracenote library items
+static wchar_t * TitleTagFuncGracenote(const wchar_t * tag, void * p)
+{ //return 0 if not found, -1 for empty tag
+ //tagItem * s = (tagItem *)p;
+ //wchar_t *filename = (wchar_t *)p;
+ ICddbPL2Result *gracenoteResult = (ICddbPL2Result *)p;
+
+ BSTR tag_data;
+
+ if (!_wcsicmp(tag, L"artist")) gracenoteResult->GetArtist(&tag_data)/*wsprintf(buf,L"%s",L"artist")*/;
+ else if (!_wcsicmp(tag, L"album")) gracenoteResult->GetAlbum(&tag_data);
+ else if (!_wcsicmp(tag, L"title")) gracenoteResult->GetTitle(&tag_data);
+ else if (!_wcsicmp(tag, L"filename")) gracenoteResult->GetFilename(&tag_data);
+ else
+ return 0;
+
+ //else if (!_wcsicmp(tag, L"genre")) -;
+ //else if (!_wcsicmp(tag, L"year")) -;
+ //else if (!_wcsicmp(tag, L"tracknumber")) -;
+ //else if (!_wcsicmp(tag, L"discnumber")) -;
+ //else if (!_wcsicmp(tag, L"bitrate")) -;
+
+ //else if (!_wcsicmp(tag, L"albumartist")) -;
+ //else if (!_wcsicmp(tag, L"composer")) -;
+ //else if (!_wcsicmp(tag, L"publisher")) -;
+
+ return tag_data;
+}
+
+// Callback for getting a tag for media library items
+static wchar_t * TitleTagFuncML(const wchar_t * tag, void * p)
+{ //return 0 if not found, -1 for empty tag
+ itemRecordW *mlResult = (itemRecordW *)p;
+
+ if (!mlResult)
+ return 0; // Return 0 because we dont have this ml object
+
+ wchar_t buf[128] = {0};
+
+ wchar_t *tag_data = 0;
+
+ if (!_wcsicmp(tag, L"artist")) tag_data = mlResult->artist;
+ else if (!_wcsicmp(tag, L"album")) tag_data = mlResult->album;
+ else if (!_wcsicmp(tag, L"title")) tag_data = mlResult->title;
+ else if (!_wcsicmp(tag, L"filename")) tag_data = mlResult->filename;
+ else if (!_wcsicmp(tag, L"genre")) tag_data = mlResult->genre;
+ else if (!_wcsicmp(tag, L"year")) if (mlResult->year > 0) { StringCchPrintfW(buf, 128, L"%04d", mlResult->year); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) if (mlResult->track > 0) { StringCchPrintfW(buf, 128, L"%04d", mlResult->track); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"discnumber")) if (mlResult->disc > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->disc); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"bitrate")) if (mlResult->bitrate > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->bitrate); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"albumartist")) tag_data = mlResult->albumartist;
+ else if (!_wcsicmp(tag, L"composer")) tag_data = mlResult->composer;
+ else if (!_wcsicmp(tag, L"publisher")) tag_data = mlResult->publisher;
+ else if (!_wcsicmp(tag, L"bpm")) if (mlResult->bpm > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->bpm); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"comment")) tag_data = mlResult->comment;
+ else if (!_wcsicmp(tag, L"discs")) if (mlResult->discs > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->discs); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"filesize")) if (mlResult->filesize > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->filesize); tag_data = buf; }
+ //else if (!_wcsicmp(tag, L"filetime")) tag_data = mlResult->filetime;
+ else if (!_wcsicmp(tag, L"length")) if (mlResult->length > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->length); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"playcount")) if (mlResult->playcount > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->playcount); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"rating")) if (mlResult->rating > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->rating); tag_data = buf; }
+ else
+ return 0;
+
+ return _wcsdup(tag_data);
+}
+
+// Callback to free a tag in gracenote library
+static void TitleTagFreeFuncGracenote(wchar_t *tag_data, void *p)
+{
+ if(tag_data)
+ SysFreeString(tag_data);
+}
+
+// Callback to free a tag in media library
+static void TitleTagFreeFuncML(wchar_t *tag_data, void *p)
+{
+ if(tag_data)
+ free(tag_data);
+}
+
+// Retreive the title formatting for gracenote library
+void GetTitleFormattingGracenote(const wchar_t *filename, ICddbPL2Result * gracenoteResult, wchar_t * buf, int len)
+{
+ waFormatTitleExtended fmt={ filename, 1, NULL, (void *)gracenoteResult, buf, len, TitleTagFuncGracenote, TitleTagFreeFuncGracenote };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+// Retreive the title formatting for media library
+void GetTitleFormattingML(const wchar_t *filename, itemRecordW *mlResult, wchar_t * buf, int len)
+{
+ waFormatTitleExtended fmt={ filename, 1, NULL, (void *)mlResult, buf, len, TitleTagFuncML, TitleTagFreeFuncML };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+static int MoreLikeTheseSongsFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ Playlist *pl = (Playlist *)user_data;
+ const int number_of_files = (int)pl->GetNumItems();
+
+ isGenerating = true;
+
+ //Sleep(501); // Wait for the update timer to finish its cycles, This is EVIL, keep it removed...
+ SetMarqueeProgress(true); // Turn the marquee off because we are actually generating the tracks
+ SetDlgItemText(hwndDlgCurrent, IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_GENERATING));
+ //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), FALSE );
+ SetButtonsEnabledState(false); // Disable the buttons once we start generating here
+
+ if (SetupPlaylistSDK())
+ {
+ HRESULT hr;
+
+ ICddbPLMoreLikeThisCfgPtr cfg;
+ cfg.CreateInstance(CLSID_CddbPLMoreLikeThisCfg);
+ ConfigureGeneratorPrefs(cfg);
+
+ ICddbPL2ResultListPtr results;
+ wchar_t plFilename[MAX_PATH] = {0};
+
+ if (number_of_files == 1) // Call the regular morelikethis function if there is only the standard seed track
+ {
+ pl->GetItem(0, plFilename, MAX_PATH);
+ BSTR i_dunno = SysAllocString(plFilename);
+ hr=playlistMgr->MoreLikeThisSong(i_dunno, cfg, &results);
+ SysFreeString(i_dunno);
+ }
+ else if (number_of_files > 1) // We have more than 1 seed track
+ {
+ // Create the variant of an array of filenames for MoreLikeTheseSongs
+ VARIANT fileList;
+ VariantInit((VARIANTARG *)&fileList);
+ SAFEARRAY *psa = SafeArrayCreateVector (VT_BSTR, 0, number_of_files);
+ BSTR *data;
+ SafeArrayAccessData(psa, (void **)&data);
+ for (size_t i=0;i!=number_of_files;i++)
+ {
+ pl->GetItem(i, plFilename, MAX_PATH);
+ data[i] = SysAllocString(plFilename);
+ }
+ SafeArrayUnaccessData(psa);
+ V_VT(&fileList) = VT_ARRAY|VT_BSTR;
+ V_ARRAY(&fileList) = psa;
+
+
+ hr=playlistMgr->MoreLikeTheseSongs(fileList, cfg, &results);
+ }
+ else // We dont have any seed tracks (this should not happen because we shouldnt get this far.
+ {
+ return 1; // Failure
+ }
+
+ long count=-1;
+ if (results && SUCCEEDED(hr=results->get_Count(&count)) && count)
+ {
+ currentPlaylist.Clear();
+ for (long i=0;i<count;i++)
+ {
+ ICddbPL2Result *result;
+ if (SUCCEEDED(hr=results->GetResult(i+1, &result)))
+ {
+ BSTR filename = 0;
+ BSTR title = 0;
+
+ unsigned int length = -1;
+ unsigned int size = 0;
+
+ result->GetFilename(&filename);
+ result->GetTitle(&title);
+ result->GetTracklength(&length); // Gracenote is returning seconds here
+ result->GetFilesize(&size); // Gracenote took the size as kilobytes but it is returning it to us as bytes
+
+ length *= 1000; // Multiply length by 1000 to turn it into milliseconds from seconds
+
+ // Get the winamp user formatted title.
+ wchar_t winamp_title[512] = {0};
+ GetTitleFormattingGracenote(filename, result, winamp_title, 512);
+
+ // Only check for the query if the user wants to apply one
+ if ( useMLQuery == TRUE && MatchesQuery(filename, mlQuery ) == false )
+ {
+ SysFreeString(filename);
+ SysFreeString(title);
+ result->Release();
+ continue;
+ }
+
+ // Lets check for the playlist limit to see if we should add this track
+ if ( !PlaylistIsFull(&currentPlaylist, length, size) )
+ currentPlaylist.AppendWithInfo(filename, winamp_title, length, size);
+ // DONT break here if we are full, keep going as we may find something that will fit into the playlist eventually
+
+ SysFreeString(filename);
+ SysFreeString(title);
+ result->Release();
+ }
+ }
+ /*char cfg[1024 + 32] = {0};
+ char *dir = (char*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);
+ PathCombineA(cfg, dir, "Plugins\\gen_ml.ini");
+ int en = GetPrivateProfileIntA("gen_ml_config","enqueuedef",0,cfg);*/
+
+ PopulateResults(&currentPlaylist);
+ }
+ else /*if (count == 0)*/
+ {
+ PopulateResults(0); // Call populate with an empty playlist
+ CantPopulateResults(); // Display warning about not being able to generate any tracks
+ }
+ }
+
+ isGenerating = false;
+
+ return 0;
+}
+
+void MoreLikeTheseSongs(Playlist *pl)
+{
+ // Capture the stats, we dont care if its successful or not, we only care about the try
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::PLG_COUNT);
+
+ // Call the the mor like this fuction on the gracenote reserved thread
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, MoreLikeTheseSongsFunc, pl, 0, api_threadpool::FLAG_REQUIRE_COM_STA);
+}
diff --git a/Src/Plugins/Library/ml_plg/playlist.h b/Src/Plugins/Library/ml_plg/playlist.h
new file mode 100644
index 00000000..5af603ed
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/playlist.h
@@ -0,0 +1,40 @@
+#ifndef NULLSOFT_ML_PLG_PLAYLIST_H
+#define NULLSOFT_ML_PLG_PLAYLIST_H
+
+#include "../gracenote/gracenote.h"
+#include "../../General/gen_ml/ml.h"
+#include <bfc/error.h>
+
+#include "impl_playlist.h"
+
+extern ICddbPlaylist25Mgr *playlistMgr;
+extern ICddbMLDBManager *mldbMgr;
+extern Playlist currentPlaylist;
+bool SetupPlaylistSDK();
+void ShutdownPlaylistSDK();
+int InitializeMLDBManager(void);
+int DeleteGracenoteMLDB(bool silent);
+int BackupGracenoteMLDB(void);
+int RestoreGracenoteMLDB(void);
+void playPlaylist(Playlist &pl, bool enqueue, int startplaybackat, /*const wchar_t *seedfn,*/ int useSeed);
+
+void GetTitleFormattingGracenote(const wchar_t *filename, ICddbPL2Result * gracenoteResult, wchar_t * buf, int len);
+void GetTitleFormattingML(const wchar_t *filename, itemRecordW *mlResult, wchar_t * buf, int len);
+
+void MoreLikeThisSong(const wchar_t *filename);
+void MoreLikeTheseSongs(Playlist *pl);
+
+typedef enum
+{
+ PL_NOT_INITIALIZED = 0,
+ PL_ITEMS = 1,
+ PL_MINUTES = 2,
+ PL_MEGABYTES = 3
+} PlLengthTypeEnum;
+
+#define PLM_Filename L"PLM_rec_filename"
+#define PLM_Pathname L"PLM_rec_pathname"
+#define GRACENOTE_DB_BASE_PATH L"Gracenote"
+#define GRACENOTE_DB_BACKUP_PATH L"Gracenote/Backup"
+
+#endif
diff --git a/Src/Plugins/Library/ml_plg/prefs.cpp b/Src/Plugins/Library/ml_plg/prefs.cpp
new file mode 100644
index 00000000..f8ce2d75
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/prefs.cpp
@@ -0,0 +1,596 @@
+#include "../gracenote/gracenote.h"
+#include "api__ml_plg.h"
+#include <windows.h>
+#include "resource.h"
+#include "../../General/gen_ml/ml.h"
+#include "../winamp/wa_ipc.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/ComboBox.h"
+
+#include "main.h"
+#include <shlwapi.h>
+#include <assert.h>
+#include "playlist.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+#include <strsafe.h>
+
+extern bool pluginEnabled;
+extern int scanMode;
+extern int plLengthType;
+volatile bool is_scan_running=false;
+
+static bool IsScanRunning()
+{
+ return is_scan_running;
+}
+
+void ShowErrorDlg(HWND parent)
+{
+ wchar_t title[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_INITIALIZING,title,32);
+ MessageBox(parent, WASABI_API_LNGSTRINGW(IDS_INITFAILMSG),title,0);
+}
+
+IDScanner scanner;
+
+int ShutdownScanner(HANDLE handle, void *user_data, intptr_t id)
+{
+ HANDLE event = (HANDLE)user_data;
+ scanner.Shutdown();
+ ShutdownPlaylistSDK();
+ SetEvent(event);
+ return 0;
+}
+
+static int ScanThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ is_scan_running=true;
+ scanner.ScanDatabase();
+ is_scan_running=false;
+ return 0;
+}
+
+static void doProgressBar(HWND h, int x, int t=-1) {
+ h = GetDlgItem(h,IDC_PROGRESS1);
+ if(t!=-1 && SendMessage(h,PBM_GETRANGE,0,0) != t)
+ SendMessage(h,PBM_SETRANGE32,0,t);
+ SendMessage(h,PBM_SETPOS,x,0);
+}
+
+static void FillStatus(HWND hwndDlg)
+{
+ long state, track, tracks;
+ if (scanner.GetStatus(&state, &track, &tracks))
+ {
+ static int x=0;
+ wchar_t *ticker;
+ switch (x++)
+ {
+ case 0: ticker=L""; break;
+ case 1: ticker=L"."; break;
+ case 2: ticker=L".."; break;
+ default: ticker=L"...";
+ }
+ x%=4;
+ wchar_t status[1024]=L"";
+ switch (state)
+ {
+ case IDScanner::STATE_ERROR:
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_INITIALIZING,status,1024);
+ KillTimer(hwndDlg, 1);
+ doProgressBar(hwndDlg,0,1);
+ ShowErrorDlg(hwndDlg);
+ break;
+ case IDScanner::STATE_IDLE:
+ WASABI_API_LNGSTRINGW_BUF(IDS_IDLE,status,1024);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_INITIALIZING:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_INITIALIZING), ticker);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_SYNC:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_SYNC), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_METADATA:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_METADATA), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_MUSICID:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_MUSICID), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_DONE:
+ WASABI_API_LNGSTRINGW_BUF(IDS_DONE,status,1024);
+ doProgressBar(hwndDlg,100,100);
+ KillTimer(hwndDlg, 1);
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_SCAN));
+ break;
+ }
+ SetDlgItemTextW(hwndDlg, IDC_STATUS, status);
+ }
+}
+#if 0 // TODO: reimplement contract requirement
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_TIMER)
+ {
+ if (wParam == 0)
+ {
+ KillTimer(hwnd, 0);
+ if (CheckThread())
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER+641, 0, 0);
+ SetTimer(hwnd, 1, 3000, 0);
+ }
+ else
+ DestroyWindow(hwnd);
+ }
+ else if (wParam == 1)
+ {
+ KillTimer(hwnd, 1);
+ PostMessage(plugin.hwndLibraryParent, WM_USER+641, 1, 0);
+ SetTimer(hwnd, 0, 27000, 0);
+ }
+ }
+ else if (uMsg == WM_DESTROY)
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER+641, 1, 0);
+ }
+
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+static bool classRegistered=0;
+HWND CreateDummyWindow()
+{
+ if (!classRegistered)
+ {
+ WNDCLASSW wc = {0, };
+
+ wc.style = 0;
+ wc.lpfnWndProc = DeviceMsgProc;
+ wc.hInstance = plugin.hDllInstance;
+ wc.hIcon = 0;
+ wc.hCursor = NULL;
+ wc.lpszClassName = L"ml_plg_window";
+
+ if (!RegisterClassW(&wc))
+ return 0;
+
+ classRegistered = true;
+ }
+ HWND dummy = CreateWindowW(L"ml_plg_window", L"ml_plg_window", 0, 0, 0, 0, 0, NULL, NULL, plugin.hDllInstance, NULL);
+
+ return dummy;
+}
+#endif
+
+bool StartScan()
+{
+ if (IsScanRunning())
+ {
+ //run_full_scan_flag = true; // Not really necessary since we are already running the scan
+ return false;
+ }
+
+ if (reset_db_flag) // See if we have the flag set to reset the DB.
+ {
+ SetDlgItemTextW(hwndDlgCurrent, IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_RESETDB));
+ NukeDB();
+ //ResetDBOnThread(true);
+ reset_db_flag = false;
+ }
+
+ if (run_full_scan_flag) // See if we have the flag set for running the whole scan
+ {
+ // Call the scan procedure on the reserved gracenote specific thread
+ if (WASABI_API_THREADPOOL->RunFunction(plg_thread, ScanThread, 0, 0, api_threadpool::FLAG_REQUIRE_COM_STA) == 0)
+ {
+ run_full_scan_flag = false; // Reset the flag to false since we (will) complete the run
+ return true;
+ }
+ }
+ else if (run_pass2_flag) // If we have the pass2 scan flag set then run the second pass only
+ {
+ if (WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::Pass2OnThread, 0, (intptr_t)&scanner, api_threadpool::FLAG_REQUIRE_COM_STA) == 0)
+ {
+ run_pass2_flag = false; // Set the flag back to false so we know that we wont have to run it again
+ return true;
+ }
+ }
+
+
+ return false;
+}
+
+void StopScan()
+{
+ if (IsScanRunning())
+ {
+ scanner.Kill();
+ while (IsScanRunning())
+ Sleep(500);
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+ }
+}
+
+void SetPlLengthTypeComboToItems(HWND hwndDlg, int value)
+{
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ combo.Clear();
+ combo.SetItemData(combo.AddString("10"),10);
+ combo.SetItemData(combo.AddString("20"),20);
+ combo.SetItemData(combo.AddString("50"),50);
+ combo.SetItemData(combo.AddString("100"),100);
+ combo.SetItemData(combo.AddString("200"),200);
+ wchar_t buf[32] = {0};
+ _itow(value,buf,10);
+ combo.SetEditText(buf);
+}
+
+void SetPlLengthTypeComboToMinutes(HWND hwndDlg, int value)
+{
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ combo.Clear();
+ combo.SetItemData(combo.AddString("10"),10);
+ combo.SetItemData(combo.AddString("20"),20);
+ combo.SetItemData(combo.AddString("30"),30);
+ combo.SetItemData(combo.AddString("60"),60);
+ combo.SetItemData(combo.AddString("74"),60);
+ combo.SetItemData(combo.AddString("80"),60);
+ combo.SetItemData(combo.AddString("90"),60);
+ combo.SetItemData(combo.AddString("120"),120);
+ wchar_t buf[32] = {0};
+ _itow(value,buf,10);
+ combo.SetEditText(buf);
+}
+
+void SetPlLengthTypeComboToMegabytes(HWND hwndDlg, int value)
+{
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ combo.Clear();
+ combo.SetItemData(combo.AddString("100"),10);
+ combo.SetItemData(combo.AddString("650"),20);
+ combo.SetItemData(combo.AddString("703"),30);
+ combo.SetItemData(combo.AddString("4489"),60);
+ combo.SetItemData(combo.AddString("8147"),120);
+
+ wchar_t buf[32] = {0};
+ _itow(value,buf,10);
+ combo.SetEditText(buf);
+}
+
+// Set a control to bold text and a more bold font
+void BoldStatusText(HWND hwndControl)
+{
+ HFONT hFont ;
+
+ LOGFONT lfFont;
+
+ memset(&lfFont, 0x00, sizeof(lfFont));
+ memcpy(lfFont.lfFaceName, TEXT("Microsoft Sans Serif"), 24);
+
+ lfFont.lfHeight = 14;
+ lfFont.lfWeight = FW_BOLD;
+ lfFont.lfCharSet = ANSI_CHARSET;
+ lfFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lfFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lfFont.lfQuality = DEFAULT_QUALITY;
+
+ // Create the font from the LOGFONT structure passed.
+ hFont = CreateFontIndirect (&lfFont);
+
+ //SendMessageW(GetDlgItem(hwndDlg,IDC_STATUS), WM_SETFONT, (LPARAM)hFont, MAKELONG(TRUE, 0 ) );
+ SendMessageW(hwndControl, WM_SETFONT, (LPARAM)hFont, MAKELONG(TRUE, 0 ) );
+}
+
+void getViewport(RECT *r, HWND wnd, int full, RECT *sr)
+{
+ POINT *p = NULL;
+ if (p || sr || wnd)
+ {
+ HMONITOR hm = NULL;
+ if (sr)
+ hm = MonitorFromRect(sr, MONITOR_DEFAULTTONEAREST);
+ else if (wnd)
+ hm = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
+ else if (p)
+ hm = MonitorFromPoint(*p, MONITOR_DEFAULTTONEAREST);
+
+ if (hm)
+ {
+ MONITORINFOEXW mi;
+ memset(&mi, 0, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+
+ if (GetMonitorInfoW(hm, &mi))
+ {
+ if (!full)
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+ return ;
+ }
+ }
+ }
+ if (full)
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics(SM_CXSCREEN);
+ r->bottom = GetSystemMetrics(SM_CYSCREEN);
+ }
+ else
+ {
+ SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0);
+ }
+}
+
+BOOL windowOffScreen(HWND hwnd, POINT pt)
+{
+ RECT r = {0}, wnd = {0}, sr = {0};
+ GetWindowRect(hwnd, &wnd);
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + (wnd.right - wnd.left);
+ sr.bottom = sr.top + (wnd.bottom - wnd.top);
+ getViewport(&r, hwnd, 0, &sr);
+ return !PtInRect(&r, pt);
+}
+
+#define STATUS_MS 500
+INT_PTR CALLBACK PrefsProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ // this will make sure that we've got thr aacplus logo shown even when using a localised version
+ SendDlgItemMessage(hwndDlg,IDC_LOGO,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(IDB_GN_LOGO),IMAGE_BITMAP,0,0,LR_SHARED));
+
+ SetDlgItemText(hwndDlg,IDC_BLURB,WASABI_API_LNGSTRINGW(IDS_SCANNER_BLURB));
+ if (IsScanRunning())
+ {
+ //EnableWindow(GetDlgItem(hwndDlg, IDC_TEST), FALSE);
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_PAUSE));
+ SetTimer(hwndDlg, 1, STATUS_MS, 0);
+ }
+
+ BoldStatusText(GetDlgItem(hwndDlg, IDC_STATUS) );
+ FillStatus(hwndDlg);
+
+ /*if(!pluginEnabled)
+ CheckDlgButton(hwndDlg,IDC_SCANDISABLE,TRUE);
+ else */
+ if(scanMode == 1)
+ CheckDlgButton(hwndDlg,IDC_SCANLAUNCH,TRUE);
+ else
+ CheckDlgButton(hwndDlg,IDC_SCANONUSE,TRUE);
+
+ SetRadioControlsState(hwndDlg); // Set the playlist length radio buttons state
+
+ if(scanMode == 0 || pluginEnabled == false)
+ {
+ pluginEnabled = true;
+ scanMode = 2;
+ }
+ if(multipleArtists)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ARTISTS,TRUE);
+ if(multipleAlbums)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ALBUMS,TRUE);
+ if(useSeed)
+ CheckDlgButton(hwndDlg,IDC_CHECK_USE_SEED,TRUE);
+
+ // show config window and restore last position as applicable
+ POINT pt = {(LONG)GetPrivateProfileInt(L"ml_plg", L"prefs_x", -1, mediaLibrary.GetWinampIniW()),
+ (LONG)GetPrivateProfileInt(L"ml_plg", L"prefs_y", -1, mediaLibrary.GetWinampIniW())};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_TIMER:
+ FillStatus(hwndDlg);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_TEST:
+ {
+ if(StartScan()) // Start the scanning
+ {
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_PAUSE));
+ //EnableWindow(GetDlgItem(hwndDlg, IDC_TEST), FALSE);
+ SetTimer(hwndDlg, 1, STATUS_MS, 0); // Start the timer ?
+ }
+ else // Stop the scanning
+ {
+ StopScan();
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_SCAN));
+ SetDlgItemText(hwndDlg,IDC_STATUS,WASABI_API_LNGSTRINGW(IDS_PAUSE));
+ }
+ }
+ break;
+ case IDOK:
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_plg", "prefs_x", buf, mediaLibrary.GetWinampIni());
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_plg", "prefs_y", buf, mediaLibrary.GetWinampIni());
+
+ EndDialog(hwndDlg, 0);
+
+ StringCchPrintfA(buf, 10, "%d", scanMode);
+ WritePrivateProfileStringA("ml_plg", "scanmode", buf, mediaLibrary.GetWinampIni());
+ WritePrivateProfileStringA("ml_plg", "enable", pluginEnabled ? "1" : "0", mediaLibrary.GetWinampIni());
+ }
+ break;
+ case IDC_SCANDISABLE:
+ pluginEnabled = false;
+ scanMode = 2;
+ break;
+ case IDC_SCANONUSE:
+ pluginEnabled = true;
+ scanMode = 2;
+ break;
+ case IDC_SCANLAUNCH:
+ pluginEnabled = true;
+ scanMode = 1;
+ break;
+
+ case IDC_RESETDB:
+ {
+ wchar_t title[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_RESETDB,title,32);
+ if(MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_RESETDB_TEXT),title,MB_YESNO) == IDNO)
+ break;
+
+ StopScan();
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_SCAN));
+ ResetDBOnThread(false);
+ //NukeDB();
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+int DeleteGracenoteMLDBOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ HANDLE event = (HANDLE)user_data;
+ SetupPlaylistSDK(); // Need to set up the playlist SDK so that we have the MLDB manager ready to go, which is linked to the playlist manager
+ DeleteGracenoteMLDB(!!id); // This will Shutdown the playlist manager in order to allow for a delete, id is recreating the silent boolean double NOT is creating int -> bool
+ ShutdownPlaylistSDK(); // Hence we need to do a complete and proper shutdown afterwards
+ SetEvent(event);
+ return 0;
+}
+
+// silent - pass in true if the user should not be prompted on errors
+int ResetDBOnThread(bool silent)
+{
+ int silentInt = (silent) ? 1 : 0; // Convert silent to int to pass it to wasabi thread runner
+ /*HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, ShutdownScanner, (void *)wait_event, 0, api_threadpool::FLAG_REQUIRE_COM_STA);
+ WaitForSingleObject(wait_event, INFINITE);*/
+
+ HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, DeleteGracenoteMLDBOnThread, (void *)wait_event, silentInt, api_threadpool::FLAG_REQUIRE_COM_STA);
+ WaitForSingleObject(wait_event, INFINITE);
+
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+
+ return 0;
+}
+
+int ResetDB(bool silent)
+{
+ int silentInt = silent;
+
+ HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ DeleteGracenoteMLDBOnThread (0, (void *)wait_event, silentInt);
+
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+
+ return 0;
+}
+
+BOOL DeleteGracenoteFile(char *filename)
+{
+ BOOL result;
+ char path[MAX_PATH] = {0};
+ PathCombineA(path,mediaLibrary.GetIniDirectory(),"Plugins\\Gracenote");
+ PathAppendA(path, filename);
+ result = DeleteFileA(path);
+ return result;
+}
+
+int NukeDB(void)
+{
+ ShutdownPlaylistSDK();
+
+ DeleteGracenoteFile("cddb.db");
+ DeleteGracenoteFile("cddbplm.chk");
+ DeleteGracenoteFile("cddbplm.gcf");
+ DeleteGracenoteFile("cddbplm.idx");
+ DeleteGracenoteFile("cddbplm.pdb");
+ DeleteGracenoteFile("elists.db");
+
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+
+ return 0;
+}
+
+
+
+INT_PTR CALLBACK BGScanProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+ if(!StartScan())
+ {
+ EndDialog(hwndDlg,0);
+ return 0;
+ }
+ SetTimer(hwndDlg,1,STATUS_MS,NULL);
+ ShowWindow(hwndDlg,SW_HIDE);
+
+ break;
+ case WM_TIMER:
+ switch(wParam)
+ {
+ case 1:
+ {
+ long state, track, tracks;
+ if (scanner.GetStatus(&state, &track, &tracks))
+ {
+ if(state == IDScanner::STATE_MUSICID && tracks != 0 && track != 0)
+ {
+ KillTimer(hwndDlg,1);
+ ShowWindow(hwndDlg,SW_SHOWNA);
+ SetTimer(hwndDlg,2,3000,NULL);
+ }
+ else if(state == IDScanner::STATE_DONE)
+ EndDialog(hwndDlg,0);
+ else if(state == IDScanner::STATE_ERROR)
+ {
+ EndDialog(hwndDlg,0);
+ if(!GetWindowLongPtr(hwndDlg,GWLP_USERDATA)){
+ KillTimer(hwndDlg,1);
+ ShowErrorDlg(hwndDlg);
+ }
+ }
+ }
+ }
+ break;
+ case 2:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_BUTTON1:
+ EndDialog(hwndDlg,0);
+ WASABI_API_DIALOGBOXW(IDD_PREFS, plugin.hwndLibraryParent, PrefsProcedure);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/resource.h b/Src/Plugins/Library/ml_plg/resource.h
new file mode 100644
index 00000000..34be1fbb
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/resource.h
@@ -0,0 +1,120 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_plg.rc
+//
+#define IDS_PLAY_TRACKS_SIMILAR_TO 1
+#define IDS_SCANNING_NEEDED 2
+#define IDS_SCANNER_BLURB 3
+#define IDS_ERROR_INITIALIZING 4
+#define IDS_IDLE 5
+#define IDS_INITIALIZING 6
+#define IDS_SYNC 7
+#define IDS_METADATA 8
+#define IDS_MUSICID 9
+#define IDS_DONE 10
+#define IDS_PAUSE 11
+#define IDS_SCAN 12
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 13
+#define IDS_INITFAILMSG 14
+#define IDS_RESETDB 15
+#define IDS_RESETDB_TEXT 16
+#define IDS_MINUTES 17
+#define IDS_ITEMS 18
+#define IDS_ARTIST 19
+#define IDS_ALBUM 20
+#define IDS_TRACK 21
+#define IDS_TITLE 22
+#define IDS_SIZE 23
+#define IDS_LENGTH 24
+#define IDS_MEGABYTES 25
+#define IDS_STRING26 26
+#define IDD_PREFS 101
+#define IDD_DIALOG1 102
+#define IDD_NAG 107
+#define IDB_BITMAP1 108
+#define IDB_GN_LOGO 108
+#define IDD_FAIL 108
+#define IDD_GENERATE 113
+#define IDS_EXCUSE_ME 114
+#define IDS_STRING115 115
+#define IDS_SEED 115
+#define IDS_YES 116
+#define IDS_NO 117
+#define IDS_OPTIONS 118
+#define IDS_NO_OPTIONS 119
+#define IDS_STATS 120
+#define IDS_ML_QUERY 122
+#define IDD_ADD_PLAYLIST 123
+#define IDS_ENTER_A_NAME 123
+#define IDS_ERROR 124
+#define IDS_PL_NAME_PREFIX 126
+#define IDS_GENERATING 128
+#define IDS_CANT_USE_SEED 132
+#define IDS_UNKNOWN 133
+#define IDS_THERE_CAN_BE_ONLY_ONE 134
+#define IDS_DAYS 135
+#define IDS_DAY 136
+#define IDS_ERROR_RESET 138
+#define IDS_CANNOT_SHUT_DOWN 139
+#define IDC_TEST 1001
+#define IDC_BUTTON1 1002
+#define IDC_PLAYLIST 1003
+#define IDC_ENQUEUE 1004
+#define IDC_SAVE 1006
+#define IDC_PLAY 1007
+#define IDC_STATUS 1008
+#define IDC_SENDTO 1009
+#define IDC_PROGRESS1 1009
+#define IDC_BLURB 1010
+#define IDC_SCANONUSE 1012
+#define IDC_SCANLAUNCH 1013
+#define IDC_SCANDISABLE 1014
+#define IDC_LOGO 1015
+#define IDC_CHECK_USE_SEED 1016
+#define IDC_CHECK_MULTIPLE_ARTISTS 1017
+#define IDC_CHECK_MULTIPLE_ALBUMS 1018
+#define IDC_COMBO_LENGTH 1019
+#define IDC_RESETDB 1020
+#define IDC_RADIO_PLAYLIST_ITEMS 1021
+#define IDC_RADIO_PLAYLIST_LENGTH 1022
+#define IDC_RADIO_PLAYLIST_SIZE 1023
+#define IDC_CHECK_ML_QUERY 1024
+#define IDC_BUTTON_ML_QUERY 1026
+#define IDC_STATIC_GENERATING 1027
+#define IDC_BUTTON_GENERATE_CANCEL 1028
+#define IDC_LENGTH_TYPE 1029
+#define IDC_PROGRESS_GENERATE 1032
+#define IDC_STATIC_PROGRESS_STATE 1033
+#define IDC_BUTTON_CANCEL 1034
+#define IDC_LIST_RESULTS 1035
+#define IDC_BUTTON_OPTIONS 1035
+#define IDC_BUTTON_REGENERATE 1036
+#define IDC_BUTTON_SAVEAS 1037
+#define IDC_BUTTON_PLAY_NOW 1038
+#define IDC_BUTTON_ENQUEUE_NOW 1039
+#define IDC_LIST_RESULTS2 1040
+#define IDC_STATIC_UP_TO 1042
+#define IDC_STATIC_STATS 1043
+#define IDC_STATIC_STATUS_LABEL 1044
+#define IDC_EDIT_ML_QUERY 1045
+#define IDC_STATIC_QUERY_DESCRIPTION 1046
+#define IDC_STATIC_NAME 1047
+#define IDC_EDIT_NAME 1048
+#define IDC_BUTTON_QUERY_DEFAULT 1049
+#define IDC_BUTTON_RESTORE_QUERY_DEFAULT 1049
+#define IDC_GROUP_SEED 1050
+#define IDC_GROUP_PL_ENTRIES 1051
+#define IDC_GROUP_ML_QUERY 1052
+#define IDC_GROUP_STATUS 1053
+#define IDS_NULLSOFT_PLAYLIST_GENERATOR 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 140
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1054
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_plg/util.cpp b/Src/Plugins/Library/ml_plg/util.cpp
new file mode 100644
index 00000000..599512de
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/util.cpp
@@ -0,0 +1,36 @@
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+
+int GetFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *dest, int len)
+{
+ dest[0]=0;
+ extendedFileInfoStructW efis=
+ {
+ filename,
+ metadata,
+ dest,
+ (size_t)len,
+ };
+ int r = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFOW); //will return 1 if wa2 supports this IPC call
+ return r;
+}
+
+int updateFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *data)
+{
+ extendedFileInfoStructW efis =
+ {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? wcslen(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+
+void WriteFileInfo(const wchar_t *filename)
+{
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)filename, IPC_FILE_TAG_MAY_HAVE_UPDATEDW);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/version.rc2 b/Src/Plugins/Library/ml_plg/version.rc2
new file mode 100644
index 00000000..967002b6
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,81,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,81,0,0"
+ VALUE "InternalName", "Nullsoft Playlist Generator"
+ VALUE "LegalCopyright", "Copyright © 2007-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_plg.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_plg/view.cpp b/Src/Plugins/Library/ml_plg/view.cpp
new file mode 100644
index 00000000..9520a34b
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/view.cpp
@@ -0,0 +1,368 @@
+#include "resource.h"
+#include "main.h"
+#include "api.h"
+#include "../nu/listview.h"
+#include "../nu/ChildSizer.h"
+#include "../nu/DialogSkinner.h"
+#include "impl_playlist.h"
+#include "../nu/SendTo.h"
+#include <strsafe.h>
+
+static ChildWndResizeItem view_rlist[] =
+{
+ { IDC_PLAYLIST, ResizeBottom | ResizeRight},
+ { IDC_PLAY, DockToBottom},
+ { IDC_ENQUEUE, DockToBottom},
+ { IDC_SAVE, DockToBottom},
+ { IDC_SENDTO, DockToBottom},
+};
+
+W_ListView playlist_list;
+Playlist currentPlaylist;
+int playlist_skin;
+static SendToMenu sendTo(plugin);
+
+
+static void PlaySelection(int enqueue, int is_all)
+{
+ if (!enqueue)
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+
+ int numTracks = playlist_list.GetCount();
+ for (int i = 0;i < numTracks;i++)
+ {
+ if (is_all || playlist_list.GetSelected(i))
+ {
+ if (currentPlaylist.IsCached(i))
+ {
+ enqueueFileWithMetaStructW s;
+ s.filename = currentPlaylist.ItemName(i);
+ s.title = currentPlaylist.ItemTitle(i);
+ s.length = currentPlaylist.GetItemLengthMilliseconds(i) / 1000;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+ else
+ {
+ enqueueFileWithMetaStructW s;
+ s.filename = currentPlaylist.ItemName(i);
+ s.title = 0;
+ s.length = 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+
+ }
+ }
+
+ if (!enqueue)
+ {
+ if (is_all)
+ {
+ int pos = playlist_list.GetNextSelected(-1);
+ if (pos != -1)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, pos, IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40047, 0); // stop button, literally
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40045, 0); // play button, literally
+ return ;
+ }
+ }
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ }
+ /*
+ int cnt=0;
+ int l=PlayList_getlength();
+
+ int foo_all=0; // all but play the only selected
+ int foo_selected=-1;
+
+ if (g_config->ReadInt("plplaymode",1)||is_all)
+ {
+ int selcnt=0;
+ for(int i=0;i<l;i++)
+ {
+ if(PlayList_getselect(i)) selcnt++;
+ }
+ if ((selcnt == 1 && !enqueue) || selcnt == 0)
+ {
+ foo_all=-1;
+ }
+ }
+
+ */
+}
+
+
+static void playlist_DisplayChange()
+{
+ playlist_list.SetTextColors(dialogSkinner.Color(WADLG_ITEMFG), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetBkColor(playlist_list.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ playlist_list.SetFont(dialogSkinner.GetFont());
+}
+
+static BOOL playlist_GetDisplayInfo(NMLVDISPINFO *lpdi)
+{
+ size_t item = lpdi->item.iItem;
+ if (item < 0 || item >= currentPlaylist.GetNumItems()) return 0;
+
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ switch (lpdi->item.iSubItem)
+ {
+ case 0:
+ {
+ bool cached = currentPlaylist.IsCached(item);
+
+ if (!cached)
+ {
+ wchar_t title[400];
+ int length;
+ mediaLibrary.GetFileInfo(currentPlaylist.ItemName(item), title, 400, &length);
+ if (length == 0)
+ currentPlaylist.SetItemLengthMilliseconds(item, -1000);
+ else
+ currentPlaylist.SetItemLengthMilliseconds(item, length*1000);
+ currentPlaylist.SetItemTitle(item, title);
+ }
+ // CUT: currentPlaylist.GetItemTitle(item, lpdi->item.pszText, lpdi->item.cchTextMax);
+ StringCchPrintf(lpdi->item.pszText, lpdi->item.cchTextMax, L"%d. %s", item + 1, currentPlaylist.ItemTitle(item));
+ }
+ break;
+ case 1:
+ {
+
+ if (currentPlaylist.GetItemLengthMilliseconds(item) == 0) // if the length is 0, then we'll re-read it
+ {
+ wchar_t title[400];
+ int length;
+ mediaLibrary.GetFileInfo(currentPlaylist.ItemName(item), title, 400, &length);
+ if (length == 0)
+ currentPlaylist.SetItemLengthMilliseconds(item, -1000);
+ else
+ {
+ currentPlaylist.SetItemLengthMilliseconds(item, length*1000);
+// currentPlaylist.SetItemTitle(item, AutoWide(title));
+ }
+ }
+
+ int length = currentPlaylist.GetItemLengthMilliseconds(item) / 1000;
+ if (length <= 0)
+ lpdi->item.pszText[0] = 0;
+ else
+ StringCchPrintf(lpdi->item.pszText, lpdi->item.cchTextMax, L"%d:%02d", length / 60, length % 60);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+
+static INT_PTR playlist_Notify(HWND hwndDlg, WPARAM wParam, LPARAM lParam)
+{
+ LPNMHDR l = (LPNMHDR)lParam;
+ if (l->idFrom == IDC_PLAYLIST)
+ {
+ switch (l->code)
+ {
+ /*
+ case NM_RETURN:
+ if (!(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ PlaySelection(g_config->ReadInt("enqueuedef", 0), g_config->ReadInt("plplaymode", 1));
+ else
+ PlaySelection(!g_config->ReadInt("enqueuedef", 0), 0);
+ break;
+
+ case NM_DBLCLK:
+ PlaySelection(g_config->ReadInt("enqueuedef", 0), g_config->ReadInt("plplaymode", 1));
+ break;*/
+ case LVN_GETDISPINFO:
+ return playlist_GetDisplayInfo((NMLVDISPINFO*) lParam);
+ /*case LVN_BEGINDRAG:
+ we_are_drag_and_dropping = 1;
+ SetCapture(hwndDlg);
+ break;
+ */
+ /*
+ case LVN_ITEMCHANGED:
+ case LVN_ODSTATECHANGED:
+ UpdatePlaylistTime(hwndDlg);
+ break;
+ */
+ /*
+ case LVN_KEYDOWN:
+ {
+ LPNMLVKEYDOWN pnkd = (LPNMLVKEYDOWN) lParam;
+ switch (pnkd->wVKey)
+ {
+ case VK_DELETE:
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ Playlist_DeleteSelected(0);
+ else
+ Playlist_DeleteSelected(1);
+
+ break;
+
+ case '3':
+ if (GetAsyncKeyState(VK_MENU)&0x8000)
+ TagEditor(hwndDlg);
+ break;
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ playlist_list.SelectAll();
+ break;
+ case 'I':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ playlist_list.InvertSelection();
+ break;
+ case 'L':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ CurrentPlaylist_AddLocation(hwndDlg);
+ else if (GetAsyncKeyState(VK_SHIFT)&0x8000)
+ CurrentPlaylist_AddDirectory(hwndDlg);
+ else
+ CurrentPlaylist_AddFiles(hwndDlg);
+ SyncPlaylist();
+ break;
+ case 'R':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ {
+ if (GetAsyncKeyState(VK_SHIFT)&0x8000)
+ AGAVE_API_PLAYLISTMANAGER->Randomize(&currentPlaylist);
+ else
+ AGAVE_API_PLAYLISTMANAGER->Reverse(&currentPlaylist);
+ playlist_list.RefreshAll();
+ }
+ break;
+ case 'E':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ {
+ if (GetAsyncKeyState(VK_MENU)&0x8000)
+ Playlist_ResetSelected();
+ else
+ EditEntry(l->hwndFrom);
+ }
+ break;
+
+ }
+ }
+ break;
+ */
+ }
+ }
+ return 0;
+}
+
+static wchar_t *BuildFilenameList(int is_all)
+{
+ wchar_t filename[MAX_PATH] = {0};
+ size_t len = 200;
+ wchar_t *str = (wchar_t *)malloc(len*sizeof(wchar_t));
+ size_t sofar = 0;
+
+ int numTracks = playlist_list.GetCount();
+ for (int i = 0;i < numTracks;i++)
+ {
+ if (is_all || playlist_list.GetSelected(i))
+ {
+ if (currentPlaylist.GetItem(i, filename, MAX_PATH))
+ {
+ size_t filenameLen = wcslen(filename);
+ if ((filenameLen + sofar) > len)
+ {
+ size_t newLen = filenameLen + sofar + 32; // add some cushion
+ wchar_t *newStr = (wchar_t *)malloc(newLen*sizeof(wchar_t));
+ memcpy(newStr, str, sofar*sizeof(wchar_t));
+ len = newLen;
+ free(str);
+ str = newStr;
+ }
+
+ StringCchCopyW(str + sofar, filenameLen, filename);
+ sofar += filenameLen + 1;
+ }
+ }
+ }
+ *(str + sofar) = 0;
+
+ return str;
+}
+
+INT_PTR CALLBACK ViewProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, msg, wParam, lParam);
+ if (a)
+ return a;
+
+ switch (msg)
+ {
+ case WM_INITMENUPOPUP:
+ sendTo.InitPopupMenu(wParam);
+ return 0;
+ case WM_INITDIALOG:
+ childSizer.Init(hwndDlg, view_rlist, sizeof(view_rlist) / sizeof(view_rlist[0]));
+
+ playlist_list.setwnd(GetDlgItem(hwndDlg, IDC_PLAYLIST));
+ playlist_skin = mediaLibrary.SkinList(GetDlgItem(hwndDlg, IDC_PLAYLIST));
+ playlist_list.AddCol(L"Song",200);
+ playlist_list.AddCol(L"Length",100);
+ playlist_list.JustifyColumn(1, LVCFMT_RIGHT);
+ playlist_DisplayChange();
+ playlist_list.SetVirtualCount(currentPlaylist.GetNumItems());
+ break;
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_PLAYLIST|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg,tab,1);
+ }
+ return 0;
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ childSizer.Resize(hwndDlg, view_rlist, sizeof(view_rlist) / sizeof(view_rlist[0]));
+
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_PLAY:
+ if (playlist_list.GetSelectedCount() > 0)
+ PlaySelection(0, 0/*g_config->ReadInt("plplaymode", 1)*/);
+ else
+ PlaySelection(0, 1);
+ break;
+ case IDC_ENQUEUE:
+ if (playlist_list.GetSelectedCount() > 0)
+ PlaySelection(0, 0);
+ else
+ PlaySelection(1, 0);
+ break;
+ case IDC_SAVE:
+ break;
+ case IDC_SENDTO:
+ {
+ HMENU blah = CreatePopupMenu();
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_SENDTO), &r);
+ sendTo.AddHere(hwndDlg, blah, ML_TYPE_FILENAMESW);
+ int x = TrackPopupMenu(blah, TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN | TPM_RETURNCMD, r.left, r.top, 0, hwndDlg, NULL);
+ if (sendTo.WasClicked(x))
+ {
+ int is_all = playlist_list.GetSelectedCount() == 0;
+ wchar_t *names = BuildFilenameList(is_all);
+ sendTo.SendFilenames(names);
+ free(names);
+ }
+
+ sendTo.Cleanup();
+
+ }
+ break;
+ }
+ break;
+ case WM_DISPLAYCHANGE: playlist_DisplayChange(); return 0;
+ case WM_NOTIFY: return playlist_Notify(hwndDlg, wParam, lParam);
+ case WM_DESTROY:
+ mediaLibrary.UnskinList(playlist_skin);
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp b/Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp
new file mode 100644
index 00000000..42566ba8
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp
@@ -0,0 +1,848 @@
+#include "AlbumArtListView.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "../tataki/export.h"
+#include <api/service/waServiceFactory.h>
+#include <api/service/svcs/svc_imgload.h>
+#include "./local_menu.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+#define WM_EX_GETREALLIST (WM_USER + 0x01)
+
+#define HORZ_SPACING 4
+#define VERT_SPACING 4
+
+AlbumArtListView::AlbumArtListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu)
+ : SkinnedListView(lc,dlgitem,libraryParent,parent,enableHeaderMenu), hbmpNames(NULL),
+ classicnotfoundW(0), classicnotfoundH(0), ratingrow(-1), itemHeight(0), textHeight(0), ratingTop(0),
+ notfound(L"winamp.cover.notfound"), notfound60(L"winamp.cover.notfound.60"), notfound90(L"winamp.cover.notfound.90")
+{
+ this->hwndDlg = parent;
+ this->dlgitem = dlgitem;
+ mode = lc->config->ReadInt(L"albumartviewmode",0);
+ lc->SetMode(mode);
+
+ ZeroMemory(classicnotfound, sizeof(classicnotfound));
+}
+
+AlbumArtListView::~AlbumArtListView() {
+ if (hbmpNames) DeleteObject(hbmpNames);
+}
+
+int AlbumArtListView::GetFindItemColumn() {
+ if(mode > 1) return 1;
+ return contents->GetSortColumn();
+}
+
+static COLORREF GetWAColor(INT index)
+{
+ static int (*wad_getColor)(int idx) = NULL;
+ if (!wad_getColor) *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ return (wad_getColor) ? wad_getColor(index) : 0xFF00FF;
+}
+
+static void getImgSize(int mode, int &w, int &h) {
+ switch(mode) {
+ case 0: w=h=60; break;
+ case 1: w=h=90; break;
+ case 2: w=h=120; break;
+ case 3: w=h=60; break;
+ case 4: w=h=90; break;
+ case 5: w=h=120; break;
+ }
+}
+
+static bool isDetailsMode(int mode) {
+ return mode == 0 || mode == 1 || mode == 2;
+}
+
+void DrawRect(HDC dc, int x, int y, int w, int h) {
+ w-=1;
+ h-=1;
+ MoveToEx(dc,x,y,NULL);
+ LineTo(dc,x,y+h);
+ MoveToEx(dc,x,y+h,NULL);
+ LineTo(dc,x+w,y+h);
+ MoveToEx(dc,x+w,y+h,NULL);
+ LineTo(dc,x+w,y);
+ MoveToEx(dc,x+w,y,NULL);
+ LineTo(dc,x,y);
+}
+
+static int getStrExtent(HDC dc, const wchar_t * s) {
+ int ret=0;
+ while(s && *s) {
+ int f=0;
+ GetCharWidth32(dc,*s,*s,&f);
+ s++;
+ ret+=f;
+ }
+ return int(ret);
+}
+
+ARGB32 * loadImg(const void * data, int len, int *w, int *h, bool ldata=false) {
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for(int i=0; i<n; i++) {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if(sf) {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if(l) {
+ if(l->testData(data,len)) {
+ ARGB32* ret;
+ if(ldata) ret = l->loadImageData(data,len,w,h);
+ else ret = l->loadImage(data,len,w,h);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+ARGB32 * loadRrc(int id, char * sec, int *w, int *h, bool data=false)
+{
+ DWORD size = 0;
+ // as a nice little treat, allow lang packs to contain a custom IDR_IMAGE_NOTFOUND file
+ HGLOBAL resourceHandle = WASABI_API_LOADRESFROMFILEA((LPCSTR)sec, (LPCSTR)MAKEINTRESOURCEA(id), &size);
+ if(resourceHandle)
+ {
+ ARGB32* ret = loadImg(resourceHandle,size,w,h,data);
+ UnlockResource(resourceHandle);
+ return ret;
+ }
+ return NULL;
+}
+
+void adjustbmp(ARGB32 * p, int len, COLORREF fg)
+{
+ ARGB32 * end = p+len;
+ while (p < end)
+ {
+ int a = (*p>>24)&0xff ;
+ int b = a*((*p&0xff) * (fg&0xff)) / (0xff*0xff);
+ int g = a*(((*p>>8)&0xff) * ((fg>>8)&0xff)) / (0xff*0xff);
+ int r = a*(((*p>>16)&0xff) * ((fg>>16)&0xff)) / (0xff*0xff);
+ *p = (a<<24) | (r&0xff) | ((g&0xff)<<8) | ((b&0xff)<<16);
+ p++;
+ }
+}
+
+void AlbumArtListView::drawArt(pmpart_t art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex)
+{
+ int x = prcDst->left;
+ int y = prcDst->top;
+ int w = prcDst->right - prcDst->left;
+ int h = prcDst->bottom - prcDst->top;
+ HDC dc = pCanvas->getHDC();
+ if(art) contents->dev->setArtNaturalSize(art,w,h);
+ // draw image 4,4,w,h
+ if(art && contents->dev->drawArt(art,pCanvas->getHDC(),x,y,w,h)) {
+ // drawn by plugin!
+ }
+ else
+ {
+ SkinBitmap *noart;
+
+ int h = prcDst->right - prcDst->left;
+ if (h == 60)
+ noart = notfound60.getBitmap();
+ else if (h == 90)
+ noart = notfound90.getBitmap();
+ else
+ noart = notfound.getBitmap();
+
+ if (!noart || noart->isInvalid())
+ {
+ if(classicnotfound[imageIndex])
+ SkinBitmap(classicnotfound[imageIndex],classicnotfoundW,classicnotfoundH).stretchToRectAlpha(pCanvas, prcDst);
+ else
+ {
+ DrawRect(dc,x,y,w,h);
+ wchar_t str1[32] = {0}, str2[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_IMAGE,str1,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_AVAILABLE,str2,32);
+ ExtTextOutW(dc, w/2 - 22 + x, w/2 - 14 + y, 0,NULL,str1,wcslen(str1),0);
+ ExtTextOutW(dc, w/2 - 22 + x, w/2 + 1 + y, 0,NULL,str2,wcslen(str2),0);
+ }
+ }
+ else
+ noart->stretch(pCanvas,x,y,w,h);
+ }
+}
+
+// icon view
+BOOL AlbumArtListView::DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive)
+{
+ HDC hdc = plvcd->nmcd.hdc;
+ RECT ri, re, rcText;
+ int w=0,h=0, imageIndex;
+ getImgSize(mode,w,h);
+
+ SetBkColor(hdc, plvcd->clrTextBk);
+ SetTextColor(hdc, plvcd->clrText);
+ SetRect(&rcText, plvcd->nmcd.rc.left, plvcd->nmcd.rc.bottom - (textHeight + 2), plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom);
+ imageIndex = 0;
+ if ((LVIS_SELECTED | LVIS_FOCUSED) & itemState)
+ {
+ SetRect(&re, plvcd->nmcd.rc.left, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, rcText.top - 4);
+ if (IntersectRect(&ri, &re, prcClip)) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+ if (LVIS_SELECTED & itemState) imageIndex = (bWndActive) ? 1 : 2;
+ }
+
+ SetRect(&re, plvcd->nmcd.rc.left + 2, plvcd->nmcd.rc.top + 2, plvcd->nmcd.rc.left + 2 + w, plvcd->nmcd.rc.top + 2 + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ pmpart_t art = contents->GetArt(plvcd->nmcd.dwItemSpec);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ if (IntersectRect(&ri, &rcText, prcClip))
+ {
+ if ((LVIS_SELECTED | LVIS_FOCUSED) & itemState) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ wchar_t buf[104] = {0};
+ contents->GetCellText(plvcd->nmcd.dwItemSpec,1,buf,100);
+ const wchar_t *p = buf;
+ if (p && *p)
+ {
+ SetRect(&ri, rcText.left + 2, rcText.top + 1, rcText.right - 2, rcText.bottom -1);
+ DrawTextW(hdc, p, -1, &ri, DT_CENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_NOPREFIX);
+ }
+
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ HWND hwndRealList;
+ hwndRealList = (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L);
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
+ DrawFocusRect(hdc, &rcText);
+ }
+ }
+ }
+ return TRUE;
+}
+
+//detail view
+BOOL AlbumArtListView::PrepareDetails(HDC hdc)
+{
+ INT width(0), height, len;
+ HFONT hFont,hFontBold, hOldFont;
+ HDC hdcTmp;
+ HBITMAP hbmpOld;
+ LOGFONT l={0};
+ RECT ri = {0};
+ wchar_t ratingstr[100] = {0}, buf[100] = {0};
+
+ hdcTmp = CreateCompatibleDC(hdc);
+ if (!hdcTmp) return FALSE;
+
+ hFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
+ GetObject(hFont, sizeof(LOGFONT), &l);
+ l.lfWeight = FW_BOLD;
+ hFontBold = CreateFontIndirect(&l);
+
+ hOldFont = (HFONT)SelectObject(hdcTmp, hFontBold);
+
+ for (int i=0; i < contents->GetNumColumns(); i++)
+ {
+ int of = getStrExtent(hdcTmp, contents->GetColumnTitle(i));
+ if (of > width) width = of;
+ }
+ if(width) width += 20;
+ height = contents->GetNumColumns() * textHeight;
+ hbmpNames = CreateCompatibleBitmap(hdc, width * 3, height);
+ hbmpOld = (HBITMAP)SelectObject(hdcTmp, hbmpNames);
+
+ SetRect(&ri, 0, 0, width, height);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_RATING,ratingstr,100);
+ INT clrText[3] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, };
+ INT clrBk[3] = { WADLG_ITEMBG, WADLG_SELBAR_BGCOLOR, WADLG_INACT_SELBAR_BGCOLOR, };
+ for (int j = 0; j < 3; j++)
+ {
+ SetTextColor(hdcTmp, GetWAColor(clrText[j]));
+ SetBkColor(hdcTmp, GetWAColor(clrBk[j]));
+
+ ExtTextOutW(hdcTmp,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ for (int i=0, top = 0; i < contents->GetNumColumns(); i++, top += textHeight)
+ {
+ if (-1 == ratingrow && 0 == lstrcmpW(contents->GetColumnTitle(i), ratingstr)) ratingrow = i;
+ StringCchCopyW(buf, 100, contents->GetColumnTitle(i));
+ len = wcslen(buf);
+ if (len > 0 && len < 99) { buf[len] = L':'; len ++; }
+ ExtTextOutW(hdcTmp, ri.left + 1, top, ETO_CLIPPED, &ri, buf, len, NULL);
+ }
+ OffsetRect(&ri, width, 0);
+ }
+
+ SelectObject(hdcTmp, hbmpOld);
+ SelectObject(hdcTmp, hOldFont);
+ DeleteObject(hFontBold);
+ DeleteDC(hdcTmp);
+ return TRUE;
+}
+
+BOOL AlbumArtListView::DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNames, INT namesWidth)
+{
+ RECT ri, re;
+ HDC hdc;
+
+ INT imageIndex;
+
+ hdc = plvcd->nmcd.hdc;
+
+ SetTextColor(hdc, plvcd->clrText);
+ SetBkColor(hdc, plvcd->clrTextBk);
+
+ if (LVIS_SELECTED & itemState)
+ {
+ // background
+ SetRect(&re, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if (IntersectRect(&ri, &re, prcClip)) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+ imageIndex = (bWndActive) ? 1 : 2;
+ }
+ else imageIndex = 0;
+
+ int w, h;
+ getImgSize(mode, w, h);
+ SetRect(&re, 6+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, 6+ plvcd->nmcd.rc.left + w, 3+ plvcd->nmcd.rc.top + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ pmpart_t art = contents->GetArt(plvcd->nmcd.dwItemSpec);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ // text
+ int limCY, limCX;
+ wchar_t buf[100] = {0};
+
+ limCY = plvcd->nmcd.rc.bottom;
+ if (prcClip->bottom < plvcd->nmcd.rc.bottom) limCY = prcClip->bottom;
+ limCX = plvcd->nmcd.rc.right -1;
+ if (prcClip->right < plvcd->nmcd.rc.right) limCX = prcClip->right;
+
+ SetRect(&ri, w+16+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, limCX, limCY);
+
+ if (hdcNames && ri.left < ri.right)
+ {
+ BitBlt(hdc, ri.left, ri.top, min(ri.right - ri.left, namesWidth), ri.bottom - ri.top, hdcNames, namesWidth*imageIndex, 0, SRCCOPY);
+ ri.left += namesWidth;
+ }
+
+ ri.bottom = ri.top;
+
+ if (ri.left < ri.right)
+ {
+ for (int i=0; i < contents->GetNumColumns() && ri.top < limCY; i++, ri.top += textHeight)
+ {
+ contents->GetCellText(plvcd->nmcd.dwItemSpec,i,buf,100);
+ const wchar_t *p = buf;
+
+ ri.bottom += textHeight;
+ if (ri.bottom > limCY) ri.bottom = limCY;
+
+ if ((INT)i == ratingrow) // this is the ratings column, so draw graphical stars
+ {
+ int rating = wcslen(buf);
+ RATINGDRAWPARAMS p = {sizeof(RATINGDRAWPARAMS),hdc,
+ {ri.left, ri.top + ratingTop, ri.right,ri.bottom},
+ rating,5,0,RDS_SHOWEMPTY,NULL,0};
+ MLRating_Draw(plugin.hwndLibraryParent,&p);
+ }
+ else ExtTextOutW(hdc, ri.left, ri.top, ETO_CLIPPED ,&ri,p,wcslen(p),NULL);
+ }
+ }
+
+ // bottom line
+ MoveToEx(hdc,plvcd->nmcd.rc.left + 4,plvcd->nmcd.rc.bottom - 3,NULL);
+ LineTo(hdc,plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 3);
+
+ // focus rect
+ SetRect(&ri, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ HWND hwndRealList;
+ hwndRealList = (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L);
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
+ DrawFocusRect(hdc, &ri);
+ }
+ }
+
+ return TRUE;
+}
+
+static HWND CreateSmoothScrollList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"SmoothScrollList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+static HWND CreateHeaderIconList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"HeaderIconList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+BOOL AlbumArtListView::OnKeyDown(NMLVKEYDOWN *plvkd)
+{
+ switch (plvkd->wVKey)
+ {
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT))
+ {
+ LVITEM item;
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(plvkd->hdr.hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtListView::OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ bActive = (GetFocus() == (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L));
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState;
+ itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+ plvcd->nmcd.rc.right -= HORZ_SPACING;
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemIcon(plvcd, &activeCanvas, itemState, &rcClip, bActive);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtListView::OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static HDC hdcNames;
+ static INT namesWidth;
+ static HBITMAP hbmpOld;
+ static HPEN penOld;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ if (!hbmpNames) PrepareDetails(plvcd->nmcd.hdc);
+ if (hbmpNames)
+ {
+ BITMAP bi;
+ GetObject(hbmpNames, sizeof(BITMAP), &bi);
+ namesWidth = bi.bmWidth/3;
+ hdcNames = CreateCompatibleDC(plvcd->nmcd.hdc);
+ hbmpOld = (hdcNames) ? (HBITMAP)SelectObject(hdcNames, hbmpNames) : NULL;
+ }
+ else
+ {
+ hdcNames = NULL;
+ namesWidth = 0;
+ }
+ bActive = (GetFocus() == (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L));
+
+ penOld = (HPEN)SelectObject(plvcd->nmcd.hdc, CreatePen(PS_SOLID,1, GetWAColor(WADLG_HILITE)));
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState;
+ itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemDetail(plvcd, &activeCanvas, itemState, &rcClip, bActive, hdcNames, namesWidth);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ if (hdcNames)
+ {
+ SelectObject(hdcNames, hbmpOld);
+ DeleteDC(hdcNames);
+ }
+ if (penOld)
+ {
+ HPEN pen;
+ pen = (HPEN)SelectObject(plvcd->nmcd.hdc, penOld);
+ if (pen) DeleteObject(pen);
+ penOld = NULL;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtListView::CalcuateItemHeight(void)
+{
+ int w, h;
+ HWND hwndList = GetDlgItem(hwndDlg, dlgitem);
+ TEXTMETRIC tm = {0};
+
+ getImgSize(mode, w, h);
+
+ textHeight = 0;
+
+ HDC hdc = GetDC(hwndDlg);
+ if (hdc)
+ {
+ HFONT hFont = (HFONT)SendMessageW(hwndList, WM_GETFONT, 0, 0L);
+ if (!hFont) hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ HFONT hFontOld = (HFONT)SelectObject(hdc, hFont);
+
+ GetTextMetrics(hdc, &tm);
+ textHeight = tm.tmHeight;
+ SelectObject(hdc, hFontOld);
+ ReleaseDC(hwndDlg, hdc);
+ }
+
+ if (isDetailsMode(mode))
+ {
+ if (textHeight < 14) textHeight = 14;
+ RECT r;
+ MLRating_CalcRect(plugin.hwndLibraryParent, NULL, 5, &r);
+ r.bottom -= r.top;
+
+ if ( r.bottom >= textHeight ) ratingTop = 0;
+ else
+ {
+ if (tm.tmAscent > (r.bottom + (r.bottom/2))) ratingTop = tm.tmAscent - r.bottom;
+ else ratingTop = (textHeight - r.bottom)/2 + 1;
+ }
+
+ int newHeight = max(h, textHeight * (INT)contents->GetNumColumns()) + 12;
+ if (newHeight != itemHeight)
+ {
+ itemHeight = newHeight;
+ RECT rw;
+ GetWindowRect(hwndList, &rw);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left - 1, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
+ }
+ }
+ else
+ {
+ HIMAGELIST hIL = (HIMAGELIST)SendMessageW(hwndList, LVM_GETIMAGELIST, 0, 0L);
+ if (!hIL || !ImageList_GetIconSize(hIL, &w, &h))
+ { h += 4; w+= 4; }
+ SendMessageW(hwndList, LVM_SETICONSPACING, 0, MAKELPARAM(w + HORZ_SPACING, h + textHeight + 6 + VERT_SPACING));
+ if (!tm.tmAveCharWidth) tm.tmAveCharWidth = 2;
+ itemHeight = w / tm.tmAveCharWidth;
+ }
+ return TRUE;
+}
+
+BOOL AlbumArtListView::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ HWND hwnd = GetDlgItem(hwndDlg,dlgitem);
+
+ RECT r;
+ if (hwnd)
+ {
+ GetWindowRect(hwnd,&r);
+ MapWindowPoints(HWND_DESKTOP, hwndDlg, (POINT*)&r, 2);
+ }
+ else SetRect(&r, 0, 0, 1, 1);
+
+ if (isDetailsMode(mode))
+ {
+ if (hwnd) DestroyWindow(hwnd);
+ hwnd = CreateSmoothScrollList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ SendMessage(hwnd,WM_USER+6,0,0);
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+ }
+ else
+ {
+ if (hwnd) DestroyWindow(hwnd);
+ hwnd = CreateHeaderIconList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ ShowWindow(hwnd,SW_SHOWNORMAL);
+ int w=0,h=0;
+ getImgSize(mode,w,h);
+ HIMAGELIST il = ImageList_Create(w + 4,h + 4,ILC_COLOR24,0,1); // add borders
+ ListView_SetImageList(hwnd,il,LVSIL_NORMAL);
+ }
+ }
+ break;
+
+ case WM_DISPLAYCHANGE:
+ {
+ if (hbmpNames) DeleteObject(hbmpNames);
+ hbmpNames = NULL;
+ ratingrow = -1;
+ CalcuateItemHeight();
+
+ int rw,rh;
+ ARGB32 * bmp = loadRrc(IDR_IMAGE_NOTFOUND,"PNG",&rw,&rh,true);
+ classicnotfoundW = rw;
+ classicnotfoundH = rh;
+ INT color[] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, };
+
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i]) WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+ classicnotfound[i] = NULL;
+
+ if(bmp)
+ {
+ if (0 != i)
+ {
+ classicnotfound[i] = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*rw*rh);
+ CopyMemory(classicnotfound[i], bmp, sizeof(ARGB32)*rw*rh);
+ }
+ else classicnotfound[i] = bmp;
+ adjustbmp(classicnotfound[i],rw*rh, GetWAColor(color[i]));
+ }
+ }
+
+ if(uMsg == WM_DISPLAYCHANGE) PostMessageW(GetDlgItem(hwndDlg,dlgitem),uMsg,wParam,lParam);
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i]) WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+ classicnotfound[i] = NULL;
+ }
+ break;
+
+ case WM_MEASUREITEM:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ ((MEASUREITEMSTRUCT*)lParam)->itemHeight = itemHeight;
+ return TRUE;
+ }
+ break;
+ case WM_NOTIFY:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ BOOL bProcessed(FALSE);
+ LRESULT result(0);
+ switch (((NMHDR*)lParam)->code)
+ {
+ case LVN_KEYDOWN: bProcessed = OnKeyDown((NMLVKEYDOWN*)lParam); break;
+ case NM_CUSTOMDRAW:
+ bProcessed = (isDetailsMode(mode)) ? OnCustomDrawDetails((NMLVCUSTOMDRAW*)lParam, &result) : OnCustomDrawIcon((NMLVCUSTOMDRAW*)lParam, &result);
+ break;
+ case LVN_GETDISPINFOW:
+ return TRUE;
+ case LVN_GETINFOTIPW:
+ {
+ wchar_t buf[256]=L"";
+ contents->GetCellText(((NMLVGETINFOTIPW*)lParam)->iItem,1,buf,256);
+ const wchar_t *p = buf;
+ if (p && *p && lstrlenW(p) > itemHeight) // we use itemHeight to write number of average characters that fits label in icon mode
+ {
+ StringCchCopyW(((NMLVGETINFOTIPW*)lParam)->pszText, ((NMLVGETINFOTIPW*)lParam)->cchTextMax, p);
+ }
+ }
+ return TRUE;
+ }
+
+ if (bProcessed) SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return bProcessed;
+ }
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->code == NM_RCLICK && l->hwndFrom == ListView_GetHeader(listview.getwnd())) {
+ extern HMENU m_context_menus;
+ HMENU menu = GetSubMenu(m_context_menus, 10);
+ POINT p;
+ GetCursorPos(&p);
+ int checked=0;
+ switch(mode) {
+ case 0: checked=ID_ARTHEADERMENU_SMALLDETAILS; break;
+ case 1: checked=ID_ARTHEADERMENU_MEDIUMDETAILS; break;
+ case 2: checked=ID_ARTHEADERMENU_LARGEDETAILS; break;
+ case 3: checked=ID_ARTHEADERMENU_SMALLICON; break;
+ case 4: checked=ID_ARTHEADERMENU_MEDIUMICON; break;
+ case 5: checked=ID_ARTHEADERMENU_LARGEICON; break;
+ }
+ CheckMenuItem(menu,checked,MF_CHECKED | MF_BYCOMMAND);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, l->hwndFrom, NULL);
+ CheckMenuItem(menu,checked,MF_UNCHECKED | MF_BYCOMMAND);
+ if(!r) return TRUE;
+ ProcessMenuResult(r,false,0,NULL,hwndDlg);
+ return TRUE;
+ } else if(l->code == LVN_GETINFOTIP && l->idFrom == dlgitem) {
+ NMLVGETINFOTIP *n = (NMLVGETINFOTIP*)l;
+ wchar_t artist[100] = {0}, album[100] = {0}, tracks[10] = {0}, year[10] = {0};
+ contents->GetCellText(n->iItem,0,artist,100);
+ contents->GetCellText(n->iItem,1,album,100);
+ contents->GetCellText(n->iItem,2,tracks,10);
+ contents->GetCellText(n->iItem,3,year,10);
+ wchar_t trackstr[100] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRACKS,trackstr,100);
+ CharLower(trackstr);
+ if(year[0]) StringCchPrintf(n->pszText,n->cchTextMax,L"%s - %s (%s): %s %s",artist,album,year,tracks,trackstr);
+ else StringCchPrintf(n->pszText,n->cchTextMax,L"%s - %s: %s %s",artist,album,tracks,trackstr);
+ }
+ }
+ break;
+
+ case LVM_REDRAWITEMS:
+ {
+ RECT ri;
+ HWND hwndList = GetDlgItem(hwndDlg, dlgitem);
+ if (hwndList)
+ {
+ HWND hwndRealList = (HWND)SendMessageW(hwndList, WM_EX_GETREALLIST, 0, 0L);
+ if (hwndRealList)
+ {
+ int w, h;
+ getImgSize(mode, w, h);
+ if (isDetailsMode(mode))
+ {
+ for(int i = (INT)wParam; i <= (INT)lParam; i++)
+ {
+ ri.left = LVIR_BOUNDS;
+ if (SendMessageW(hwndRealList, LVM_GETITEMRECT, i, (LPARAM)&ri))
+ {
+ ri.left += 6;
+ ri.top += 3;
+ ri.right = ri.left + w;
+ ri.bottom = ri.top + h;
+ InvalidateRect(hwndRealList, &ri, FALSE);
+ }
+ }
+ }
+ else
+ {
+ for(int i = (INT)wParam; i <= (INT)lParam; i++)
+ {
+ ri.left = LVIR_ICON;
+ if (SendMessageW(hwndRealList, LVM_GETITEMRECT, i, (LPARAM)&ri))
+ {
+ ri.left += 2;
+ ri.top += 2;
+ ri.right = ri.left + w;
+ ri.bottom = ri.top + h;
+ InvalidateRect(hwndRealList, &ri, FALSE);
+ }
+ }
+ }
+ }
+ else SendMessageW(hwndList, LVM_REDRAWITEMS, wParam, lParam);
+ }
+ }
+ return TRUE;
+ }
+ return SkinnedListView::DialogProc(hwndDlg,uMsg,wParam,lParam);
+}
+
+HMENU AlbumArtListView::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu) {
+ HMENU menu = GetSubMenu(themenu, 10);
+
+ int checked=0;
+ switch(mode) {
+ case 0: checked=ID_ARTHEADERMENU_SMALLDETAILS; break;
+ case 1: checked=ID_ARTHEADERMENU_MEDIUMDETAILS; break;
+ case 2: checked=ID_ARTHEADERMENU_LARGEDETAILS; break;
+ case 3: checked=ID_ARTHEADERMENU_SMALLICON; break;
+ case 4: checked=ID_ARTHEADERMENU_MEDIUMICON; break;
+ case 5: checked=ID_ARTHEADERMENU_LARGEICON; break;
+ }
+ CheckMenuItem(menu,checked,MF_CHECKED | MF_BYCOMMAND);
+
+ if(isFilter) {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i=0;
+ while(GetMenuItemInfo(menu,i,TRUE,&m)) {
+ m.wID |= (1+filterNum) << 16;
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ i++;
+ }
+ }
+ return menu;
+}
+
+void AlbumArtListView::ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent) {
+ int mid = (r >> 16);
+ if(!isFilter && mid) return;
+ if(isFilter && mid-1 != filterNum) return;
+ r &= 0xFFFF;
+
+ switch(r) {
+ case ID_ARTHEADERMENU_SMALLDETAILS: mode=0; break;
+ case ID_ARTHEADERMENU_MEDIUMDETAILS: mode=1; break;
+ case ID_ARTHEADERMENU_LARGEDETAILS: mode=2; break;
+ case ID_ARTHEADERMENU_SMALLICON: mode=3; break;
+ case ID_ARTHEADERMENU_MEDIUMICON: mode=4; break;
+ case ID_ARTHEADERMENU_LARGEICON: mode=5; break;
+ default: return;
+ }
+
+ contents->config->WriteInt(L"albumartviewmode",mode);
+ contents->SetMode(mode);
+ while (ListView_DeleteColumn(listview.getwnd(), 0));
+ DialogProc(parent,WM_INITDIALOG,0,0);
+ HWND hwndList = GetDlgItem(parent,dlgitem);
+ if (hwndList)
+ {
+ MLSkinnedWnd_SkinChanged(hwndList, TRUE, TRUE);
+ ListView_SetItemCount(hwndList, contents->GetNumRows());
+ CalcuateItemHeight();
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/AlbumArtListView.h b/Src/Plugins/Library/ml_pmp/AlbumArtListView.h
new file mode 100644
index 00000000..d6335849
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/AlbumArtListView.h
@@ -0,0 +1,44 @@
+#ifndef _ALBUMARTLISTVIEW_H_
+#define _ALBUMARTLISTVIEW_H_
+
+#include "SkinnedListView.h"
+#include <tataki/bitmap/autobitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+
+class AlbumArtListView : public SkinnedListView {
+public:
+ AlbumArtListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu=true);
+
+ virtual ~AlbumArtListView();
+ virtual int GetFindItemColumn();
+ virtual BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+
+ BOOL DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive);
+ BOOL PrepareDetails(HDC hdc);
+ BOOL DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNaes, INT namesWidth);
+ void drawArt(pmpart_t art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex);
+
+protected:
+ BOOL OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnKeyDown(NMLVKEYDOWN *plvkd);
+ BOOL CalcuateItemHeight(void);
+
+ int dlgitem;
+ HWND hwndDlg;
+ int mode;
+ WNDPROC oldproc;
+ AutoSkinBitmap notfound, notfound60, notfound90;
+ ARGB32 * classicnotfound[3];
+ int classicnotfoundW,classicnotfoundH;
+ INT ratingrow;
+ HBITMAP hbmpNames;
+ INT itemHeight;
+ INT textHeight;
+ INT ratingTop;
+};
+
+#endif // _ALBUMARTLISTVIEW_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp
new file mode 100644
index 00000000..33cf8a9d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp
@@ -0,0 +1,1173 @@
+#include "ArtistAlbumLists.h"
+#include "Filters.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "metadata_utils.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
+int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb) {
+ if (!pa) pa=L"";
+ else SKIP_THE_AND_WHITESPACE(pa)
+ if (!pb) pb=L"";
+ else SKIP_THE_AND_WHITESPACE(pb)
+ return lstrcmpi(pa,pb);
+}
+#undef SKIP_THE_AND_WHITESPACE
+
+Filter * getFilter(wchar_t *name);
+
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+
+typedef struct
+{
+ songid_t songid;
+ Device * dev;
+} SortSongItem;
+
+int thread_killed = 0;
+static int useby, usedir, usecloud;
+static Device * currentDev;
+
+static int sortFunc(const void *elem1, const void *elem2)
+{
+ int use_by=useby;
+ int use_dir=usedir;
+ songid_t a=(songid_t)*(void **)elem1;
+ songid_t b=(songid_t)*(void **)elem2;
+ wchar_t bufa[2048] = {0}, bufb[2048] = {0};
+
+ // this might be too slow, but it'd be nice
+ for (int x = 0; x < 5; x ++)
+ {
+ if (thread_killed) break;
+
+ bufa[0]=bufb[0]=0;
+ if (use_by == (7+usecloud)) // year -> artist -> album -> track
+ {
+ int v1=currentDev->getTrackYear(a);
+ int v2=currentDev->getTrackYear(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == 4 && usecloud) // cloud -> artist -> album -> track
+ {
+ currentDev->getTrackExtraInfo(a,L"cloud",bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackExtraInfo(b,L"cloud",bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=0;
+ }
+ else if (use_by == 1) // title -> artist -> album -> disc -> track
+ {
+ currentDev->getTrackTitle(a,bufa,2048);
+ currentDev->getTrackTitle(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=0;
+ }
+ else if (use_by == 0) // artist -> album -> disc -> track -> title
+ {
+ currentDev->getTrackArtist(a,bufa,2048);
+ currentDev->getTrackArtist(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == 2) // album -> disc -> track -> title -> artist
+ {
+ currentDev->getTrackAlbum(a,bufa,2048);
+ currentDev->getTrackAlbum(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_dir=0;
+ use_by=5;
+ }
+ else if (use_by == 5+usecloud) // disc -> track -> title -> artist -> album
+ {
+ int v1=currentDev->getTrackDiscNum(a);
+ int v2=currentDev->getTrackDiscNum(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=4;
+ }
+ else if (use_by == 4+usecloud) // track -> title -> artist -> album -> disc
+ {
+ int v1=currentDev->getTrackTrackNum(a);
+ int v2=currentDev->getTrackTrackNum(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=1;
+ }
+ else if (use_by == 6+usecloud) // genre -> artist -> album -> disc -> track
+ {
+ currentDev->getTrackGenre(a,bufa,2048);
+ currentDev->getTrackGenre(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=0;
+ }
+ else if (use_by == 3) // length -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackLength(a);
+ int v2=currentDev->getTrackLength(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (8+usecloud)) // bitrate -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackBitrate(a);
+ int v2=currentDev->getTrackBitrate(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (9+usecloud)) // size -> artist -> album -> disc -> track
+ {
+ __int64 v1=currentDev->getTrackSize(a);
+ __int64 v2=currentDev->getTrackSize(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (10+usecloud)) // playcount -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackPlayCount(a);
+ int v2=currentDev->getTrackPlayCount(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (11+usecloud)) // rating -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackRating(a);
+ int v2=currentDev->getTrackRating(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (12+usecloud))
+ {
+ double v = difftime((time_t)currentDev->getTrackLastPlayed(a),(time_t)currentDev->getTrackLastPlayed(b));
+ RETIFNZ(v);
+ use_by=0;
+ }
+ else if (use_by == (13+usecloud)) // album artist -> album
+ {
+ currentDev->getTrackAlbumArtist(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackAlbumArtist(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (14+usecloud)) // publisher -> album
+ {
+ currentDev->getTrackPublisher(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackPublisher(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (15+usecloud)) // composer -> album
+ {
+ currentDev->getTrackComposer(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackComposer(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (16+usecloud)) // mime -> artist -> album -> disc -> track
+ {
+ currentDev->getTrackMimeType(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackMimeType(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (17+usecloud)) // date added -> artist -> album -> disc -> track
+ {
+ double v = difftime((time_t)currentDev->getTrackDateAdded(a),(time_t)currentDev->getTrackDateAdded(b));
+ RETIFNZ(v);
+ use_by=0;
+ }
+ else break; // no sort order?
+
+ if (thread_killed) break;
+ }
+ return 0;
+}
+
+static int sortFunc_filteritems(const void *elem1, const void *elem2) {
+ FilterItem *a=(FilterItem *)*(void **)elem1;
+ FilterItem *b=(FilterItem *)*(void **)elem2;
+ return a->compareTo2(b,useby,usedir);
+}
+
+class FilterList : public ListContents {
+public:
+ Filter * filter;
+ C_ItemList * items;
+ ArtistAlbumLists * aaList;
+
+ wchar_t topString[128];
+ int nextFilterNum;
+ int tracks;
+
+ int sortcol;
+ int sortdir;
+ int id;
+
+ FilterList(int id, Device * dev0, C_Config * config, Filter * filter, ArtistAlbumLists * aaList, bool cloud) :
+ id(id), filter(filter), sortcol(0), sortdir(0), aaList(aaList), tracks(0), nextFilterNum(0), items(0) {
+ this->topString[0] = 0;
+ this->config = config;
+ this->dev = dev0;
+ this->cloud = cloud;
+ this->cloudcol = -1;
+ filter->AddColumns(dev, &fields, config, !!this->cloud);
+
+ for(int i = 0; i < fields.GetSize(); i++) {
+ if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud"))
+ {
+ this->cloudcol = ((ListField *)fields.Get(i))->pos;
+ break;
+ }
+ }
+
+ wchar_t temp[16] = {0};
+ wsprintf(temp, L"filter%d_sortcol", id);
+ this->sortcol = config->ReadInt(temp, 0);
+ wsprintf(temp, L"filter%d_sortdir", id);
+ this->sortdir = config->ReadInt(temp, 0);
+
+ this->SortColumns();
+ }
+ virtual ~FilterList() {
+ wchar_t temp[16] = {0};
+ wsprintf(temp, L"filter%d_sortcol", id);
+ config->WriteInt(temp, sortcol);
+ wsprintf(temp, L"filter%d_sortdir", id);
+ config->WriteInt(temp, sortdir);
+ }
+ virtual int GetNumColumns() { return fields.GetSize(); }
+ virtual int GetNumRows() { return items->GetSize() + (filter->HaveTopItem()?1:0); }
+ virtual wchar_t * GetColumnTitle(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->name;
+ return L"";
+ }
+ virtual int GetColumnWidth(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->width;
+ return 0;
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) {
+ buf[0]=0;
+ if(col >= fields.GetSize() || aaList->bgThread_Handle) return;
+ int colid = ((ListField*)fields.Get(col))->field;
+ if(filter->HaveTopItem()) {
+ if(row) ((FilterItem*)items->Get(row-1))->GetCellText(colid,buf,buflen);
+ else {
+ if(colid%100 == 0) lstrcpyn(buf,topString,buflen);
+ else if(colid%100 == 41) wsprintf(buf,L"%d",tracks);
+ else if(colid%100 == 40 && filter->nextFilter) wsprintf(buf,L"%d",nextFilterNum);
+ }
+ } else ((FilterItem*)items->Get(row))->GetCellText(colid,buf,buflen);
+ }
+ virtual void SortList() {
+ if (sortcol > fields.GetSize()) return;
+ useby=((ListField*)fields.Get(sortcol))->field;
+ usedir=sortdir;
+ qsort(items->GetAll(),items->GetSize(),sizeof(void*),sortFunc_filteritems);
+ }
+ virtual int GetSortDirection() { return sortdir; }
+ virtual int GetSortColumn() { return sortcol; }
+ virtual void ColumnClicked(int col) {
+ if(col == sortcol)
+ sortdir = sortdir?0:1;
+ else {
+ sortdir=0;
+ sortcol=col;
+ }
+ SortList();
+ }
+ virtual void ColumnResize(int col, int newWidth) {
+ if(col >=0 && col < fields.GetSize()) {
+ ListField * lf = (ListField *)fields.Get(col);
+ lf->width = newWidth;
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"colWidth_%d",lf->field);
+ config->WriteInt(buf,newWidth);
+ }
+ }
+ virtual pmpart_t GetArt(int row) { return ((FilterItem*)items->Get(row))->GetArt(); }
+ virtual void SetMode(int mode) {
+ filter->SetMode(mode);
+ int i=fields.GetSize();
+ while(i>=0) { i--; delete (ListField*)fields.Get(i); fields.Del(i); }
+ filter->AddColumns(dev, &fields, config, !!this->cloud);
+ SortColumns();
+ }
+ virtual songid_t GetTrack(int pos) { return 0; }
+};
+
+static void getStars(int stars, wchar_t * buf, int buflen) {
+ wchar_t * r=L"";
+ switch(stars) {
+ case 1: r=L"\u2605"; break;
+ case 2: r=L"\u2605\u2605"; break;
+ case 3: r=L"\u2605\u2605\u2605"; break;
+ case 4: r=L"\u2605\u2605\u2605\u2605"; break;
+ case 5: r=L"\u2605\u2605\u2605\u2605\u2605"; break;
+ }
+ lstrcpyn(buf,r,buflen);
+}
+
+extern void timeToString(__time64_t time, wchar_t * buf, int buflen);
+
+static void timeValue(int totalsecs, wchar_t *dest)
+{
+ int secs=totalsecs%60;
+ int mins=(totalsecs/60)%60;
+ int hours=(totalsecs/3600)%24;
+ int days=(totalsecs/86400);
+ if(days==0) {
+ wsprintf(dest,L"%d:%02d:%02d",hours,mins,secs);
+ } else if(days==1) {
+ wsprintf(dest,L"%d day+%d:%02d:%02d",days,hours,mins,secs);
+ } else {
+ wsprintf(dest,L"%d days+%d:%02d:%02d",days,hours,mins,secs);
+ }
+}
+
+void GetInfoString(wchar_t * buf, Device * dev, int numTracks, __int64 totalSize, int totalPlayLength, int cloud) {
+ wchar_t lengthStr[100]=L"";
+ wchar_t sizeStr[100]=L"";
+ wchar_t availStr[100]=L"";
+ wchar_t devCapacityStr[100]=L"";
+ int usedPercent;
+
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits || (fieldsBits & SUPPORTS_LENGTH)) {
+ lengthStr[0] = L'[';
+ timeValue(totalPlayLength,&lengthStr[1]);
+ wcscat_s(lengthStr,100,L"] ");
+ }
+
+ __int64 available = dev->getDeviceCapacityAvailable();
+ WASABI_API_LNG->FormattedSizeString(sizeStr, ARRAYSIZE(sizeStr), totalSize);
+ WASABI_API_LNG->FormattedSizeString(availStr, ARRAYSIZE(availStr), available);
+ __int64 capacity = dev->getDeviceCapacityTotal();
+ WASABI_API_LNG->FormattedSizeString(devCapacityStr, ARRAYSIZE(devCapacityStr), capacity);
+
+ if(capacity > 0) usedPercent = (int)((((__int64)100)*available) / capacity);
+ else usedPercent = 0;
+ if (!cloud || cloud && available > 0)
+ wsprintf(buf, WASABI_API_LNGSTRINGW(IDS_X_ITEMS_X_AVAILABLE), numTracks, lengthStr, sizeStr, availStr, usedPercent, devCapacityStr);
+ else
+ wsprintf(buf, WASABI_API_LNGSTRINGW(IDS_X_ITEMS_X_AVAILABLE_SLIM), numTracks, lengthStr, sizeStr);
+}
+
+class TracksList : public PrimaryListContents {
+public:
+ __int64 totalSize;
+ int totalPlayLength;
+ int sortcol;
+ int sortdir;
+ C_ItemList * tracks;
+ Device * dev;
+ ArtistAlbumLists * aaList;
+ TracksList(Device * dev,C_Config * config, ArtistAlbumLists * aaList, bool cloud) :
+ dev(dev), aaList(aaList), totalSize(0), totalPlayLength(0), tracks(0) {
+ this->config = config;
+ this->cloud = cloud;
+ this->cloudcol = -1;
+
+ this->sortcol = config->ReadInt(L"sortcol", 0);
+ this->sortdir = config->ReadInt(L"sortdir", 0);
+
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if (!fieldsBits) fieldsBits = -1;
+ if (fieldsBits & SUPPORTS_ARTIST) fields.Add(new ListField(0, 200, WASABI_API_LNGSTRINGW(IDS_ARTIST), config));
+ if (fieldsBits & SUPPORTS_TITLE) fields.Add(new ListField(1, 200, WASABI_API_LNGSTRINGW(IDS_TITLE), config));
+ if (fieldsBits & SUPPORTS_ALBUM) fields.Add(new ListField(2, 200, WASABI_API_LNGSTRINGW(IDS_ALBUM), config));
+ if (fieldsBits & SUPPORTS_LENGTH) fields.Add(new ListField(3, 64, WASABI_API_LNGSTRINGW(IDS_LENGTH), config));
+ if (cloud) fields.Add(new ListField(3+cloud, 27, WASABI_API_LNGSTRINGW(IDS_CLOUD), config));
+ if (fieldsBits & SUPPORTS_TRACKNUM) fields.Add(new ListField(4+cloud, 50, WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER), config));
+ if (fieldsBits & SUPPORTS_DISCNUM) fields.Add(new ListField(5+cloud, 38, WASABI_API_LNGSTRINGW(IDS_DISC), config));
+ if (fieldsBits & SUPPORTS_GENRE) fields.Add(new ListField(6+cloud, 100, WASABI_API_LNGSTRINGW(IDS_GENRE), config));
+ if (fieldsBits & SUPPORTS_YEAR) fields.Add(new ListField(7+cloud, 38, WASABI_API_LNGSTRINGW(IDS_YEAR), config));
+ if (fieldsBits & SUPPORTS_BITRATE) fields.Add(new ListField(8+cloud, 45, WASABI_API_LNGSTRINGW(IDS_BITRATE), config));
+ if (fieldsBits & SUPPORTS_SIZE) fields.Add(new ListField(9+cloud, 90, WASABI_API_LNGSTRINGW(IDS_SIZE), config));
+ if (fieldsBits & SUPPORTS_PLAYCOUNT) fields.Add(new ListField(10+cloud, 64, WASABI_API_LNGSTRINGW(IDS_PLAY_COUNT), config));
+ if (fieldsBits & SUPPORTS_RATING) fields.Add(new ListField(11+cloud, 64, WASABI_API_LNGSTRINGW(IDS_RATING), config));
+ if (fieldsBits & SUPPORTS_LASTPLAYED) fields.Add(new ListField(12+cloud, 120, WASABI_API_LNGSTRINGW(IDS_LAST_PLAYED), config));
+ if (fieldsBits & SUPPORTS_ALBUMARTIST) fields.Add(new ListField(13+cloud, 200, WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST), config, true));
+ if (fieldsBits & SUPPORTS_PUBLISHER) fields.Add(new ListField(14+cloud, 200, WASABI_API_LNGSTRINGW(IDS_PUBLISHER), config, true));
+ if (fieldsBits & SUPPORTS_COMPOSER) fields.Add(new ListField(15+cloud, 200, WASABI_API_LNGSTRINGW(IDS_COMPOSER), config, true));
+ if (fieldsBits & SUPPORTS_MIMETYPE) fields.Add(new ListField(16+cloud, 100, WASABI_API_LNGSTRINGW(IDS_MIME_TYPE), config, true));
+ if (fieldsBits & SUPPORTS_DATEADDED) fields.Add(new ListField(17+cloud, 120, WASABI_API_LNGSTRINGW(IDS_DATE_ADDED), config, true));
+ this->SortColumns();
+
+ if (cloud)
+ {
+ // not pretty but it'll allow us to know the current
+ // position of the cloud column for drawing purposes
+ for(int i = 0; i < fields.GetSize(); i++) {
+ if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud"))
+ {
+ this->cloudcol = ((ListField *)fields.Get(i))->pos;
+ break;
+ }
+ }
+ }
+ }
+ virtual ~TracksList() {
+ config->WriteInt(L"sortcol", sortcol);
+ config->WriteInt(L"sortdir", sortdir);
+ }
+ virtual int GetNumColumns() { return fields.GetSize(); }
+ virtual int GetNumRows() { return (tracks ? tracks->GetSize() : 0); }
+ virtual int GetColumnWidth(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->width;
+ return 0;
+ }
+ virtual wchar_t * GetColumnTitle(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->name;
+ return L"";
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) {
+ buf[0]=0;
+ if(row >= tracks->GetSize() || aaList->bgThread_Handle) return;
+ songid_t s = (songid_t)tracks->Get(row);
+ if(col >=0 && col < fields.GetSize()) {
+ if (cloud)
+ {
+ switch(((ListField *)fields.Get(col))->field) {
+ case 0: dev->getTrackArtist(s,buf,buflen); return;
+ case 1: dev->getTrackTitle(s,buf,buflen); return;
+ case 2: dev->getTrackAlbum(s,buf,buflen); return;
+ case 3: { int l=dev->getTrackLength(s); if (l>=0) wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; }
+ case 4: { dev->getTrackExtraInfo(s,L"cloud",buf,buflen); return; }
+ case 5: { int t=dev->getTrackTrackNum(s); if (t>0) wsprintf(buf,L"%d",t); return; }
+ case 6: { int d = dev->getTrackDiscNum(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 7: dev->getTrackGenre(s,buf,buflen); return;
+ case 8: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 9: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; }
+ case 10: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return;
+ case 11: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 12: getStars(dev->getTrackRating(s),buf,buflen); return;
+ case 13: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return;
+ case 14: dev->getTrackAlbumArtist(s,buf,buflen); return;
+ case 15: dev->getTrackPublisher(s,buf,buflen); return;
+ case 16: dev->getTrackComposer(s,buf,buflen); return;
+ case 17: dev->getTrackMimeType(s,buf,buflen); return;
+ case 18: timeToString(dev->getTrackDateAdded(s),buf,buflen); return;
+ }
+ }
+ else
+ {
+ switch(((ListField *)fields.Get(col))->field) {
+ case 0: dev->getTrackArtist(s,buf,buflen); return;
+ case 1: dev->getTrackTitle(s,buf,buflen); return;
+ case 2: dev->getTrackAlbum(s,buf,buflen); return;
+ case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; }
+ case 4: { int t=dev->getTrackTrackNum(s); if (t>0) wsprintf(buf,L"%d",t); return; }
+ case 5: { int d = dev->getTrackDiscNum(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 6: dev->getTrackGenre(s,buf,buflen); return;
+ case 7: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 8: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; }
+ case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return;
+ case 10: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 11: getStars(dev->getTrackRating(s),buf,buflen); return;
+ case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return;
+ case 13: dev->getTrackAlbumArtist(s,buf,buflen); return;
+ case 14: dev->getTrackPublisher(s,buf,buflen); return;
+ case 15: dev->getTrackComposer(s,buf,buflen); return;
+ case 16: dev->getTrackMimeType(s,buf,buflen); return;
+ case 17: timeToString(dev->getTrackDateAdded(s),buf,buflen); return;
+ }
+ }
+ }
+ }
+ virtual void SortList() {
+ useby = ((ListField*)fields.Get(sortcol))->field;
+ usedir = sortdir;
+ // if a cloud item then adjust things as needed
+ // since we're inserting between genre and year
+ usecloud = cloud;
+ thread_killed = 0;
+ currentDev = dev;
+ qsort(tracks->GetAll(),tracks->GetSize(),sizeof(void*),sortFunc);
+ }
+ virtual void ColumnResize(int col, int newWidth) {
+ if(col >=0 && col < fields.GetSize()) {
+ ListField * lf = (ListField *)fields.Get(col);
+ lf->width = newWidth;
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"colWidth_%d",lf->field);
+ config->WriteInt(buf,newWidth);
+ }
+ }
+ virtual int GetSortColumn() { return sortcol; }
+ virtual int GetSortDirection() { return sortdir; }
+ virtual void ColumnClicked(int col) {
+ if(col == sortcol)
+ sortdir = sortdir?0:1;
+ else {
+ sortdir=0;
+ sortcol=col;
+ }
+ SortList();
+ }
+ virtual void GetInfoString(wchar_t * buf) {
+ ::GetInfoString(buf, dev, tracks->GetSize(), totalSize, totalPlayLength, cloud);
+ }
+ virtual songid_t GetTrack(int pos) { return (songid_t)tracks->Get(pos); }
+ virtual void RemoveTrack(songid_t song) {
+ for(int i=0; i<tracks->GetSize(); i++) {
+ if((songid_t)tracks->Get(i) == song) tracks->Del(i--);
+ }
+ }
+};
+
+static void FreeFilterItemList(C_ItemList * list) {
+ if(!list) return;
+ for(int i=0; i < list->GetSize(); i++) {
+ FilterItem * a = (FilterItem*)list->Get(i);
+ if(a->nextFilter) FreeFilterItemList(a->nextFilter); a->nextFilter=0;
+ delete a;
+ }
+ delete list;
+}
+
+static DWORD WINAPI bgThreadSearchProc(void *tmp)
+{
+ ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp;
+ return (aacList ? aacList->bgSearchThreadProc(tmp) : 0);
+}
+
+static DWORD WINAPI bgThreadLoadProc(void *tmp)
+{
+ ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp;
+ return (aacList ? aacList->bgLoadThreadProc(tmp) : 0);
+}
+
+static DWORD WINAPI bgThreadRefineProc(void *tmp)
+{
+ ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp;
+ return (aacList ? aacList->bgRefineThreadProc(tmp) : 0);
+}
+
+extern HWND hwndMediaView;
+DWORD WINAPI ArtistAlbumLists::bgLoadThreadProc(void *tmp)
+{
+ int l = dev->getPlaylistLength(playlistId);
+
+ for(int i=0; i<l; i++) {
+ songid_t x=dev->getPlaylistTrack(playlistId,i);
+ int t = dev->getTrackType(x);
+ if(type != -1 && t != type) continue;
+ searchedTracks->Add((void*)x);
+ trackList->Add((void*)x);
+ unrefinedTracks->Add((void*)x);
+ }
+
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) {
+ if (filters[0]) FreeFilterItemList(filters[0]->items);
+ filters[0]->items = CompilePrimaryList(searchedTracks);
+ }
+ else {
+ delete filters[i]->items;
+ filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ }
+ filters[i]->SortList();
+ }
+
+ SetRefine(L"");
+
+ if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0);
+ return 0;
+}
+
+DWORD WINAPI ArtistAlbumLists::bgSearchThreadProc(void *tmp)
+{
+ int l = dev->getPlaylistLength(playlistId);
+ C_ItemList allTracks;
+
+ for(int i=0; i<l; i++) {
+ songid_t x=dev->getPlaylistTrack(playlistId,i);
+ int t = dev->getTrackType(x);
+ if(type != -1 && t != type) continue;
+ allTracks.Add((void*)x);
+ }
+
+ searchedTracks = FilterSongs(lastSearch, &allTracks);
+ delete unrefinedTracks;
+ unrefinedTracks = new C_ItemList;
+ l=searchedTracks->GetSize();
+ for(int i=0; i<l; i++) unrefinedTracks->Add(searchedTracks->Get(i));
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) {
+ FreeFilterItemList(filters[0]->items);
+ filters[0]->items = CompilePrimaryList(searchedTracks);
+ }
+ else {
+ delete filters[i]->items;
+ filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ }
+ }
+ SetRefine(L"");
+
+ for(int i=0; i<numFilters; i++) filters[i]->SortList();
+
+ if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0);
+ return 0;
+}
+
+DWORD WINAPI ArtistAlbumLists::bgRefineThreadProc(void *tmp)
+{
+ SetRefine(lastRefine);
+
+ if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0);
+ return 0;
+}
+
+void ArtistAlbumLists::bgQuery_Stop() // exported for other people to call since it is useful (eventually
+{
+ KillTimer(hwndMediaView, 123);
+ if (bgThread_Handle)
+ {
+ thread_killed = bgThread_Kill = 1;
+ WaitForSingleObject(bgThread_Handle, INFINITE);
+ CloseHandle(bgThread_Handle);
+ bgThread_Handle = 0;
+ }
+}
+
+void ArtistAlbumLists::bgQuery(int mode) // only internal used
+{
+ bgQuery_Stop();
+
+ // TODO cache the HWND to avoid confusion
+ SetTimer(hwndMediaView, 123, 200, NULL);
+ DWORD id;
+ bgThread_Kill = 0;
+ bgThread_Handle = CreateThread(NULL, 0, (!mode ? bgThreadLoadProc : (mode == 1 ? bgThreadSearchProc : bgThreadRefineProc)), (LPVOID)this, 0, &id);
+}
+
+ArtistAlbumLists::ArtistAlbumLists(Device * dev, int playlistId, C_Config * config, wchar_t ** filterNames, int numFilters0, int type, bool async) {
+ this->type = type;
+ this->dev = dev;
+ this->playlistId = playlistId;
+ this->numFilters = numFilters0;
+ this->bgThread_Handle = 0;
+ this->async = async;
+ this->lastSearch = 0;
+ this->lastRefine = 0;
+ ZeroMemory(&filters, sizeof(filters));
+
+ if (config->ReadInt(L"savefilter", 1))
+ {
+ lastSearch = wcsdup(config->ReadString(L"savedfilter", L""));
+ lastRefine = wcsdup(config->ReadString(L"savedrefinefilter", L""));
+ }
+
+ Filter * f = NULL;
+ for(int i = 0; i < numFilters; i++) {
+ if(!i) { f = firstFilter = getFilter(filterNames[i]); }
+ else { f->nextFilter = getFilter(filterNames[i]); f = f->nextFilter; }
+ }
+
+ f = firstFilter;
+ for(int i=0; i<numFilters; i++) {
+ filters[i] = new FilterList(i,dev,config,f,this,async);
+ f = f->nextFilter;
+ }
+
+ tracksLC = new TracksList(dev,config,this,async);
+
+ searchedTracks = new C_ItemList;
+ trackList = new C_ItemList;
+ unrefinedTracks = new C_ItemList;
+ if (!async && (!lastSearch || lastSearch && !*lastSearch))
+ {
+ int l = dev->getPlaylistLength(playlistId);
+ for(int i=0; i<l; i++) {
+ songid_t x=dev->getPlaylistTrack(playlistId,i);
+ int t = dev->getTrackType(x);
+ if(type != -1 && t != type) continue;
+ searchedTracks->Add((void*)x);
+ trackList->Add((void*)x);
+ unrefinedTracks->Add((void*)x);
+ }
+ }
+
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) filters[i]->items = CompilePrimaryList(searchedTracks);
+ else filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ filters[i]->SortList();
+ }
+
+ tracksLC->tracks = trackList;
+ tracksLC->dev=dev;
+
+ if (async) bgQuery();
+ else SetRefine((lastRefine ? lastRefine : L""));
+}
+
+ArtistAlbumLists::~ArtistAlbumLists() {
+ if(numFilters && filters[0]) FreeFilterItemList(filters[0]->items);
+ for(int i=0; i<numFilters; i++) {
+ delete filters[i]->filter;
+ if(i!=0) delete filters[i]->items;
+ delete filters[i];
+ }
+ delete trackList;
+ delete searchedTracks;
+ delete unrefinedTracks;
+ delete tracksLC;
+
+ if (lastSearch) free(lastSearch);
+ if (lastRefine) free(lastRefine);
+}
+
+static void parsequicksearch(wchar_t *out, wchar_t *in) // parses a list into a list of terms that we are searching for
+{
+ int inquotes=0, neednull=0;
+ while (in && *in)
+ {
+ wchar_t c=*in++;
+ if (c != L' ' && c != L'\t' && c != L'\"')
+ {
+ neednull=1;
+ *out++=c;
+ }
+ else if (c == L'\"')
+ {
+ inquotes=!inquotes;
+ if (!inquotes)
+ {
+ *out++=0;
+ neednull=0;
+ }
+ }
+ else
+ {
+ if (inquotes) *out++=c;
+ else if (neednull)
+ {
+ *out++=0;
+ neednull=0;
+ }
+ }
+ }
+ *out++=0;
+ *out++=0;
+}
+
+static int in_string(wchar_t *string, wchar_t *substring)
+{
+ if (!string) return 0;
+ if (!*substring) return 1;
+ int l=lstrlen(substring);
+ while (string[0]) if (!_wcsnicmp(string++,substring,l)) return 1;
+ return 0;
+}
+
+C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs, Device * dev, bool cloud)
+{
+ wchar_t filterstr[256] = {0}, filteritems[300] = {0};
+ lstrcpyn(filterstr,filter,256);
+ parsequicksearch(filteritems,filterstr);
+
+ C_ItemList * filtered = new C_ItemList;
+ int l = songs->GetSize();
+ for(int i=0; i<l; i++) {
+ songid_t s = (songid_t)songs->Get(i);
+ wchar_t *p=filteritems;
+ if(p && *p) {
+ while(p && *p) {
+ bool in=false;
+ for(int j=0; j<15; j++) {
+ wchar_t buf[2048] = {0};
+ int buflen=2048;
+ if (cloud)
+ {
+ switch(j) {
+ case 0: dev->getTrackArtist(s,buf,buflen); break;
+ case 1: dev->getTrackTitle(s,buf,buflen); break;
+ case 2: dev->getTrackAlbum(s,buf,buflen); break;
+ case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); break; }
+ case 4: { dev->getTrackExtraInfo(s,L"cloud",buf,buflen); break; }
+ case 5: wsprintf(buf,L"%d",dev->getTrackTrackNum(s)); break;
+ case 6: wsprintf(buf,L"%d",dev->getTrackDiscNum(s)); break;
+ case 7: dev->getTrackGenre(s,buf,buflen); break;
+ case 8: wsprintf(buf,L"%d",dev->getTrackYear(s)); break;
+ case 9: wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),dev->getTrackBitrate(s)); break;
+ case 10: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); break;
+ case 11: wsprintf(buf,L"%d",dev->getTrackPlayCount(s)); break;
+ case 12: getStars(dev->getTrackRating(s),buf,buflen); break;
+ case 13: timeToString(dev->getTrackLastPlayed(s),buf,buflen); break;
+ case 14: dev->getTrackAlbumArtist(s,buf,buflen); break;
+ case 15: dev->getTrackPublisher(s,buf,buflen); break;
+ case 16: dev->getTrackComposer(s,buf,buflen); break;
+ case 17: dev->getTrackMimeType(s,buf,buflen); break;
+ case 18: timeToString(dev->getTrackDateAdded(s),buf,buflen); break;
+ default: lstrcpyn(buf,L"",buflen); break;
+ }
+ }
+ else
+ {
+ switch(j) {
+ case 0: dev->getTrackArtist(s,buf,buflen); break;
+ case 1: dev->getTrackTitle(s,buf,buflen); break;
+ case 2: dev->getTrackAlbum(s,buf,buflen); break;
+ case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); break; }
+ case 4: wsprintf(buf,L"%d",dev->getTrackTrackNum(s)); break;
+ case 5: wsprintf(buf,L"%d",dev->getTrackDiscNum(s)); break;
+ case 6: dev->getTrackGenre(s,buf,buflen); break;
+ case 7: wsprintf(buf,L"%d",dev->getTrackYear(s)); break;
+ case 8: wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),dev->getTrackBitrate(s)); break;
+ case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); break;
+ case 10: wsprintf(buf,L"%d",dev->getTrackPlayCount(s)); break;
+ case 11: getStars(dev->getTrackRating(s),buf,buflen); break;
+ case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); break;
+ case 13: dev->getTrackAlbumArtist(s,buf,buflen); break;
+ case 14: dev->getTrackPublisher(s,buf,buflen); break;
+ case 15: dev->getTrackComposer(s,buf,buflen); break;
+ case 16: dev->getTrackMimeType(s,buf,buflen); break;
+ case 17: timeToString(dev->getTrackDateAdded(s),buf,buflen); break;
+ default: lstrcpyn(buf,L"",buflen); break;
+ }
+ }
+ if(in_string(buf,p)) { in=true; break;}
+ }
+ if(in) p+=lstrlen(p)+1;
+ else break;
+ }
+ }
+ if(p && *p) continue;
+ filtered->Add((void*)s);
+ }
+ return filtered;
+}
+
+C_ItemList * ArtistAlbumLists::FilterSongs(const wchar_t * filter, const C_ItemList * songs)
+{
+ return ::FilterSongs(filter,songs,dev,this->async);
+}
+
+static Filter * firstFil;
+
+static int sortFunc_ssi(const void *elem1, const void *elem2)
+{
+ SortSongItem * a = (SortSongItem *)elem1;
+ SortSongItem * b = (SortSongItem *)elem2;
+ Filter * f = firstFil;
+ while(f) {
+ int r = f->sortFunc(a->dev,a->songid,b->songid);
+ if(r) return r;
+ f = f->nextFilter;
+ }
+ return 0;
+}
+
+C_ItemList * ArtistAlbumLists::CompilePrimaryList(const C_ItemList * songs) {
+ C_ItemList * list = new C_ItemList;
+
+ SortSongItem *songList = (SortSongItem*)calloc(songs->GetSize(), sizeof(SortSongItem));
+ int l=songs->GetSize();
+ for(int i=0; i<l; i++) { songList[i].songid = (songid_t)songs->Get(i); songList[i].dev = dev; }
+
+ firstFil = firstFilter;
+ qsort(songList,l,sizeof(SortSongItem),sortFunc_ssi); //sort it
+
+ FilterItem * items[MAX_FILTERS]={0};
+ Filter * filters[MAX_FILTERS]={0};
+
+ Filter * f = firstFilter;
+ int numFilters=0;
+ while(f) {filters[numFilters++] = f; f=f->nextFilter;}
+
+ for(int i=0; i<l; i++) {
+ songid_t s = songList[i].songid;
+ for(int j=0; j<numFilters; j++) {
+ if(items[j] && filters[j]->isInGroup(dev,s,items[j])) filters[j]->addToGroup(dev,s,items[j]);
+ else {
+ for(int k=j; k<numFilters; k++) {
+ items[k] = filters[k]->newGroup(dev,s);
+ items[k]->nextFilter = new C_ItemList;
+ if(k==0) list->Add(items[k]);
+ else {
+ items[k-1]->nextFilter->Add(items[k]);
+ items[k-1]->numNextFilter++;
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (songList)
+ {
+ free(songList);
+ songList = 0;
+ }
+
+ if(list->GetSize() && ((FilterItem*)list->Get(0))->isWithoutGroup()) {
+ wsprintf(this->filters[0]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X_WITHOUT_X),
+ list->GetSize()-1,(list->GetSize()==2)?this->filters[0]->filter->name:this->filters[0]->filter->namePlural,((FilterItem*)list->Get(0))->numTracks,this->filters[0]->filter->name);
+ }
+ else
+ wsprintf(this->filters[0]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X),
+ list->GetSize(),(list->GetSize()==1)?this->filters[0]->filter->name:this->filters[0]->filter->namePlural);
+ CharLower(this->filters[0]->topString+3);
+ this->filters[0]->tracks = songs->GetSize();
+ return list;
+}
+
+static int sortFunc_filters(const void *elem1, const void *elem2) {
+ FilterItem *a=(FilterItem *)*(void **)elem1;
+ FilterItem *b=(FilterItem *)*(void **)elem2;
+ return a->compareTo(b);
+}
+
+C_ItemList * ArtistAlbumLists::CompileSecondaryList(const C_ItemList * selectedItems, int level, bool updateTopArtist) {
+ int totalTracks=0;
+ C_ItemList * list = new C_ItemList;
+ C_ItemList * collatedlist = new C_ItemList;
+
+ for(int i=0; i < selectedItems->GetSize(); i++) {
+ FilterItem * item = (FilterItem*)selectedItems->Get(i);
+ if (item) {
+ C_ItemList *nf = item->independentNextFilter?item->independentNextFilter:item->nextFilter;
+ for(int j=0; j < nf->GetSize(); j++) list->Add(nf->Get(j));
+ }
+ }
+ qsort(list->GetAll(),list->GetSize(),sizeof(void*),sortFunc_filters);
+
+ FilterItem * curItem=0;
+ for(int i=0; i < list->GetSize(); i++) {
+ FilterItem * item = (FilterItem*)list->Get(i);
+ if(curItem && !curItem->compareTo(item)) {
+ curItem->independentTracks += item->numTracks;
+ curItem->independentSize += item->size;
+ curItem->independentLength += item->length;
+ curItem->independentCloudState += item->cloudState;
+ } else {
+ curItem = item;
+ collatedlist->Add(item);
+ item->independentTracks = item->numTracks;
+ curItem->independentSize = item->size;
+ curItem->independentLength = item->length;
+ curItem->independentCloudState = item->cloudState;
+ if(item->independentNextFilter) delete item->independentNextFilter;
+ item->independentNextFilter = new C_ItemList;
+ }
+ totalTracks+=item->numTracks;
+ for(int k=0; k<item->nextFilter->GetSize(); k++) curItem->independentNextFilter->Add(item->nextFilter->Get(k));
+ }
+ delete list;
+ if(updateTopArtist) this->filters[level-1]->nextFilterNum = collatedlist->GetSize();
+
+ if(collatedlist->GetSize() && ((FilterItem*)collatedlist->Get(0))->isWithoutGroup()) {
+ wsprintf(this->filters[level]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X_WITHOUT_X),
+ collatedlist->GetSize(),(collatedlist->GetSize()==1)?this->filters[level]->filter->name:this->filters[level]->filter->namePlural,((FilterItem*)collatedlist->Get(0))->numTracks,this->filters[level]->filter->name);
+ }
+ else {
+ wsprintf(this->filters[level]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X),
+ collatedlist->GetSize(),(collatedlist->GetSize()==1)?this->filters[level]->filter->name:this->filters[level]->filter->namePlural);
+ }
+ CharLower(this->filters[level]->topString+3);
+ this->filters[level]->tracks=totalTracks;
+ return collatedlist;
+}
+
+// removes song from all relevant lists
+void ArtistAlbumLists::RemoveTrack(songid_t song)
+{
+ if (searchedTracks)
+ {
+ for (int i=0;i<searchedTracks->GetSize();i++)
+ {
+ if (searchedTracks->Get(i) == (void *) song)
+ {
+ searchedTracks->Del(i--);
+ }
+ }
+ }
+}
+
+void ArtistAlbumLists::SelectionChanged(int filterNum, SkinnedListView **listview) {
+ for(int i=filterNum; i<numFilters-1; i++) {
+ int l = listview[i]->listview.GetCount();
+ bool all = (i != filterNum || (ListView_GetSelectedCount(listview[i]->listview.getwnd())==0));
+ if(listview[i]->listview.GetSelected(0) && filters[i]->filter->HaveTopItem()) all=true;
+ C_ItemList selectedItems;
+ int j=0,f=0;
+ if(filters[i]->filter->HaveTopItem()) { j=1; f=1; }
+ for(; j<l; j++) {
+ if(all || listview[i]->listview.GetSelected(j))
+ selectedItems.Add(filters[i]->items->Get(j-f));
+ }
+ delete filters[i+1]->items;
+ filters[i+1]->items = CompileSecondaryList(&selectedItems,i+1,false);
+ filters[i+1]->SortList();
+ listview[i+1]->UpdateList();
+ }
+
+ C_ItemList * tracks = new C_ItemList;
+
+ C_ItemList * selectedItems[MAX_FILTERS]={0};
+
+ for(int i=0; i<=filterNum; i++) {
+ bool all = (ListView_GetSelectedCount(listview[i]->listview.getwnd())==0);
+ if(listview[i]->listview.GetSelected(0) && filters[i]->filter->HaveTopItem()) all=true;
+ if(all) selectedItems[i] = NULL;
+ else {
+ selectedItems[i] = new C_ItemList;
+ int m = filters[i]->items->GetSize();
+ int offset = filters[i]->filter->HaveTopItem()?1:0;
+ for(int k=0; k<m; k++) if(listview[i]->listview.GetSelected(k+offset)) selectedItems[i]->Add(filters[i]->items->Get(k));
+ }
+ }
+
+ int l=searchedTracks->GetSize();
+ for(int j=0; j<l; j++) {
+ songid_t track = (songid_t)searchedTracks->Get(j);
+ bool matches=true;
+ for(int i=0; i<=filterNum; i++) {
+ matches=false;
+ if(selectedItems[i]) {
+ for(int k=0; k<selectedItems[i]->GetSize(); k++)
+ if(filters[i]->filter->isInGroup(dev,track,(FilterItem*)selectedItems[i]->Get(k))) { matches=true; break; }
+ }
+ else matches=true;
+ if(!matches) break;
+ }
+ if(matches) //woo hoo, its in!
+ tracks->Add((void*)track);
+ }
+ delete unrefinedTracks;
+ unrefinedTracks = tracks;
+ SetRefine(L"");
+ for(int i=0; i<=filterNum; i++)
+ {
+ delete selectedItems[i];
+ }
+}
+
+void ArtistAlbumLists::SetRefine(const wchar_t * str, bool async) {
+ if (!async)
+ {
+ C_ItemList * refinedTracks = FilterSongs(str,unrefinedTracks);
+ C_ItemList * oldTrackList = trackList;
+ trackList = refinedTracks;
+ tracksLC->tracks = trackList;
+ delete oldTrackList;
+ tracksLC->SortList();
+
+ // get stats
+ __int64 fileSize=0;
+ int playLength=0;
+ int millis=0;
+ for(int i=0; i < trackList->GetSize(); i++) {
+ songid_t s = (songid_t)trackList->Get(i);
+ fileSize += (__int64)dev->getTrackSize(s);
+ playLength += dev->getTrackLength(s)/1000;
+ millis += dev->getTrackLength(s)%1000;
+ }
+ playLength += millis/1000;
+ tracksLC->totalPlayLength=playLength;
+ tracksLC->totalSize=fileSize;
+ }
+ else
+ {
+ if (lastRefine)
+ {
+ free(lastRefine);
+ lastRefine = 0;
+ }
+ lastRefine = wcsdup(str);
+
+ bgQuery(2);
+ }
+}
+
+void ArtistAlbumLists::SetSearch(const wchar_t * str, bool async) {
+ bgQuery_Stop();
+
+ if (!async)
+ {
+ delete searchedTracks; searchedTracks = NULL;
+ C_ItemList allTracks;
+
+ int l = dev->getPlaylistLength(playlistId);
+ for(int i=0; i<l; i++) {
+ songid_t x = dev->getPlaylistTrack(playlistId,i);
+ if(type != -1 && dev->getTrackType(x) != type) continue;
+ allTracks.Add((void*)x);
+ }
+
+ searchedTracks = FilterSongs(str,&allTracks);
+ delete unrefinedTracks;
+ unrefinedTracks = new C_ItemList;
+ l=searchedTracks->GetSize();
+ for(int i=0; i<l; i++) unrefinedTracks->Add(searchedTracks->Get(i));
+
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) {
+ FreeFilterItemList(filters[0]->items);
+ filters[0]->items = CompilePrimaryList(searchedTracks);
+ }
+ else {
+ delete filters[i]->items;
+ filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ }
+ }
+
+ SetRefine(L"");
+
+ for(int i=0; i<numFilters; i++) filters[i]->SortList();
+ }
+ else
+ {
+ if (lastSearch)
+ {
+ free(lastSearch);
+ lastSearch = 0;
+ }
+
+ if (!(str && *str))
+ {
+ if (unrefinedTracks) delete unrefinedTracks;
+ if (searchedTracks) delete searchedTracks;
+ if (trackList) delete trackList;
+ searchedTracks = new C_ItemList;
+ trackList = new C_ItemList;
+ unrefinedTracks = new C_ItemList;
+ }
+ else
+ {
+ lastSearch = wcsdup(str);
+ }
+ bgQuery((str && *str));
+ }
+}
+
+ListContents * ArtistAlbumLists::GetFilterList(int i) { return filters[i]; }
+PrimaryListContents * ArtistAlbumLists::GetTracksList() { return this->tracksLC; } \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h
new file mode 100644
index 00000000..be868d0c
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h
@@ -0,0 +1,64 @@
+#ifndef _ARTISTALBUMLISTS_H_
+#define _ARTISTALBUMLISTS_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <time.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "pmp.h"
+#include "SkinnedListView.h"
+#include "config.h"
+
+class Filter;
+class FilterList;
+class TracksList;
+
+#define MAX_FILTERS 3
+extern int thread_killed;
+
+class ArtistAlbumLists {
+protected:
+ Device * dev;
+ int playlistId;
+ C_ItemList * searchedTracks;
+ C_ItemList * unrefinedTracks;
+ C_ItemList * trackList;
+ C_ItemList * CompilePrimaryList(const C_ItemList * songs);
+ C_ItemList * CompileSecondaryList(const C_ItemList * selectedArtists, int level, bool updateTopArtist);
+ C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs);
+ int numFilters;
+ FilterList *filters[MAX_FILTERS];
+ Filter * firstFilter;
+ TracksList *tracksLC;
+ int type;
+ wchar_t * lastSearch;
+ wchar_t * lastRefine;
+public:
+ ArtistAlbumLists(Device * dev, int playlistId, C_Config * config, wchar_t ** filterNames, int numFilters, int type=-1, bool async=false);
+ ~ArtistAlbumLists();
+ void SetRefine(const wchar_t * str, bool async=false);
+ void SetSearch(const wchar_t * str, bool async=false);
+ void SelectionChanged(int filterNum, SkinnedListView **listview);
+ ListContents * GetFilterList(int i);
+ PrimaryListContents * GetTracksList();
+ void RemoveTrack(songid_t song); // removes song from all relevant lists
+
+ // used for threaded background scans (mainly aimed for cloud support but could be used for other devices if needed)
+ DWORD WINAPI bgLoadThreadProc(void *tmp);
+ DWORD WINAPI bgSearchThreadProc(void *tmp);
+ DWORD WINAPI bgRefineThreadProc(void *tmp);
+ void bgQuery_Stop();
+ int bgThread_Kill;
+ HANDLE bgThread_Handle;
+ bool async;
+ void bgQuery(int mode=0);
+};
+
+#endif //_ARTISTALBUMLISTS_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/DeviceCommands.cpp b/Src/Plugins/Library/ml_pmp/DeviceCommands.cpp
new file mode 100644
index 00000000..5c34a84a
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceCommands.cpp
@@ -0,0 +1,192 @@
+#include "main.h"
+#include "DeviceCommands.h"
+
+#include <strsafe.h>
+PortableCommand::PortableCommand(const char *name, int title, int description)
+ : name(name), title(title), description(description)
+{
+}
+
+const char *PortableCommand::GetName()
+{
+ return name;
+}
+
+HRESULT PortableCommand::GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT PortableCommand::GetDisplayName(wchar_t *buffer, size_t bufferSize)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(title, buffer, bufferSize);
+ return S_OK;
+}
+
+HRESULT PortableCommand::GetDescription(wchar_t *buffer, size_t bufferSize)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(description, buffer, bufferSize);
+ return S_OK;
+}
+
+#define CBCLASS PortableCommand
+START_DISPATCH;
+CB(API_GETNAME, GetName);
+CB(API_GETICON, GetIcon);
+CB(API_GETDISPLAYNAME, GetDisplayName);
+CB(API_GETDESCRIPTION, GetDescription);
+END_DISPATCH;
+#undef CBCLASS
+
+
+BOOL SetDeviceCommandInfo(DeviceCommandInfo *info, const char *name, DeviceCommandFlags flags)
+{
+ if (NULL == info)
+ return FALSE;
+
+ info->name = name;
+ info->flags = flags;
+ return TRUE;
+}
+
+/* -------------- */
+DeviceCommand::DeviceCommand(const char *name, DeviceCommandFlags flags)
+: name(name), flags(flags)
+{
+}
+
+DeviceCommand::DeviceCommand(const DeviceCommandInfo *commandInfo)
+: name(commandInfo->name), flags(commandInfo->flags)
+{
+}
+
+const char *DeviceCommand::GetName()
+{
+ return name;
+}
+
+HRESULT DeviceCommand::GetFlags(DeviceCommandFlags *flags)
+{
+ *flags = this->flags;
+ return 0;
+}
+
+
+#define CBCLASS DeviceCommand
+START_DISPATCH;
+REFERENCE_COUNTED;
+CB(API_GETNAME, GetName);
+CB(API_GETFLAGS, GetFlags);
+END_DISPATCH;
+#undef CBCLASS
+
+
+
+DeviceCommandEnumerator::DeviceCommandEnumerator(const DeviceCommandInfo *commandInfoList, size_t listSize)
+ : position(0), commands(NULL), count(0)
+{
+ if (NULL != commandInfoList &&
+ 0 != listSize)
+ {
+ commands = (DeviceCommand**)calloc(listSize, sizeof(DeviceCommand*));
+ if (NULL != commands)
+ {
+ for(count = 0; count < listSize; count++)
+ {
+ commands[count] = new DeviceCommand(&commandInfoList[count]);
+ }
+ }
+ }
+}
+
+DeviceCommandEnumerator::~DeviceCommandEnumerator()
+{
+ if (NULL != commands)
+ {
+ while(count--)
+ commands[count]->Release();
+
+ free(commands);
+ }
+
+}
+
+HRESULT DeviceCommandEnumerator::Next(ifc_devicesupportedcommand **buffer, size_t bufferMax, size_t *fetched)
+{
+ size_t available, copied, index;
+ DeviceCommand **source;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ if (0 == bufferMax)
+ return E_INVALIDARG;
+
+ if (position >= count)
+ {
+ if (NULL != fetched)
+ *fetched = 0;
+
+ return S_FALSE;
+ }
+
+ available = count - position;
+ copied = ((available > bufferMax) ? bufferMax : available);
+
+ source = commands + position;
+ CopyMemory(buffer, source, copied * sizeof(ifc_devicesupportedcommand*));
+
+ for(index = 0; index < copied; index++)
+ buffer[index]->AddRef();
+
+ position += copied;
+
+ if (NULL != fetched)
+ *fetched = copied;
+
+ return (bufferMax == copied) ? S_OK : S_FALSE;
+}
+
+HRESULT DeviceCommandEnumerator::Reset(void)
+{
+ position=0;
+ return S_OK;
+}
+
+HRESULT DeviceCommandEnumerator::Skip(size_t count)
+{
+ position += count;
+ if (position > this->count)
+ position = this->count;
+ return (position < this->count) ? S_OK : S_FALSE;
+}
+
+HRESULT DeviceCommandEnumerator::GetCount(size_t *count)
+{
+ if (NULL == count)
+ return E_POINTER;
+
+ *count = this->count;
+
+ return S_OK;
+}
+
+
+#define CBCLASS DeviceCommandEnumerator
+START_DISPATCH;
+CB(API_NEXT, Next);
+CB(API_RESET, Reset);
+CB(API_SKIP, Skip);
+CB(API_GETCOUNT, GetCount);
+REFERENCE_COUNTED;
+END_DISPATCH;
+#undef CBCLASS
+
+
+
diff --git a/Src/Plugins/Library/ml_pmp/DeviceCommands.h b/Src/Plugins/Library/ml_pmp/DeviceCommands.h
new file mode 100644
index 00000000..3aa237d2
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceCommands.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "../nu/refcount.h"
+#include "../devices/ifc_devicecommand.h"
+#include "../devices/ifc_devicesupportedcommand.h"
+#include "../devices/ifc_devicesupportedcommandenum.h"
+class PortableCommand : public ifc_devicecommand
+{
+public:
+ PortableCommand(const char *name, int title, int description);
+ const char *name;
+ int title;
+ int description;
+
+ const char *GetName();
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height);
+
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize);
+
+ HRESULT GetDescription(wchar_t *buffer, size_t bufferSize);
+RECVS_DISPATCH;
+};
+
+typedef struct DeviceCommandInfo
+{
+ const char *name;
+ DeviceCommandFlags flags;
+} DeviceCommandInfo;
+
+BOOL SetDeviceCommandInfo(DeviceCommandInfo *info, const char *name, DeviceCommandFlags flags);
+
+class DeviceCommand : public Countable<ifc_devicesupportedcommand>
+{
+public:
+ DeviceCommand(const char *name, DeviceCommandFlags flags);
+ DeviceCommand(const DeviceCommandInfo *commandInfo);
+
+public:
+ const char *GetName();
+ HRESULT GetFlags(DeviceCommandFlags *flags);
+ REFERENCE_COUNT_IMPLEMENTATION;
+
+public:
+ const char *name;
+ DeviceCommandFlags flags;
+RECVS_DISPATCH;
+};
+
+class DeviceCommandEnumerator : public Countable<ifc_devicesupportedcommandenum>
+{
+public:
+ DeviceCommandEnumerator(const DeviceCommandInfo *commandInfoList, size_t listSize);
+ ~DeviceCommandEnumerator();
+
+ HRESULT Next(ifc_devicesupportedcommand **buffer, size_t bufferMax, size_t *count);
+ HRESULT Reset(void);
+ HRESULT Skip(size_t count);
+ HRESULT GetCount(size_t *count);
+ REFERENCE_COUNT_IMPLEMENTATION;
+
+private:
+ size_t position;
+ DeviceCommand **commands;
+ size_t count;
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_pmp/DeviceView.cpp b/Src/Plugins/Library/ml_pmp/DeviceView.cpp
new file mode 100644
index 00000000..6f2b8644
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceView.cpp
@@ -0,0 +1,2714 @@
+//#define _WIN32_WINNT 0x0400
+#include "../Winamp/buildType.h"
+#include "main.h"
+#include "DeviceView.h"
+
+//#include <commctrl.h>
+#include "nu/AutoWide.h"
+#include "nu/AutoChar.h"
+#include "../nu/AutoUrl.h"
+#include "SkinnedListView.h"
+#include "../playlist/api_playlistmanager.h"
+#include "../playlist/ifc_playlistdirectorycallback.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+#include "api__ml_pmp.h"
+#include <shlwapi.h>
+#include <time.h>
+#include "metadata_utils.h"
+#include "../ml_wire/ifc_podcast.h"
+#include "./local_menu.h"
+#include "IconStore.h"
+#include "../devices/ifc_deviceevent.h"
+#include "metadata_utils.h"
+#include "../nu/sort.h"
+#include "resource1.h"
+#include <strsafe.h>
+#include "../nu/MediaLibraryInterface.h"
+
+extern C_ItemList devices;
+extern C_Config * global_config;
+
+extern INT_PTR CALLBACK pmp_artistalbum_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern INT_PTR CALLBACK pmp_playlist_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int IPC_GET_CLOUD_HINST = -1, IPC_LIBRARY_PLAYLISTS_REFRESH = -1;
+HINSTANCE cloud_hinst = 0;
+int currentViewedPlaylist=0;
+HNAVITEM cloudQueueTreeItem=NULL;
+LinkedQueue cloudTransferQueue, cloudFinishedTransfers;
+int cloudTransferProgress = 0;
+DeviceView * currentViewedDevice=NULL;
+
+volatile size_t TransferContext::paused_all = 0;
+
+extern void UpdateTransfersListView(bool softUpdate, CopyInst * item=NULL);
+extern void UpdateDevicesListView(bool softUpdate);
+extern INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern HWND mainMessageWindow;
+extern prefsDlgRecW prefsPage;
+extern int prefsPageLoaded;
+static int thread_id;
+
+static bool copySettings(wchar_t * ssrc, wchar_t * sdest);
+static __int64 fileSize(wchar_t * filename);
+static void removebadchars(wchar_t *s);
+
+extern ThreadID *transfer_thread;
+int TransferThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id);
+
+INT_PTR CALLBACK pmp_queue_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK pmp_video_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+DeviceView::DeviceView(Device *dev)
+ : activityRunning(FALSE), navigationItemCreated(FALSE), usedSpace(0), totalSpace(0)
+{
+ memset(name, 0, sizeof(name));
+ queueActiveIcon = isCloudDevice = 0;
+ treeItem = videoTreeItem = queueTreeItem = 0;
+ connection_type = "USB";
+ display_type = "Portable Media Player";
+ metadata_fields = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!metadata_fields)
+ metadata_fields = -1;
+ dev->extraActions(DEVICE_GET_CONNECTION_TYPE, (intptr_t)&connection_type, 0, 0);
+ isCloudDevice = (!lstrcmpiA(connection_type, "cloud"));
+ dev->extraActions(DEVICE_GET_DISPLAY_TYPE, (intptr_t)&display_type, 0, 0);
+ ref_count = 1;
+ if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0) == 0)
+ {
+ // fallback
+ GUID name_guid;
+ CoCreateGuid(&name_guid);
+ StringCbPrintfA(name, sizeof(name), "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ (int)name_guid.Data1, (int)name_guid.Data2, (int)name_guid.Data3,
+ (int)name_guid.Data4[0], (int)name_guid.Data4[1],
+ (int)name_guid.Data4[2], (int)name_guid.Data4[3],
+ (int)name_guid.Data4[4], (int)name_guid.Data4[5],
+ (int)name_guid.Data4[6], (int)name_guid.Data4[7] );
+ }
+
+ wchar_t inifile[MAX_PATH] = {0};
+ dev->extraActions(DEVICE_GET_INI_FILE,(intptr_t)inifile,0,0);
+ if(!inifile[0])
+ {
+ wchar_t name[256] = {0};
+ dev->getPlaylistName(0,name,256);
+ removebadchars(name);
+ // build this slow so we make sure each directory exists
+ PathCombine(inifile, WASABI_API_APP->path_getUserSettingsPath(), L"Plugins");
+ CreateDirectory(inifile, NULL);
+ PathAppend(inifile, L"ml");
+ CreateDirectory(inifile, NULL);
+ wchar_t ini_filespec[MAX_PATH] = {0};
+ StringCchPrintf(ini_filespec, MAX_PATH, L"ml_pmp_device_%s.ini",name);
+ PathAppend(inifile, ini_filespec);
+ }
+
+ if(fileSize(inifile) <= 0) copySettings(global_config->GetIniFile(),inifile); // import old settings
+ config = new C_Config(inifile,L"ml_pmp",global_config);
+
+ currentTransferProgress = 0;
+ transferRate=0;
+ commitNeeded=false;
+ this->dev = dev;
+ wchar_t deviceName[256]=L"";
+ dev->getPlaylistName(0,deviceName,sizeof(deviceName)/sizeof(wchar_t));
+
+ if (!isCloudDevice) videoView = config->ReadInt(L"showVideoView",dev->extraActions(DEVICE_SUPPORTS_VIDEO,0,0,0));
+ else videoView = 0;
+
+ prefsDlgRecW *parentPrefs = (prefsDlgRecW *)dev->extraActions(DEVICE_GET_PREFS_PARENT, 0, 0, 0);
+ if (!parentPrefs)
+ {
+ // only add it when we're using our own root page, otherwise skip this
+ if (!prefsPageLoaded)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ prefsPageLoaded+=1;
+ }
+
+ if (lstrcmpi(deviceName, L"all_sources"))
+ {
+ devPrefsPage.hInst=WASABI_API_LNG_HINST;
+ devPrefsPage.where=(parentPrefs ? (intptr_t)parentPrefs : (intptr_t)&prefsPage);
+ devPrefsPage.dlgID=IDD_CONFIG;
+ devPrefsPage.name=_wcsdup(deviceName);
+ devPrefsPage.proc=config_dlgproc;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&devPrefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ else
+ {
+ memset(&devPrefsPage, 0, sizeof(prefsDlgRecW));
+ }
+
+ UpdateSpaceInfo(TRUE, FALSE);
+
+ threadKillswitch = 0;
+ transferContext.transfer_thread = WASABI_API_THREADPOOL->ReserveThread(api_threadpool::FLAG_LONG_EXECUTION);
+ transferContext.dev = this;
+ WASABI_API_THREADPOOL->AddHandle(transferContext.transfer_thread, transferContext.notifier, TransferThreadPoolFunc, &transferContext, thread_id, api_threadpool::FLAG_LONG_EXECUTION);
+ thread_id++;
+
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ ifc_device *registered_device = this;
+ AGAVE_API_DEVICEMANAGER->DeviceRegister(&registered_device, 1);
+ }
+ //hTransferThread = CreateThread(NULL, 0, ThreadFunc_Transfer, (LPVOID)this, 0, &dwThreadId);
+ /*
+ if(dev->getDeviceCapacityTotal() > L3000000000) SyncConnectionDefault=1;
+ else SyncConnectionDefault=2;
+ */
+ SyncConnectionDefault=0; // default off for now.
+ if (!isCloudDevice)
+ {
+ // ok all started. Now do any "on connect" actions...
+ time_t lastSync = (time_t)config->ReadInt(L"syncOnConnect_time",0);
+ time_t now = time(NULL);
+ //int diff = now - lastSync;
+ double diff = difftime(now,lastSync);
+ config->WriteInt(L"syncOnConnect_time",(int)now);
+ if(diff > config->ReadInt(L"syncOnConnect_hours",12)*3600)
+ {
+ switch(config->ReadInt(L"syncOnConnect",SyncConnectionDefault))
+ {
+ case 1:
+ {
+ if (!isCloudDevice) Sync(true);
+ //else CloudSync(true);
+ }
+ break;
+ case 2: Autofill(); break;
+ }
+ }
+ }
+ if (!AGAVE_API_DEVICEMANAGER)
+ RegisterViews(0);
+}
+HNAVITEM GetNavigationRoot(BOOL forceCreate);
+HNAVITEM NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0);
+
+void DeviceView::RegisterViews(HNAVITEM parent)
+{
+ NAVINSERTSTRUCT nis = {0};
+ NAVITEM *item = 0;
+ wchar_t buffer[128] = {0};
+
+ item = &nis.item;
+
+ if(!parent)
+ {
+ MLTREEIMAGE devIcon;
+ wchar_t deviceName[256]=L"";
+
+ devIcon.resourceId = IDR_DEVICE_ICON;
+ devIcon.hinst = plugin.hDllInstance;
+ dev->extraActions(DEVICE_SET_ICON,(intptr_t)&devIcon,0,0);
+
+ dev->getPlaylistName(0,deviceName,sizeof(deviceName)/sizeof(wchar_t));
+
+ nis.hParent = GetNavigationRoot(TRUE);
+ nis.hInsertAfter = NCI_LAST;
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_IMAGE | NIMF_IMAGESEL;
+
+ item->pszText = deviceName;
+ item->pszInvariant = nis.item.pszText;
+ item->style = NIS_HASCHILDREN;
+ item->styleMask = item->style,
+ item->iImage = icon_store.GetResourceIcon(devIcon.hinst, MAKEINTRESOURCE(devIcon.resourceId));
+ item->iSelectedImage = item->iImage;
+
+ treeItem = parent = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+
+ navigationItemCreated = TRUE;
+ }
+ else
+ {
+ treeItem = parent;
+ navigationItemCreated = FALSE;
+
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_STYLE;
+ item->hItem = treeItem;
+ item->style = NIS_HASCHILDREN;
+ item->styleMask = NIS_HASCHILDREN;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, item);
+
+ /* create transfer view */
+ // TODO: create this view dynamically
+ HNAVITEM cloud = 0;
+ if (isCloudDevice)
+ {
+ cloud = NavigationItem_Find(0, L"cloud_sources", TRUE);
+ if (cloud != NULL) parent = cloud;
+ }
+
+#if 0
+ int mode = gen_mlconfig->ReadInt(L"txviewpos", 0);
+ if (mode == 1)
+ {
+ nis.hParent = cloud;//parent;
+ nis.hInsertAfter = NCI_FIRST;
+ }
+ else if (mode == 2)
+ {
+ nis.hParent = NavigationItem_Find(0, L"ml_devices_root", TRUE);
+ nis.hInsertAfter = NCI_FIRST;
+ }
+#else
+ nis.hParent = parent;
+#endif
+
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ item->pszText = WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERS, buffer, 128);
+ item->pszInvariant = (isCloudDevice ? L"cloud_transfers" : L"transfers");
+ item->iImage = icon_store.GetQueueIcon();
+ item->iSelectedImage = nis.item.iImage;
+
+#if 0
+ if (!cloudQueueTreeItem && isCloudDevice)
+ {
+ NAVINSERTSTRUCT nis2 = {0};
+ nis2.item.cbSize = sizeof(NAVITEM);
+ nis2.item.pszText = WASABI_API_LNGSTRINGW(IDS_ADD_SOURCE);
+ nis2.item.pszInvariant = L"cloud_add_sources";
+ nis2.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
+ nis2.item.iImage = nis2.item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD_ADD_SOURCE);
+ nis2.hParent = parent;
+ nis2.hInsertAfter = NCI_FIRST;
+ HNAVITEM item = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis2);
+ if (mode == 1) nis.hInsertAfter = item;
+ queueTreeItem = cloudQueueTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+
+#if 0
+ nis2.item.pszText = L"BYOS";//WASABI_API_LNGSTRINGW(IDS_ADD_SOURCE);
+ nis2.item.pszInvariant = L"cloud_byos";
+ nis2.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
+ nis2.item.iImage = nis2.item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD_ADD_BYOS);
+ nis2.hParent = parent;
+ nis2.hInsertAfter = nis.hInsertAfter;
+ MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis2);
+#endif
+ }
+ else
+ queueTreeItem = cloudQueueTreeItem;
+#endif
+ queueTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+
+ /* create video view */
+ if (videoView)
+ {
+ nis.hParent = parent;
+ nis.hInsertAfter = NCI_LAST;
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ item->pszText = WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO, buffer, 128);
+ item->pszInvariant = L"video";
+ item->iImage = icon_store.GetVideoIcon();
+ item->iSelectedImage = nis.item.iImage;
+
+ videoTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+ else
+ videoTreeItem = 0;
+
+ /* create playlists */
+ int l = (dev ? dev->getPlaylistCount() : 0);
+ for(int i=1; i<l; i++)
+ {
+ AddPlaylistNode(i);
+ }
+}
+
+DeviceView::~DeviceView()
+{
+ if(configDevice == this) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_OPENPREFSTOPAGE);
+
+ // remove it when we're removed what we added
+ int lastPrefsPageLoaded = prefsPageLoaded;
+ prefsPageLoaded-=1;
+ if(lastPrefsPageLoaded == 1)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_REMOVE_PREFS_DLG);
+ }
+
+ //OutputDebugString(L"device unloading started");
+ // get rid of the transfer thread
+ threadKillswitch=1;
+ transferContext.WaitForKill();
+ if(threadKillswitch != 100)
+ {
+ /*OutputDebugString(L"FUCKO");*/
+ }
+
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&devPrefsPage,IPC_REMOVE_PREFS_DLG);
+ free(devPrefsPage.name);
+ //OutputDebugString(L"device unloading finished");
+ delete config;
+}
+
+void DeviceView::SetVideoView(BOOL enabled)
+{
+ videoView=enabled;
+ config->WriteInt(L"showVideoView",videoView);
+ if(videoView)
+ {
+ /* add video before the playlists */
+ wchar_t buffer[128] = {0};
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO, buffer, 128);
+ nis.item.pszInvariant = L"video";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.hParent = treeItem;
+ nis.hInsertAfter = NCI_FIRST;
+ nis.item.iImage = icon_store.GetVideoIcon();
+ nis.item.iSelectedImage = nis.item.iImage;
+ videoTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+ else
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, videoTreeItem);
+ videoTreeItem = 0;
+ }
+}
+
+static void removebadchars(wchar_t *s)
+{
+ while (s && *s)
+ {
+ if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|')
+ *s = L'_';
+ s = CharNextW(s);
+ }
+}
+
+static __int64 fileSize(wchar_t * filename)
+{
+ WIN32_FIND_DATA f= {0};
+ HANDLE h = FindFirstFileW(filename,&f);
+ if(h == INVALID_HANDLE_VALUE) return -1;
+ FindClose(h);
+ ULARGE_INTEGER i;
+ i.HighPart = f.nFileSizeHigh;
+ i.LowPart = f.nFileSizeLow;
+ return i.QuadPart;
+}
+
+static bool copySettings(wchar_t * ssrc, wchar_t * sdest)
+{
+ FILE * src, * dest;
+ src=_wfopen(ssrc,L"rt");
+ if(!src) return false;
+ dest=_wfopen(sdest,L"wt");
+ if(!dest)
+ {
+ fclose(src); return false;
+ }
+ wchar_t buf[1024]=L"";
+ bool insection=false;
+ while(fgetws(buf,1024,src))
+ {
+ if(buf[0]==L'[' && wcslen(buf)>1) if(buf[wcslen(buf)-2]==L']') insection=false;
+ if(wcscmp(&buf[0],L"[ml_pmp]\n")==0) insection=true;
+ if(insection) fputws(&buf[0],dest);
+ }
+ fclose(src);
+ fclose(dest);
+ return true;
+}
+
+HNAVITEM DeviceView::AddPlaylistNode(int id)
+{
+ NAVINSERTSTRUCT nis = {0};
+ wchar_t title[256] = {0}, name[128] = {0};
+
+ dev->getPlaylistName(id, title , ARRAYSIZE(title));
+
+ StringCchPrintf(name, ARRAYSIZE(name), L"ml_pmp_playlist_%d", id);
+
+ nis.hParent = treeItem;
+ nis.hInsertAfter = NCI_LAST;
+
+ memset(&nis.item, 0, sizeof(nis.item));
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = title;
+ nis.item.pszInvariant = name;
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
+ nis.item.iImage = icon_store.GetPlaylistIcon();
+ nis.item.iSelectedImage = nis.item.iImage;
+ nis.item.style = NIS_ALLOWEDIT;
+ nis.item.styleMask = nis.item.style;
+
+ HNAVITEM item = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ playlistTreeItems.push_back(item);
+
+ return item;
+}
+
+int DeviceView::CreatePlaylist(wchar_t * name, bool silent)
+{
+ HNAVITEM item;
+ int playlistId;
+
+ if (NULL == name)
+ {
+ int count, slot, length;
+ wchar_t buffer[512] = {0}, buffer2[ARRAYSIZE(buffer)] = {0};
+ BOOL foundMatch;
+
+ name = WASABI_API_LNGSTRINGW_BUF(IDS_NEW_PLAYLIST, buffer, ARRAYSIZE(buffer));
+
+ count = dev->getPlaylistCount();
+ slot = 1;
+ length = -1;
+
+ do
+ {
+ foundMatch = FALSE;
+ for (int i = 1; i < count; i++)
+ {
+
+ dev->getPlaylistName(i, buffer2, ARRAYSIZE(buffer2));
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, buffer2, -1, name, -1))
+ {
+ foundMatch = TRUE;
+
+ if(name != buffer)
+ {
+ if (FAILED(StringCchCopy(buffer, ARRAYSIZE(buffer), name)))
+ return -1;
+ }
+
+ if (-1 == length)
+ {
+ wchar_t *end;
+ length = lstrlen(buffer);
+ end = buffer + length;
+
+ if(length > 2 && L')' == *(--end))
+ {
+ unsigned short charType;
+
+ for(wchar_t *begin = --end; begin != buffer; begin--)
+ {
+ if (L'(' == *begin)
+ {
+ if (begin > buffer && L' ' == *(--begin))
+ {
+ length = (int)(intptr_t)(begin - buffer);
+ slot = 0;
+ }
+ break;
+ }
+ else if (FALSE == GetStringTypeW(CT_CTYPE1, begin, 1, &charType) ||
+ 0 == (C1_DIGIT & charType))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ slot++;
+
+ if (1 == slot)
+ buffer[length] = L'\0';
+ else if (FAILED(StringCchPrintf(buffer + length, ARRAYSIZE(buffer) - length, L" (%d)", slot)))
+ return false;
+
+ break;
+ }
+ }
+ } while(FALSE != foundMatch);
+ }
+
+ playlistId = dev->newPlaylist(name);
+ if(playlistId == -1)
+ return -1; // failed
+
+ item = AddPlaylistNode(playlistId);
+ if (NULL == item)
+ {
+ dev->deletePlaylist(playlistId);
+ return -1;
+ }
+
+ DevicePropertiesChanges();
+
+ if(!silent)
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+
+ return playlistId;
+}
+
+void DeviceView::RenamePlaylist(int playlistId)
+{
+ HNAVITEM item;
+
+ item = NULL;
+
+ if (0 == playlistId)
+ {
+ if (0 != dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ item = treeItem;
+ }
+ else
+ {
+ if (playlistId > 0 && playlistId <= (int)playlistTreeItems.size())
+ item = playlistTreeItems[playlistId - 1];
+ }
+
+ if (NULL != item)
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+}
+
+size_t DeviceView::GetPlaylistName(wchar_t *buffer, size_t bufferSize, int playlistId,
+ const wchar_t *defaultName, BOOL quoteSpaces)
+{
+ size_t length;
+
+ if (NULL == buffer || 0 == bufferSize)
+ return 0;
+
+ buffer[0] = L'\0';
+ if (NULL != dev)
+ dev->getPlaylistName(playlistId, buffer, bufferSize);
+
+ if (FAILED(StringCchLength(buffer, bufferSize, &length)))
+ return 0;
+
+ if (0 == length)
+ {
+ if (NULL != defaultName)
+ {
+ if (FALSE != IS_INTRESOURCE(defaultName))
+ WASABI_API_LNGSTRINGW_BUF((int)(intptr_t)defaultName, buffer, bufferSize);
+ else
+ {
+ if (FAILED(StringCchCopy(buffer, bufferSize, defaultName)))
+ return 0;
+ }
+
+ if (FAILED(StringCchLength(buffer, bufferSize, &length)))
+ return 0;
+ }
+ }
+ else
+ {
+ if (FALSE != quoteSpaces &&
+ (L' ' == buffer[0] || L' ' == buffer[length-1]) &&
+ (bufferSize - length) > 2)
+ {
+ memmove(buffer + 1, buffer, sizeof(wchar_t) * length);
+ buffer[0] = L'\"';
+ buffer[length++] = L'\"';
+ buffer[length] = L'\0';
+ }
+ }
+ return length;
+}
+
+bool DeviceView::DeletePlaylist(int playlistId, bool deleteFiles, bool verbal)
+{
+ int index;
+ C_ItemList delList;
+
+ if(playlistId < 1)
+ return false;
+
+ index = playlistId - 1;
+
+ if (false != deleteFiles)
+ {
+ int length;
+
+ length = dev->getPlaylistLength(playlistId);
+ for(int i = 0; i < length; i++)
+ {
+ delList.Add((void*)dev->getPlaylistTrack(playlistId, i));
+ }
+ }
+
+ if (false != verbal)
+ {
+ wchar_t message[1024] = {0}, title[1024] = {0}, playlistName[256] = {0}, deviceName[256] = {0};
+
+ GetPlaylistName(playlistName, ARRAYSIZE(playlistName), playlistId, NULL, FALSE);
+ GetPlaylistName(deviceName, ARRAYSIZE(deviceName), 0, MAKEINTRESOURCE(IDS_DEVICE_LOWERCASE), TRUE);
+
+ if (0 != delList.GetSize())
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_PHYSICALLY_REMOVE_X_TRACKS, title, ARRAYSIZE(title));
+ StringCchPrintf(message, ARRAYSIZE(message), title, delList.GetSize(), playlistName);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST_TITLE, title, ARRAYSIZE(title));
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST, title, ARRAYSIZE(title));
+ StringCchPrintf(message, ARRAYSIZE(message), title, playlistName, deviceName);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST_TITLE, title, ARRAYSIZE(title));
+ }
+
+ if(IDYES != MessageBox(plugin.hwndLibraryParent, message, title,
+ MB_YESNO | MB_ICONWARNING))
+ {
+ return false;
+ }
+ }
+
+ if (0 != delList.GetSize())
+ {
+ int result;
+ result = DeleteTracks(&delList, CENTER_OVER_ML_VIEW);
+ if (IDABORT == result) /* user abort */
+ return false;
+
+ if (IDOK != result) /* error */
+ {
+
+ }
+
+ }
+
+ HNAVITEM item = playlistTreeItems[index];
+ playlistTreeItems.erase(playlistTreeItems.begin() + index);
+
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, item);
+
+ dev->deletePlaylist(playlistId);
+ DevicePropertiesChanges();
+
+ return true;
+}
+
+bool DeviceView::GetTransferFromMlSupported(int dataType)
+{
+ switch(dataType)
+ {
+ case ML_TYPE_ITEMRECORDLISTW:
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_PLAYLIST:
+ case ML_TYPE_PLAYLISTS:
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_FILENAMESW:
+ return true;
+ }
+ return false;
+}
+intptr_t DeviceView::TransferFromML(int type, void* data, int unsupportedReturn, int supportedReturn, int playlist)
+{
+ int r;
+
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ if(type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ r=AddItemListToTransferQueue((itemRecordListW*)data,playlist);
+ }
+ else if(type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordListW list= {0};
+ convertRecordList(&list,(itemRecordList*)data);
+ r=AddItemListToTransferQueue(&list,playlist);
+ freeRecordList(&list);
+ }
+ else if(type == ML_TYPE_FILENAMES)
+ {
+ C_ItemList fileList;
+ char * filenames = (char *)data;
+ while(filenames && *filenames)
+ {
+ fileList.Add(filenames);
+ filenames+=strlen(filenames)+1;
+ }
+ r=AddFileListToTransferQueue((char**)fileList.GetAll(),fileList.GetSize(),playlist);
+ }
+ else if(type == ML_TYPE_FILENAMESW)
+ {
+ C_ItemList fileList;
+ wchar_t * filenames = (wchar_t *)data;
+ while(filenames && *filenames)
+ {
+ fileList.Add(filenames);
+ filenames+=wcslen(filenames)+1;
+ }
+ r=AddFileListToTransferQueue((wchar_t**)fileList.GetAll(),fileList.GetSize(),playlist);
+ }
+ else if(type == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist * pl = (mlPlaylist *)data;
+ TransferPlaylist((wchar_t*)pl->filename,(wchar_t*)pl->title);
+ r=0;
+ }
+ else if(type == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)data;
+ while(playlists && *playlists)
+ {
+ mlPlaylist *pl = *playlists;
+ TransferPlaylist((wchar_t*)pl->filename,(wchar_t*)pl->title);
+ playlists++;
+ }
+ r=0;
+ }
+ else return unsupportedReturn;
+
+ wchar_t errStr[32] = {0};
+ if(r==-1)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_DEVICE_OUT_OF_SPACE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+ else if(r==-2)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_FORMAT_NO_TX),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+
+ return supportedReturn;
+}
+
+
+class ItemListRefLoader : public ifc_playlistloadercallback
+{
+public:
+ ItemListRefLoader(C_ItemList &itemList, C_ItemList &playlistItemList) : fileList(itemList), playlistList(playlistItemList)
+ {
+ }
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ if(playlistManager->CanLoad(filename))
+ playlistList.Add(_wcsdup(filename));
+ else
+ fileList.Add(_wcsdup(filename));
+ }
+
+ C_ItemList &fileList, &playlistList;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS ItemListRefLoader
+START_DISPATCH;
+VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile )
+END_DISPATCH;
+#undef CBCLASS
+
+
+class PlaylistDirectoryCallback : public ifc_playlistdirectorycallback
+{
+public:
+ PlaylistDirectoryCallback(const wchar_t *_extlist) : extlist(_extlist)
+ {
+ }
+
+ bool ShouldRecurse(const wchar_t *path)
+ {
+ // TODO: check for recursion?
+ return true;
+ }
+
+ bool ShouldLoad(const wchar_t *filename)
+ {
+ if(playlistManager->CanLoad(filename))
+ return true;
+ const wchar_t *ext = PathFindExtensionW(filename);
+ if(!*ext)
+ return false;
+
+ ext++;
+
+ const wchar_t *a = extlist;
+ while(a && *a)
+ {
+ if(!_wcsicmp(a, ext))
+ return true;
+ a += wcslen(a) + 1;
+ }
+ return false;
+ }
+
+ const wchar_t *extlist;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS PlaylistDirectoryCallback
+START_DISPATCH;
+CB(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse)
+CB(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad)
+END_DISPATCH;
+#undef CBCLASS
+
+intptr_t DeviceView::TransferFromDrop(HDROP hDrop, int playlist)
+{
+ // benski> ugh. memory allocation hell. oh well
+ C_ItemList fileList;
+ C_ItemList playlistList;
+ const wchar_t *extListW = (const wchar_t *)SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_EXTLISTW);
+ PlaylistDirectoryCallback dirCB(extListW);
+ wchar_t temp[2048] = {0};
+ int y = DragQueryFileW(hDrop, 0xffffffff, temp, 2048);
+
+ for(int x = 0; x < y; x ++)
+ {
+ DragQueryFileW(hDrop, x, temp, 2048);
+ // see if it's a directory
+ bool isDir=false;
+ if(!PathIsURLW(temp) && !PathIsNetworkPathW(temp))
+ {
+ HANDLE h;
+ WIN32_FIND_DATAW d;
+
+ h = FindFirstFileW(temp, &d);
+ if(h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+ if(d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ ItemListRefLoader fileListCB(fileList, playlistList);
+ playlistManager->LoadDirectory(temp, &fileListCB, &dirCB);
+ isDir=true;
+ }
+ }
+ }
+
+ if(!isDir)
+ {
+ if(playlistManager->CanLoad(temp))
+ playlistList.Add(_wcsdup(temp));
+ else
+ fileList.Add(_wcsdup(temp));
+ }
+ }
+
+ int r=0, r2=0;
+ if(fileList.GetSize())
+ r=AddFileListToTransferQueue((wchar_t**)fileList.GetAll(),fileList.GetSize(),playlist);
+#if 0
+ if(playlistList.GetSize())
+ r2=AddFileListToTransferQueue((wchar_t**)playlistList.GetAll(), playlistList.GetSize(),1/*playlists*/);
+#endif
+ wchar_t errStr[32] = {0};
+ if(r==-1 || r2==-1)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_DEVICE_OUT_OF_SPACE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+ if(r==-2 || r2 == -2)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_FORMAT_NO_TX),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+
+ // benski> my CS301 professor would be proud!
+ fileList.for_all(free);
+ playlistList.for_all(free);
+
+ GlobalFree((HGLOBAL)extListW);
+ return 0;
+}
+
+HWND DeviceView::CreateView(HWND parent)
+{
+ currentViewedDevice=this;
+ currentViewedPlaylist=0;
+
+ if (currentViewedDevice->config->ReadInt(L"media_numfilters", 2) == 1)
+ return WASABI_API_CREATEDIALOGPARAMW((isCloudDevice ? IDD_VIEW_CLOUD_SIMPLE : IDD_VIEW_PMP_VIDEO),
+ parent, pmp_video_dlgproc, (currentViewedDevice->isCloudDevice ? 1 : 2));
+ else
+ return WASABI_API_CREATEDIALOGPARAMW((isCloudDevice ? IDD_VIEW_CLOUD_ARTISTALBUM : IDD_VIEW_PMP_ARTISTALBUM),
+ parent, pmp_artistalbum_dlgproc, 0);
+}
+
+BOOL DeviceView::DisplayDeviceContextMenu(HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ HMENU menu = GetSubMenu(m_context_menus,3);
+ if (NULL == menu)
+ return FALSE;
+
+ if(dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ AppendMenu(menu,0,ID_TREEPLAYLIST_RENAMEPLAYLIST,WASABI_API_LNGSTRINGW(IDS_RENAME_DEVICE));
+
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,
+ pt.x, pt.y, plugin.hwndLibraryParent, NULL);
+
+ if(dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ DeleteMenu(menu,ID_TREEPLAYLIST_RENAMEPLAYLIST,MF_BYCOMMAND);
+
+ switch(r)
+ {
+ case ID_TREEDEVICE_NEWPLAYLIST:
+ CreatePlaylist();
+ break;
+ case ID_TREEDEVICE_EJECTDEVICE:
+ Eject();
+ break;
+ case ID_TREEPLAYLIST_RENAMEPLAYLIST:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ //RenamePlaylist(0);
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL DeviceView::DisplayPlaylistContextMenu(int playlistId, HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ HMENU menu = GetSubMenu(m_context_menus,4);
+ if (NULL == menu)
+ return FALSE;
+
+ EnableMenuItem(menu,ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA,
+ MF_BYCOMMAND | (dev->copyToHardDriveSupported()? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
+
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,
+ pt.x, pt.y, plugin.hwndLibraryParent, NULL);
+ switch(r)
+ {
+ case ID_TREEPLAYLIST_RENAMEPLAYLIST:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ break;
+ case ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES:
+ DeletePlaylist(playlistId, true, true);
+ break;
+ case ID_TREEPLAYLIST_REMOVEPLAYLIST:
+ DeletePlaylist(playlistId, false, true);
+ break;
+ case ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA:
+ CopyPlaylistToLibrary(playlistId);
+ break;
+ }
+
+ return TRUE;
+}
+static HNAVITEM Navigation_GetItemFromMessage(INT msg, INT_PTR param)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param) :
+ (HNAVITEM)param;
+}
+
+BOOL DeviceView::Navigation_IsPlaylistItem(HNAVITEM item, int *playlistId)
+{
+ for(size_t i=0; i < playlistTreeItems.size(); i++)
+ {
+ if(item == playlistTreeItems[i])
+ {
+ if (NULL != playlistId)
+ *playlistId = (i + 1);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+HWND DeviceView::Navigation_CreateViewCb(HNAVITEM item, HWND parentWindow)
+{
+ if(item == treeItem)
+ {
+ if (FALSE != navigationItemCreated)
+ {
+ currentViewedDevice = this;
+ currentViewedPlaylist = 0;
+ return WASABI_API_CREATEDIALOGW((currentViewedDevice->isCloudDevice ? IDD_VIEW_CLOUD_ARTISTALBUM : IDD_VIEW_PMP_ARTISTALBUM), parentWindow, pmp_artistalbum_dlgproc);
+ }
+ }
+ else if (item == queueTreeItem)
+ {
+ currentViewedDevice = this;
+ currentViewedPlaylist = 0;
+ return WASABI_API_CREATEDIALOGPARAMW((currentViewedDevice->isCloudDevice ? IDD_VIEW_CLOUD_QUEUE : IDD_VIEW_PMP_QUEUE), parentWindow, pmp_queue_dlgproc, (LPARAM)this);
+ }
+ else if(item == videoTreeItem)
+ {
+ currentViewedDevice = this;
+ currentViewedPlaylist = 0;
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_PMP_VIDEO, parentWindow, pmp_video_dlgproc, 0);
+ }
+ else
+ {
+ if (FALSE != Navigation_IsPlaylistItem(item, &currentViewedPlaylist))
+ {
+ currentViewedDevice = this;
+ return WASABI_API_CREATEDIALOGW(IDD_VIEW_PMP_PLAYLIST, parentWindow, pmp_playlist_dlgproc);
+ }
+ }
+
+ return NULL;
+}
+
+BOOL DeviceView::Navigation_ShowContextMenuCb(HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ if (item == treeItem)
+ {
+ if (FALSE != navigationItemCreated)
+ return DisplayDeviceContextMenu(item, hostWindow, pt);
+ }
+ else
+ {
+ int playlistId;
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ return DisplayPlaylistContextMenu(playlistId, item, hostWindow, pt);
+ }
+
+ return FALSE;
+}
+BOOL DeviceView::Navigation_ClickCb(HNAVITEM item, int actionType, HWND hostWindow)
+{
+ int playlistId;
+
+ switch(actionType)
+ {
+ case ML_ACTION_DBLCLICK:
+ case ML_ACTION_ENTER:
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ {
+ PlayPlaylist(playlistId, false, true, hostWindow);
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+int DeviceView::Navigation_DropTargetCb(HNAVITEM item, unsigned int dataType, void *data)
+{
+ if (item == treeItem)
+ {
+ if (FALSE != navigationItemCreated)
+ {
+ if(NULL == data)
+ return (false != GetTransferFromMlSupported(dataType)) ? 1 : -1;
+
+ return TransferFromML(dataType, data, -1, 1);
+ }
+ }
+ else
+ {
+ int playlistId;
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ {
+ if(NULL == data)
+ return (false != GetTransferFromMlSupported(dataType)) ? 1 : -1;
+
+ return TransferFromML(dataType, data, -1, 1, playlistId);
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL DeviceView::Navigation_TitleEditBeginCb(HNAVITEM item)
+{
+ if (item == treeItem)
+ {
+ if (FALSE != navigationItemCreated &&
+ FALSE == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+BOOL DeviceView::Navigation_TitleEditEndCb(HNAVITEM item, const wchar_t *title)
+{
+ int playlistId = 0;
+ wchar_t buffer[512] = {0};
+
+ if (item == treeItem)
+ {
+ if (FALSE == navigationItemCreated)
+ return FALSE;
+
+ playlistId = 0;
+ }
+ else
+ {
+ if (FALSE == Navigation_IsPlaylistItem(item, &playlistId))
+ return FALSE;
+ }
+
+ if (NULL == title)
+ return TRUE;
+
+ buffer[0] = L'\0';
+ dev->getPlaylistName(playlistId, buffer, ARRAYSIZE(buffer));
+
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, title, -1))
+ return TRUE;
+
+ dev->setPlaylistName(playlistId, title);
+ DevicePropertiesChanges();
+
+ buffer[0] = L'\0';
+ dev->getPlaylistName(playlistId, buffer, ARRAYSIZE(buffer));
+
+ if (0 == playlistId)
+ {
+ free(devPrefsPage.name);
+ devPrefsPage.name = _wcsdup(buffer);
+
+ UpdateDevicesListView(false);
+ OnNameChanged(buffer);
+ }
+
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, title, -1))
+ return TRUE;
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.pszText = buffer;
+ itemInfo.hItem = item;
+ itemInfo.mask = NIMF_TEXT;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+
+ return FALSE;
+}
+
+BOOL DeviceView::Navigation_KeyDownCb(HNAVITEM item, NMTVKEYDOWN *keyData, HWND hwnd)
+{
+ int playlistId;
+ if (item == treeItem)
+ {
+ if (FALSE == navigationItemCreated)
+ return FALSE;
+
+ switch(keyData->wVKey)
+ {
+ case VK_F2:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ break;
+ }
+ return TRUE;
+ }
+
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ {
+ switch(keyData->wVKey)
+ {
+ case VK_F2:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ break;
+
+ case VK_DELETE:
+ DeletePlaylist(playlistId, false, true);
+ break;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+intptr_t DeviceView::MessageProc(int message_type, intptr_t param1, intptr_t param2, intptr_t param3)
+{
+ if(message_type >= ML_MSG_TREE_BEGIN && message_type <= ML_MSG_TREE_END)
+ {
+ HNAVITEM item;
+
+ switch(message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_CreateViewCb(item, (HWND)param2);
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ POINT pt;
+ POINTSTOPOINT(pt, MAKEPOINTS(param3));
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_ShowContextMenuCb(item, (HWND)param2, pt);
+ }
+ case ML_MSG_TREE_ONCLICK:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_ClickCb(item, (int)param2, (HWND)param3);
+ case ML_MSG_TREE_ONDROPTARGET:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_DropTargetCb(item, (unsigned int)param2, (void*)param3);
+ case ML_MSG_NAVIGATION_ONBEGINTITLEEDIT:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_TitleEditBeginCb(item);
+ case ML_MSG_NAVIGATION_ONENDTITLEEDIT:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_TitleEditEndCb(item, (const wchar_t*)param2);
+ case ML_MSG_TREE_ONKEYDOWN:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_KeyDownCb(item, (NMTVKEYDOWN*)param2, (HWND)param3);
+ }
+ }
+ else if(message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (!gen_mlconfig->ReadInt(L"pmp_send_to", DEFAULT_PMP_SEND_TO))
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_ITEMRECORDLISTW ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW ||
+ param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)
+ {
+ if (dev->extraActions(DEVICE_SENDTO_UNSUPPORTED, 0, 0, 0) == 0)
+ {
+ wchar_t buffer[128] = {0};
+ dev->getPlaylistName(0, buffer, 128);
+ mediaLibrary.AddToSendTo(buffer, param2, reinterpret_cast<INT_PTR>(this));
+ }
+ }
+ }
+ }
+ else if(message_type == ML_MSG_ONSENDTOSELECT && param2 && param3 == (intptr_t)this)
+ {
+ // TODO!!!
+ // if we get a playlist or playlist list and we can match it to a cloud device then
+ // we check for 'hss' and if so then process as a cloud playlist else do as before
+ if (this->isCloudDevice && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS))
+ {
+ char name[128] = {0};
+ if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0))
+ {
+ if (!strcmp(name, "hss"/*HSS_CLIENT*/))
+ {
+ if(param1 == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist * pl = (mlPlaylist *)param2;
+ TransferAddCloudPlaylist((wchar_t*)pl->filename, (wchar_t*)pl->title);
+ }
+ else if(param1 == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)param2;
+ while(playlists && *playlists)
+ {
+ mlPlaylist *pl = *playlists;
+ TransferAddCloudPlaylist((wchar_t*)pl->filename, (wchar_t*)pl->title);
+ playlists++;
+ }
+ }
+ return 1;
+ }
+ }
+ }
+
+ UpdateActivityState();
+ return TransferFromML(param1,(void*)param2,0,1);
+ }
+ return 0;
+}
+
+void DeviceView::DevicePropertiesChanges()
+{
+ commitNeeded=true;
+ SetEvent(transferContext.notifier);
+}
+
+void DeviceView::Eject()
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if(txQueue && txQueue->GetSize() == 0)
+ {
+ dev->Eject();
+ }
+ else
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_SYNC_IS_IN_PROGRESS),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CANNOT_EJECT,titleStr,32),0);
+ }
+}
+
+Device * deleteTrackDev;
+C_ItemList * deleteTracks;
+extern HWND hwndMediaView;
+
+static INT_PTR CALLBACK pmp_delete_progress_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static int i;
+ static songid_t s;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ i=0;
+ s=0;
+ SetWindowText(hwndDlg,WASABI_API_LNGSTRINGW((!currentViewedDevice || currentViewedDevice && !currentViewedDevice->isCloudDevice ? IDS_DELETING_TRACKS : IDS_REMOVING_TRACKS)));
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETRANGE,0,MAKELPARAM(0, (!deleteTracks ? 0 : deleteTracks->GetSize())));
+ SetTimer(hwndDlg,0,5,NULL);
+
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+
+ break;
+ case WM_TIMER:
+ if(wParam == 1)
+ {
+ KillTimer(hwndDlg,wParam);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETPOS,i,0);
+ if(i < deleteTracks->GetSize())
+ {
+ songid_t s2 = (songid_t)deleteTracks->Get(i++);
+ if(s != s2)
+ {
+ if(hwndMediaView) SendMessage(hwndMediaView,WM_USER+1,(WPARAM)s2,0);
+ deleteTrackDev->deleteTrack(s2);
+ s=s2;
+ }
+ SetTimer(hwndDlg,1,5,NULL);
+ }
+ else EndDialog(hwndDlg, IDOK);
+ }
+ else if(wParam == 0)
+ {
+ KillTimer(hwndDlg,0);
+ int s = deleteTracks->GetSize();
+ for(int i=0; i<s; i++)
+ {
+ void * p = deleteTracks->Get(i);
+ for(int j=i+1; j<s; j++)
+ {
+ if(p == deleteTracks->Get(j))
+ {
+ deleteTracks->Del(j--); s--;
+ }
+ }
+ }
+ SetTimer(hwndDlg,1,5,NULL);
+ }
+ break;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_ABORT)
+ EndDialog(hwndDlg, IDABORT);
+ break;
+ }
+ return 0;
+}
+int DeviceView::DeleteTracks(C_ItemList * tracks, HWND centerWindow)
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if(txQueue && txQueue->GetSize() > 0)
+ {
+ wchar_t sorry[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SORRY,sorry,32),0);
+ return -1;
+ }
+ if (dev && tracks)
+ {
+ deleteTrackDev = dev;
+ deleteTracks = tracks;
+ return WASABI_API_DIALOGBOXPARAMW(IDD_PROGRESS, plugin.hwndLibraryParent, pmp_delete_progress_dlgproc, (LPARAM)centerWindow);
+ }
+ return IDABORT;
+}
+
+int DeviceView::AddFileListToTransferQueue(char ** files, int num, int playlist)
+{
+ wchar_t ** filesW = (wchar_t**)calloc(num, sizeof(wchar_t*));
+ for(int i=0; i<num; i++)
+ {
+ filesW[i] = AutoWideDup(files[i]);
+ }
+ int r = AddFileListToTransferQueue(filesW,num,playlist);
+ for(int i=0; i<num; i++)
+ {
+ free(filesW[i]);
+ }
+ free(filesW);
+ return r;
+}
+
+int DeviceView::AddFileListToTransferQueue(wchar_t ** files, int num, int playlist)
+{
+ C_ItemList * irs = fileListToItemRecords(files,num, CENTER_OVER_ML_VIEW);
+ int r = AddItemListToTransferQueue(irs,playlist);
+ for(int i=0; i < irs->GetSize(); i++)
+ {
+ itemRecordW * it = (itemRecordW *)irs->Get(i);
+ freeRecord(it);
+ free(it);
+ }
+ delete irs;
+ return r;
+}
+
+void TransfersListPushPopItem(CopyInst * item, DeviceView *view);
+int DeviceView::AddItemListToTransferQueue(C_ItemList * items, int playlist)
+{
+ if(playlist == 0)
+ {
+ if (!isCloudDevice)
+ {
+ int r=0;
+ C_ItemList toSend, haveSent;
+ ProcessDatabaseDifferences(dev,items,&haveSent,&toSend,NULL,NULL);
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+
+ for(int i = 0; i < toSend.GetSize(); i++)
+ {
+ if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)toSend.Get(i), true)) == -1) break;
+ }
+
+ txQueue->unlock();
+ }
+ return r;
+ }
+ else
+ {
+ int r=0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+
+ for(int i = 0; i < items->GetSize(); i++)
+ {
+ if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)items->Get(i), true)) == -1) break;
+ }
+
+ txQueue->unlock();
+ }
+ return r;
+ }
+ }
+ else
+ {
+ return TransferTracksToPlaylist(items,playlist);
+ }
+}
+
+int DeviceView::AddItemListToTransferQueue(itemRecordListW * items, int playlist)
+{
+ if(playlist == 0)
+ {
+ if (!isCloudDevice)
+ {
+ int r=0;
+ C_ItemList toSend;
+ ProcessDatabaseDifferences(dev,items,NULL,&toSend,NULL,NULL);
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int i = 0; i < toSend.GetSize(); i++)
+ if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)toSend.Get(i), true)) == -1) break;
+ txQueue->unlock();
+ }
+ return r;
+ }
+ else
+ {
+ int r=0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int i = 0; i < items->Size; i++)
+ if((r = this->AddTrackToTransferQueue(this, &items->Items[i], true)) == -1) break;
+ txQueue->unlock();
+ }
+ return r;
+ }
+ }
+ else
+ {
+ C_ItemList itemRecords;
+ for (int i = 0; i < items->Size; i++) itemRecords.Add(&items->Items[i]);
+ return TransferTracksToPlaylist(&itemRecords,playlist);
+ }
+}
+
+class ItemListLoader : public ifc_playlistloadercallback
+{
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ fileList.Add(_wcsdup(filename));
+ }
+
+ void FreeAll()
+ {
+ fileList.for_all(free);
+ }
+ C_ItemList fileList;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS ItemListLoader
+START_DISPATCH;
+VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile )
+END_DISPATCH;
+#undef CBCLASS
+
+void DeviceView::TransferPlaylist(wchar_t * file, wchar_t * name0)
+{
+ // first sort the name out
+ if(!file) return;
+ wchar_t name[256] = {0};
+ if(!name0)
+ {
+ wchar_t * s = wcsrchr(file,L'\\');
+ if(!s) s = wcsrchr(file,L'/');
+ if(!s) s = file;
+ else s++;
+ if(wcslen(s) >= 255) s[255]=0;
+ StringCchCopy(name,256, s);
+ wchar_t * e = wcsrchr(name,L'.');
+ if(e) *e=0;
+ }
+ else lstrcpyn(name,name0,255);
+ name[255]=0;
+ // name sorted, parse m3u
+
+ ItemListLoader fileList;
+ playlistManager->Load(file, &fileList);
+ C_ItemList *itemRecords = fileListToItemRecords(&fileList.fileList, CENTER_OVER_ML_VIEW);
+ fileList.FreeAll();
+
+ // now we have a list of itemRecords, lets try and add this playlist to the device!
+ int plid = CreatePlaylist(name,true);
+ if(plid != -1)
+ TransferTracksToPlaylist(itemRecords,plid);
+ delete itemRecords;
+}
+
+int DeviceView::TransferTracksToPlaylist(C_ItemList *itemRecords, int plid)
+{
+ wchar_t name[256] = {0};
+ dev->getPlaylistName(plid,name,256);
+ int i;
+ for(i=0; i<itemRecords->GetSize(); i++)
+ {
+ itemRecordW * ice = (itemRecordW *)itemRecords->Get(i);
+ wchar_t num[12] = {0};
+ StringCchPrintf(num, 12, L"%x",i);
+ setRecordExtendedItem(ice,L"PLN",num);
+ }
+ C_ItemList irAlreadyOn, siAlreadyOn;
+ ProcessDatabaseDifferences(dev,itemRecords,&irAlreadyOn,NULL,&siAlreadyOn,NULL);
+ // itemRecords_sort, irAlreadyOn, irTransfer and siAlreadyOn will NOT be in playlist order
+ // we must get them into playlist order. In O(n) :/
+ int l = itemRecords->GetSize();
+ PlaylistAddItem * pl = (PlaylistAddItem*)calloc(l,sizeof(PlaylistAddItem));
+ int on=0;
+ for(i=0; i < l; i++)
+ {
+ itemRecordW * ice = (itemRecordW *)itemRecords->Get(i);
+ int n;
+ swscanf(getRecordExtendedItem(ice,L"PLN"),L"%x",&n);
+ if(n >= l)
+ {
+ continue;
+ }
+ pl[n].item = ice;
+ if(on < irAlreadyOn.GetSize()) if((itemRecordW*)irAlreadyOn.Get(on) == ice) // this track is on the device!
+ {
+ pl[n].songid = (songid_t)siAlreadyOn.Get(on);
+ on++;
+ }
+ }
+
+ // awesome! pl now contains our playlist in proper order with the "songid" fields set if the track is on the device.
+ C_ItemList * directAdd = new C_ItemList;
+ int m = 0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ PlaylistCopyInst * inst = NULL;
+ txQueue->lock();
+ for(i=0; i < l; i++)
+ {
+ if(pl[i].songid)
+ {
+ directAdd->Add((void*)pl[i].songid);
+ }
+ else
+ {
+ int r = dev->trackAddedToTransferQueue(pl[i].item);
+ if(r)
+ {
+ m |= (-r);
+ freeRecord(pl[i].item);
+ continue;
+ }
+ if(!inst)
+ {
+ if(plid != -1) for(int i=0; i<directAdd->GetSize(); i++)
+ dev->addTrackToPlaylist(plid,(songid_t)directAdd->Get(i));
+ delete directAdd;
+ }
+ else
+ {
+ inst->plAddSongs = directAdd;
+ AddTrackToTransferQueue(inst);
+ }
+ directAdd = new C_ItemList;
+ inst = new PlaylistCopyInst(this,pl[i].item,name,plid);
+ }
+ freeRecord(pl[i].item);
+ }
+ if(inst)
+ {
+ inst->plAddSongs = directAdd;
+ AddTrackToTransferQueue(inst);
+ }
+ else // NULL inst means no transfers!
+ {
+ if(plid != -1) for(int i=0; i<directAdd->GetSize(); i++)
+ dev->addTrackToPlaylist(plid,(songid_t)directAdd->Get(i));
+ delete directAdd;
+ }
+ txQueue->unlock();
+ }
+ if (pl) free(pl);
+
+ wchar_t warnStr[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_WARNING,warnStr,32);
+ if(m == 1) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_DEVICE_MAYBE_FULL),warnStr,0);
+ else if(m == 2) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT),warnStr,0);
+ else if(m == 3) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT),warnStr,0);
+ return 0;
+}
+
+void DeviceView::TransferAddCloudPlaylist(wchar_t * file, wchar_t * name0)
+{
+ if (!file) return;
+
+ AGAVE_API_PLAYLISTS->Lock();
+ for (size_t index = 0; index < AGAVE_API_PLAYLISTS->GetCount(); index++)
+ {
+ const wchar_t* filename = AGAVE_API_PLAYLISTS->GetFilename(index);
+ if (!lstrcmpiW(filename, file))
+ {
+ int cloud = 1;
+ if (AGAVE_API_PLAYLISTS->GetInfo(index, api_playlists_cloud, &cloud, sizeof(cloud)) == API_PLAYLISTS_SUCCESS)
+ {
+ // not set as a cloud playlist so we need to set and then announce
+ if (!cloud)
+ {
+ cloud = 1;
+ AGAVE_API_PLAYLISTS->SetInfo(index, api_playlists_cloud, &cloud, sizeof(cloud));
+ AGAVE_API_PLAYLISTS->Flush();
+ }
+
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ mlplugin->MessageProc(0x406, index, 0, 0);
+ }
+ }
+ }
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_LIBRARY_PLAYLISTS_REFRESH);
+ }
+ else
+ {
+ }
+ break;
+ }
+ }
+ AGAVE_API_PLAYLISTS->Unlock();
+}
+
+extern MLTREEITEMW mainTreeItem;
+
+HWND hwndToolTips=NULL;
+
+int DeviceView::AddTrackToTransferQueue(CopyInst * inst)
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->Offer(inst);
+ if(txQueue->GetSize() == 1)
+ SetEvent(transferContext.notifier);
+ device_update_map[0] = true;
+ device_update_map[inst->dev] = true;
+ }
+ return 0;
+}
+
+int DeviceView::AddTrackToTransferQueue(DeviceView * device, itemRecordW * item, bool dupeCheck, bool forceDupe)
+{
+ SongCopyInst * inst = new SongCopyInst(device, item);
+
+ if (dupeCheck)
+ {
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ // current queue dupe check
+ for(int i = 0; i < txQueue->GetSize(); i++)
+ {
+ if(((CopyInst *)txQueue->Get(i))->Equals(inst))
+ {
+ delete inst;
+ txQueue->unlock();
+ return 0;
+ }
+ }
+ txQueue->unlock();
+ }
+ }
+
+ if (!forceDupe)
+ {
+ int r = dev->trackAddedToTransferQueue(&inst->song);
+ if (r)
+ {
+ if (r == 2)
+ {
+ inst->status = STATUS_DONE;
+ inst->res = 2;
+ AddTrackToTransferQueue(inst);
+ return 0;
+ }
+ else
+ {
+ delete inst;
+ }
+ }
+ else AddTrackToTransferQueue(inst);
+ return r;
+ }
+ else
+ {
+ inst->res = 2;
+ AddTrackToTransferQueue(inst);
+ return 0;
+ }
+}
+
+class SyncItemListLoader : public ifc_playlistloadercallback
+{
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ if(pos < len)
+ {
+ songs[pos].filename = _wcsdup(filename);
+ metaToGet->Add(&songs[pos].map);
+ songMaps->Add(&songs[pos].pladd);
+ }
+ pos++;
+ }
+ int pos,len;
+ songMapping * songs;
+ C_ItemList * metaToGet, * songMaps;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS SyncItemListLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+typedef struct
+{
+ mlPlaylistInfo info;
+ songMapping * songs;
+} SyncPlaylist;
+
+void TransfersListUpdateItem(CopyInst * item);
+void TransfersListUpdateItem(CopyInst * item, DeviceView *view);
+/*
+static int setPlaylistTrack(Device * dev, int pl, int n, int len, songid_t song) {
+if(n >= len) { while(n >= len) { dev->addTrackToPlaylist(pl,song); len++; } return len; }
+else {
+dev->addTrackToPlaylist(pl,song);
+dev->playlistSwapItems(pl,len,n);
+dev->removeTrackFromPlaylist(pl,len);
+}
+return len;
+}
+*/
+class PlaylistSyncCopyInst : public CopyInst
+{
+public:
+ bool memFreed;
+ C_ItemList *songMaps;
+ C_ItemList * playlists;
+ PlaylistSyncCopyInst(DeviceView *dev, C_ItemList *songMaps, C_ItemList * playlists) : songMaps(songMaps), playlists(playlists)
+ {
+ usesPreCopy = false;
+ usesPostCopy = true;
+ this->dev = dev;
+ equalsType = -1;
+ status=STATUS_WAITING;
+ // status caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING,statusCaption,sizeof(statusCaption)/sizeof(wchar_t));
+ // track caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_PLAYLIST_SYNCRONIZATION,trackCaption,sizeof(trackCaption)/sizeof(wchar_t));
+ // type caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_OTHER,typeCaption,sizeof(typeCaption)/sizeof(wchar_t));
+ memFreed=false;
+ }
+
+ virtual ~PlaylistSyncCopyInst()
+ {
+ freeMemory();
+ }
+
+ virtual bool CopyAction()
+ {
+ return false;
+ }
+
+ virtual void PostCopyAction()
+ {
+ SyncPlaylists(); freeMemory();
+ }
+ virtual void Cancelled()
+ {
+ freeMemory();
+ }
+ virtual bool Equals(CopyInst * b)
+ {
+ return false;
+ }
+ void freeMemory()
+ {
+ if(memFreed) return;
+ memFreed=true;
+ if(songMaps) delete songMaps;
+ songMaps = NULL;
+ if(playlists)
+ {
+ int l = playlists->GetSize();
+ for(int i=0; i<l; i++)
+ {
+ SyncPlaylist * playlist = (SyncPlaylist *)playlists->Get(i);
+ if(playlist)
+ {
+ if(playlist->songs)
+ {
+ for(int j=0; j<playlist->info.numItems; j++)
+ {
+ if(playlist->songs[j].ice)
+ {
+ freeRecord(playlist->songs[j].ice);
+ free(playlist->songs[j].ice);
+ }
+ if(playlist->songs[j].filename)
+ free(playlist->songs[j].filename);
+ }
+ free(playlist->songs);
+ }
+ free(playlist);
+ }
+ }
+ delete playlists;
+ playlists = NULL;
+ }
+ }
+ void SyncPlaylists()
+ {
+ if(memFreed)
+ return;
+ WASABI_API_LNGSTRINGW_BUF(IDS_WORKING,statusCaption,sizeof(statusCaption)/sizeof(wchar_t));
+ TransfersListUpdateItem(this);
+ TransfersListUpdateItem(this, dev);
+ MapItemRecordsToSongs(dev->dev,(PlaylistAddItem **)songMaps->GetAll(),songMaps->GetSize());
+ int numPlaylists = playlists->GetSize();
+ for(int i=0; i<numPlaylists; i++)
+ {
+ SyncPlaylist * playlist = (SyncPlaylist *)playlists->Get(i);
+ int plnum = -1;
+ bool done = false;
+ int l = dev->dev->getPlaylistCount();
+ int j;
+ for(j=0; j < l; j++)
+ {
+ wchar_t buf[128] = {0};
+ dev->dev->getPlaylistName(j,buf,128);
+ if(wcscmp(buf,playlist->info.playlistName)) continue;
+ int plen = dev->dev->getPlaylistLength(j);
+ if(plen != playlist->info.numItems)
+ {
+ plnum = j;
+ break;
+ }
+ for(int k=0; k<plen; k++)
+ {
+ if(playlist->songs[k].song != dev->dev->getPlaylistTrack(j,k))
+ {
+ plnum = j;
+ break;
+ }
+ }
+ if(plnum == -1)
+ {
+ done = true;
+ break;
+ }
+ }
+ if(done) continue;
+ if(plnum == -1)
+ {
+ plnum = dev->CreatePlaylist(playlist->info.playlistName,true);
+ if(plnum == -1) continue;
+ }
+ int plen = dev->dev->getPlaylistLength(plnum);
+ while(plen && ((plen % 4) != 1)) dev->dev->removeTrackFromPlaylist(plnum,--plen); // avoid granulation boundarys
+ int n=0;
+ for(j=0; j<playlist->info.numItems; j++)
+ {
+ songid_t s = playlist->songs[j].song;
+ if(s && (n>=plen || s != dev->dev->getPlaylistTrack(plnum,n)))
+ {
+ // begin set item code...
+ if(n >= plen) while(n >= plen)
+ {
+ dev->dev->addTrackToPlaylist(plnum,s);
+ plen++;
+ }
+ else
+ {
+ dev->dev->addTrackToPlaylist(plnum,s);
+ dev->dev->playlistSwapItems(plnum,plen,n);
+ dev->dev->removeTrackFromPlaylist(plnum,plen);
+ }
+ // end set item code
+ }
+ if(s) n++;
+ }
+ plen = dev->dev->getPlaylistLength(plnum);
+ while(plen > n) dev->dev->removeTrackFromPlaylist(plnum,--plen);
+
+ if(_wcsicmp(playlist->info.playlistName,L"Podcasts")==0)
+ {
+ wchar_t *name=NULL;
+ for(int j=playlist->info.numItems-1; j>=0; j--)
+ {
+ wchar_t *n = getRecordExtendedItem(playlist->songs[j].ice,L"podcastchannel");
+ if(!name) name=n;
+ if(name && n)
+ {
+ if(_wcsicmp(name,n))
+ {
+ dev->dev->extraActions(DEVICE_ADDPODCASTGROUP,plnum,j+1,(intptr_t)name);
+ name=n;
+ }
+ if(j==0) dev->dev->extraActions(DEVICE_ADDPODCASTGROUP,plnum,0,(intptr_t)name);
+ }
+ }
+ dev->dev->extraActions(DEVICE_ADDPODCASTGROUP_FINISH,plnum,0,0);
+ }
+ }
+ dev->DevicePropertiesChanges();
+ freeMemory();
+ WASABI_API_LNGSTRINGW_BUF(IDS_DONE,statusCaption,sizeof(statusCaption)/sizeof(wchar_t));
+ TransfersListUpdateItem(this);
+ TransfersListUpdateItem(this, dev);
+ }
+};
+
+static bool shouldSyncPlaylist(wchar_t * name, C_Config * config)
+{
+ wchar_t buf[150] = {0};
+ StringCchPrintf(buf,150, L"sync-%s",name);
+ return config->ReadInt(buf,0) == config->ReadInt(L"plsyncwhitelist",1);
+}
+
+static int sortfunc_podcastpubdate(const void *elem1, const void *elem2)
+{
+ itemRecordW *ar = (itemRecordW *)elem1;
+ itemRecordW *br = (itemRecordW *)elem2;
+ wchar_t *a = getRecordExtendedItem(ar,L"podcastpubdate");
+ wchar_t *b = getRecordExtendedItem(br,L"podcastpubdate");
+ if(!a) a = L"0";
+ if(!b) b = L"0";
+ return _wtoi(b) - _wtoi(a);
+}
+
+void DeviceView::OnActivityStarted()
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->ActivityStarted( this, this );
+}
+
+void DeviceView::OnActivityChanged()
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->ActivityChanged( this, this );
+}
+
+void DeviceView::OnActivityFinished()
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->ActivityFinished( this, this );
+}
+
+void DeviceView::UpdateActivityState()
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue && FALSE == activityRunning)
+ {
+ if (0 != txQueue->GetSize())
+ {
+ activityRunning = TRUE;
+
+ if (FAILED(GetProgress(&currentProgress)))
+ currentProgress = 0;
+
+ OnActivityStarted();
+ }
+ }
+ else
+ {
+ if (txQueue && 0 == txQueue->GetSize())
+ {
+ activityRunning = FALSE;
+ OnActivityFinished();
+ }
+ else
+ {
+ unsigned int percent;
+ if (FAILED(GetProgress(&percent)) ||
+ percent != currentProgress)
+ {
+ currentProgress = percent;
+ OnActivityChanged();
+ }
+ }
+ }
+}
+
+void DeviceView::UpdateSpaceInfo(BOOL updateUsedSpace, BOOL notifyChanges)
+{
+ uint64_t total, used;
+ unsigned int changes;
+
+ changes = 0;
+
+ total = dev->getDeviceCapacityTotal();
+ if (total != totalSpace)
+ {
+ totalSpace = total;
+ changes |= (1 << 0);
+ }
+
+ if (FALSE != updateUsedSpace)
+ {
+ used = dev->getDeviceCapacityAvailable();
+ if (used > total)
+ used = total;
+
+ used = total - used;
+
+ if (used != usedSpace)
+ {
+ usedSpace = used;
+ changes |= (1 << 1);
+ }
+ }
+
+ if (0 != changes && FALSE != notifyChanges)
+ {
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ {
+ if (0 != ((1 << 0) & changes))
+ l_event_handler->TotalSpaceChanged(this, totalSpace);
+ if (0 != ((1 << 1) & changes))
+ l_event_handler->TotalSpaceChanged(this, usedSpace);
+ }
+ }
+}
+
+
+void DeviceView::OnNameChanged(const wchar_t *new_name)
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->DisplayNameChanged( this, new_name );
+}
+
+void DeviceView::Sync(bool silent)
+{
+ // sync configuration settings....
+ bool syncAllLibrary = config->ReadInt(L"syncAllLibrary",1)!=0;
+
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ device_name[0] = 0;
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ HWND centerWindow = CENTER_OVER_ML_VIEW;
+ UpdateActivityState();
+
+ C_ItemList mllist;
+ wchar_t * querystring=0;
+ itemRecordListW *results = 0;
+ if(syncAllLibrary)
+ {
+ querystring = _wcsdup(config->ReadString(L"SyncQuery",L"type=0"));
+ results = (AGAVE_API_MLDB ? AGAVE_API_MLDB->Query(querystring) : NULL);
+ if (results)
+ for(int i = 0; i < results->Size; i++) mllist.Add(&results->Items[i]);
+ }
+
+ // read playlists/views and find out what else needs to be added
+ PlaylistSyncCopyInst * sync = NULL;
+ C_ItemList filenameMaps;
+ C_ItemList *songMaps = new C_ItemList;
+ C_ItemList * playlists = new C_ItemList;
+
+ // first collect playlists without metadata
+ SyncItemListLoader list;
+ list.metaToGet = &filenameMaps;
+ list.songMaps = songMaps;
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_PLAYLIST_COUNT);
+ for(int i=0; i<playlistsnum; i++)
+ {
+ SyncPlaylist* playlist = (SyncPlaylist*)calloc(sizeof(SyncPlaylist),1);
+ playlist->info.size = sizeof(mlPlaylistInfo);
+ playlist->info.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&playlist->info, ML_IPC_PLAYLIST_INFO);
+ if(shouldSyncPlaylist(playlist->info.playlistName, config))
+ {
+ playlists->Add(playlist);
+ }
+ else
+ {
+ free(playlist);
+ playlist = 0;
+ continue;
+ }
+ //if(playlist->info.numItems <= 1)
+ {
+ list.pos = list.len = 0;
+ playlistManager->Load(playlist->info.filename, &list);
+ playlist->info.numItems = list.pos;
+ }
+ list.pos = 0;
+ list.len=playlist->info.numItems;
+ list.songs = playlist->songs = (songMapping*)calloc(sizeof(songMapping), list.len);
+ playlistManager->Load(playlist->info.filename, &list);
+ }
+ mapFilesToItemRecords((filenameMap **)filenameMaps.GetAll(), filenameMaps.GetSize(), centerWindow); // get metadata
+
+ // now sync podcasts...
+ if (dev->extraActions(DEVICE_SUPPORTS_PODCASTS, 0, 0, 0) == 0)
+ {
+ int podcasteps = config->ReadInt(L"podcast-sync_episodes",0);
+ int podcastsnum = AGAVE_API_PODCASTS ? AGAVE_API_PODCASTS->GetNumPodcasts() : 0;
+ if(podcasteps && podcastsnum > 0)
+ {
+ // if we want to sync podcasts and we have podcasts to sync
+ bool all = !!config->ReadInt(L"podcast-sync_all", 1);
+ SyncPlaylist * s = (SyncPlaylist *)calloc(sizeof(SyncPlaylist),1);
+ lstrcpyn(s->info.playlistName, L"Podcasts", 128); //set the name of the playlist containing our podcasts
+ int n = 0, alloc = 512;
+ s->songs = (songMapping*)calloc(alloc, sizeof(songMapping));
+ for(int i = 0; i < podcastsnum; i++)
+ {
+ ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i);
+ if(podcast)
+ {
+ wchar_t podcast_name[256] = {0};
+ if(podcast->GetTitle(podcast_name, 256) == 0)
+ {
+ wchar_t buf[300] = {0};
+ StringCchPrintf(buf, 300, L"podcast-sync-%s", podcast_name);
+ if(podcast_name[0] && (all || config->ReadInt(buf,0))) // if we have a podcast and we want to sync it
+ {
+ wchar_t query[300] = {0};
+ StringCchPrintf(query, 300, L"podcastchannel = \"%s\"", podcast_name);
+ itemRecordListW *podcasts = AGAVE_API_MLDB->Query(query);
+ if(podcasts)
+ {
+ qsort(podcasts->Items,podcasts->Size,sizeof(itemRecordW),sortfunc_podcastpubdate); // sort the podcasts into publish date order
+ for(int j=0; j<podcasts->Size && (podcasteps == -1 || j < podcasteps); j++)
+ {
+ // add podcast to playlist
+ if(n >= alloc)
+ {
+ size_t old_alloc = alloc;
+ alloc += 512;
+ songMapping* new_songs = (songMapping*)realloc(s->songs,sizeof(songMapping) * alloc);
+ if (new_songs)
+ {
+ s->songs = new_songs;
+ }
+ else
+ {
+ new_songs = (songMapping*)malloc(sizeof(songMapping) * alloc);
+ if (new_songs)
+ {
+ memcpy(new_songs, s->songs, sizeof(songMapping) * old_alloc);
+ free(s->songs);
+ s->songs = new_songs;
+ }
+ else
+ {
+ alloc = old_alloc;
+ continue;
+ }
+ }
+ }
+ ZeroMemory(&s->songs[n],sizeof(songMapping));
+ s->songs[n].ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1);
+ copyRecord(s->songs[n].ice,&podcasts->Items[j]);
+ mllist.Add(s->songs[n].ice);
+ songMaps->Add(&s->songs[n].pladd);
+ n++;
+ }
+ if(podcasts)
+ AGAVE_API_MLDB->FreeRecordList(podcasts);
+ }
+ }
+ }
+ }
+ }
+ s->info.numItems = n;
+ if(n)
+ playlists->Add(s);
+ else
+ {
+ free(s->songs);
+ free(s);
+ }
+ }
+ }
+ // now collect playlists with metadata (i.e, smart views)
+ // except the new ml_local isn't ready.
+ // calloc a new SyncPlaylist, fill in playlist->info.numItems, playlist->info.playlistName and playlist->songs[].ice then add to playlists.
+
+ // add tracks to be sync'd
+ for(int i=0; i<filenameMaps.GetSize(); i++)
+ {
+ filenameMap* f = (filenameMap*)filenameMaps.Get(i);
+ if(f->ice)
+ mllist.Add(f->ice);
+ }
+ // prepare sync
+ if(playlists->GetSize())
+ sync = new PlaylistSyncCopyInst(this, songMaps, playlists);
+ else
+ {
+ delete playlists;
+ delete songMaps;
+ }
+
+ // work out the tracks to be sent and deleted...
+ C_ItemList synclist,dellist;
+
+ ProcessDatabaseDifferences(dev, &mllist, NULL, &synclist, NULL, &dellist);
+
+ if(!synclist.GetSize() && !dellist.GetSize())
+ {
+ // nothing to do
+ if(sync)
+ {
+ sync->SyncPlaylists();
+ delete sync;
+ }
+ if(!silent)
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_NOTHING_TO_SYNC_UP_TO_DATE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SYNC, titleStr, 32),0);
+ }
+ }
+ else
+ {
+ // need to sync some tracks
+ if(IDOK == SyncDialog_Show(centerWindow, this, &synclist, &dellist, FALSE))
+ {
+ config->WriteInt(L"syncOnConnect_time",(int)time(NULL));
+ if(dellist.GetSize())
+ {
+ switch(config->ReadInt(L"TrueSync",0))
+ {
+ case 1: this->DeleteTracks(&dellist, centerWindow); break;
+ case 2: this->CopyTracksToHardDrive(&dellist); break;
+ }
+ }
+
+ int i = 0, l = 0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ l = synclist.GetSize();
+ txQueue->lock();
+ for(i = 0; i < l; i++) if(AddTrackToTransferQueue(this, (itemRecordW*)synclist.Get(i), false) == -1) break;
+ if(sync) AddTrackToTransferQueue(sync);
+ txQueue->unlock();
+ }
+
+ if(i != l)
+ {
+ wchar_t titleStr[128] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_NOT_ENOUGH_SPACE, titleStr, ARRAYSIZE(titleStr)),
+ MB_OK | MB_ICONWARNING);
+ }
+ }
+ else
+ {
+ if(sync) delete sync;
+ }
+ }
+
+ if(syncAllLibrary)
+ {
+ if(results)
+ AGAVE_API_MLDB->FreeRecordList(results);
+ free(querystring);
+ }
+}
+
+void DeviceView::CloudSync(bool silent)
+{
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ UpdateActivityState();
+
+ // work out the tracks to be sent...
+ C_ItemList *filenameMaps2 = new C_ItemList, synclist;
+ DeviceView * hss = 0, * local = 0;
+
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ // determine the cloud device and alter the device
+ // to be checked with as needed by the action done
+ for(int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+
+ if (d->isCloudDevice)
+ {
+ char name[128] = {0};
+ if (d->dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0))
+ {
+ if (!strcmp(name, "hss"/*HSS_CLIENT*/))
+ hss = d;
+ else if (!strcmp(name, "local_desktop"))
+ local = d;
+ }
+ }
+ }
+
+ if (local && hss && local->dev == dev)
+ {
+ // just use the local library as the source to compare against
+ mlplugin->MessageProc(0x403, /*source*/-1, /*dest*/hss->dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID,0,0,0), (INT_PTR)filenameMaps2);
+ }
+ else
+ {
+ // just use the local library as the source to compare against
+ mlplugin->MessageProc(0x403, /*source*/-1, /*dest*/dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID,0,0,0), (INT_PTR)filenameMaps2);
+ }
+ }
+ }
+ }
+
+ synclist = *fileListToItemRecords(filenameMaps2, CENTER_OVER_ML_VIEW);
+ nu::qsort(synclist.GetAll(), synclist.GetSize(), sizeof(void*), dev, compareSongs);
+
+ if(!synclist.GetSize())
+ {
+ if(!silent)
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_NOTHING_TO_SYNC_UP_TO_DATE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SYNC,titleStr,32),0);
+ }
+ }
+ else
+ {
+ DeviceView * destDevice = (local && hss && local->dev == dev ? hss : this);
+ // need to sync some tracks
+ if(IDOK == SyncCloudDialog_Show(CENTER_OVER_ML_VIEW, destDevice, &synclist))
+ {
+ int l = synclist.GetSize();
+ cloudTransferQueue.lock();
+ int i = 0;
+ for (; i < l; i++) if (AddTrackToTransferQueue(destDevice, (itemRecordW*)synclist.Get(i), false) == -1) break;
+ cloudTransferQueue.unlock();
+
+ if(i != l)
+ {
+ wchar_t titleStr[128] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_NOT_ENOUGH_SPACE, titleStr, ARRAYSIZE(titleStr)),
+ MB_OK | MB_ICONWARNING);
+ }
+ }
+ }
+}
+
+extern itemRecordListW * generateAutoFillList(DeviceView * dev, C_Config * config); // from autofill.cpp
+
+void DeviceView::Autofill()
+{
+ HWND centerWindow;
+
+ centerWindow = CENTER_OVER_ML_VIEW;
+
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ UpdateActivityState();
+
+ C_ItemList delList,sendList;
+
+ itemRecordListW * autofillList = generateAutoFillList(this,config);
+ ProcessDatabaseDifferences(dev,autofillList,NULL,&sendList,NULL,&delList);
+
+ if(IDOK == SyncDialog_Show(centerWindow, this, &sendList, &delList, TRUE))
+ {
+ config->WriteInt(L"syncOnConnect_time", (int)time(NULL));
+ // delete all tracks in delList
+ if(IDOK == DeleteTracks(&delList, centerWindow))
+ {
+ // not aborted
+ // send all tracks in sendList
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for(int i = 0; i < sendList.GetSize(); i++) AddTrackToTransferQueue(this, (itemRecordW*)sendList.Get(i), false);
+ txQueue->unlock();
+ }
+ }
+ }
+ if(autofillList)
+ freeRecordList(autofillList);
+}
+
+extern int serverPort;
+
+bool DeviceView::PlayTracks(C_ItemList * tracks, int startPlaybackAt, bool enqueue, bool msgIfImpossible, HWND parent)
+{
+ if(tracks->GetSize() == 0) return true;
+ // direct playback?
+ if(dev->playTracks((songid_t*)tracks->GetAll(),tracks->GetSize(),startPlaybackAt,enqueue))
+ return true;
+ if(serverPort>0 && dev->copyToHardDriveSupported())
+ {
+ // indirect playback?
+ if(!enqueue)
+ {
+ //clear playlist
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
+ }
+
+ wchar_t buf[2048] = {0};
+ dev->getPlaylistName(0,buf,128);
+ AutoUrl device(buf);
+ for(int i=0; i<tracks->GetSize(); i++)
+ {
+ songid_t s = (songid_t)tracks->Get(i);
+ //encode fields to url format
+ wchar_t metadata[2048] = {0};
+ dev->getTrackArtist(s,metadata,2048);
+ AutoUrl artist(metadata);
+ dev->getTrackAlbum(s,metadata,2048);
+ AutoUrl album(metadata);
+ dev->getTrackTitle(s,metadata,2048);
+ AutoUrl title(metadata);
+
+ // construct URL
+ wchar_t ext[10]=L"";
+ dev->getTrackExtraInfo(s,L"ext",ext,10);
+ char buf[8192] = {0};
+ StringCchPrintfA(buf,8192, "http://127.0.0.1:%d/?a=%s&l=%s&t=%s&d=%s%s%s",serverPort,artist,album,title,device,*ext?";.":"",(char*)AutoChar(ext));
+ // get title
+ AutoWide wideUrl(buf);
+
+ wchar_t buf2[4096] = {0};
+ getTitle(dev,s,wideUrl,buf2,4096);
+ // enqueue file
+ enqueueFileWithMetaStructW ef = { wideUrl, buf2, NULL, dev->getTrackLength( s ) / 1000 };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&ef, IPC_PLAYFILEW);
+ }
+ if(!enqueue) //play item startPlaybackAt
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startPlaybackAt,IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play
+ }
+ return true;
+ }
+ if(msgIfImpossible)
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(parent,WASABI_API_LNGSTRINGW(IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK),
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNSUPPORTED,titleStr,32),0);
+ }
+ return false;
+}
+
+bool DeviceView::PlayPlaylist(int playlistId, bool enqueue, bool msgIfImpossible, HWND parent)
+{
+ int l = dev->getPlaylistLength(playlistId);
+ C_ItemList tracks;
+ for(int j=0; j<l; j++)
+ tracks.Add((void*)dev->getPlaylistTrack(playlistId,j));
+ return PlayTracks(&tracks, 0, enqueue, msgIfImpossible, parent);
+}
+
+void DeviceView::CopyTracksToHardDrive(C_ItemList * tracks)
+{
+ CopyTracksToHardDrive((songid_t*)tracks->GetAll(),tracks->GetSize());
+}
+
+static void getReverseCopyFilenameFormat(wchar_t* filepath, wchar_t* format, int len, BOOL * uppercaseext)
+{
+ wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music";
+ wchar_t m_def_filename_fmt[MAX_PATH] = L"<Artist> - <Album>\\## - <Trackartist> - <Title>";
+ GetDefaultSaveToFolder(m_def_extract_path);
+ bool cdrip = !!global_config->ReadInt(L"extractusecdrip", 1);
+ const wchar_t *mlinifile = (const wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW);
+
+ wchar_t buf[2048] = {0};
+ if(cdrip) GetPrivateProfileString(L"gen_ml_config",L"extractpath",m_def_extract_path,buf,2048,mlinifile);
+ else lstrcpyn(buf,global_config->ReadString(L"extractpath",m_def_extract_path),2048);
+ lstrcpyn(filepath,buf,len);
+ int l = wcslen(filepath);
+ if(*(filepath+l-1) != L'\\')
+ {
+ *(filepath+l) = L'\\';
+ *(filepath+l+1)=0;
+ l++;
+ }
+ if(cdrip) GetPrivateProfileString(L"gen_ml_config",L"extractfmt2",m_def_filename_fmt,buf,2048,mlinifile);
+ else lstrcpyn(buf,global_config->ReadString(L"extractfmt2",m_def_filename_fmt),2048);
+ if(l < len) lstrcpyn(format/*+l*/,buf,len - l);
+ if(cdrip) *uppercaseext = GetPrivateProfileInt(L"gen_ml_config",L"extractucext",0,mlinifile);
+ else *uppercaseext = global_config->ReadInt(L"extractucext",0);
+}
+
+void DeviceView::CopyTracksToHardDrive(songid_t * tracks, int numTracks)
+{
+ if(!dev->copyToHardDriveSupported()) return;
+ BOOL uppercaseext=FALSE;
+ wchar_t filepath[MAX_PATH] = {0}, format[2048] = {0};
+ getReverseCopyFilenameFormat(filepath,format,2048,&uppercaseext);
+
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for(int i=0; i<numTracks; i++)
+ {
+ AddTrackToTransferQueue(new ReverseCopyInst(this,filepath,format,tracks[i],true,!!uppercaseext));
+ }
+ txQueue->unlock();
+ }
+}
+
+void DeviceView::CopyPlaylistToLibrary(int plnum)
+{
+ if(plnum==0) return;
+ wchar_t name[128] = {0};
+ dev->getPlaylistName(plnum,name,128);
+ wchar_t filename[MAX_PATH] = {0};
+ wchar_t dir[MAX_PATH] = {0};
+
+ GetTempPath(MAX_PATH,dir);
+ GetTempFileName(dir,L"pmppl",0,filename);
+ _wunlink(filename);
+ {
+ wchar_t * ext = wcsrchr(filename,L'.');
+ if(ext) *ext=0;
+ StringCchCat(filename,MAX_PATH,L".m3u");
+ }
+ FILE * f = _wfopen(filename,L"wt"); if(f)
+ {
+ fputws(L"#EXTM3U\n",f);
+ fclose(f);
+ }
+ /*
+ mlMakePlaylistV2 a = {sizeof(mlMakePlaylistV2),name,ML_TYPE_FILENAMES,"\0\0",PL_FLAG_SHOW | PL_FLAG_FILL_FILENAME};
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&a,ML_IPC_PLAYLIST_MAKE);
+ */
+
+ wchar_t filepath[MAX_PATH] = {0}, format[2048] = {0};
+ BOOL uppercaseext=FALSE;
+ getReverseCopyFilenameFormat(filepath,format,2048,&uppercaseext);
+ int l = dev->getPlaylistLength(plnum);
+
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for(int i=0; i<l; i++)
+ AddTrackToTransferQueue(new ReversePlaylistCopyInst(this,filepath,format,dev->getPlaylistTrack(plnum,i),filename,name,i==l-1,true));
+ txQueue->unlock();
+ }
+}
+
+void DeviceView::Unregister()
+{
+ for(size_t i=0; i < playlistTreeItems.size(); i++)
+ {
+ HNAVITEM item = playlistTreeItems[i];
+ // TODO: free memory associated with the text for item
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, item);
+ }
+ playlistTreeItems.clear();
+
+ if (videoTreeItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, videoTreeItem);
+ videoTreeItem=0;
+
+ if (AGAVE_API_DEVICEMANAGER)
+ AGAVE_API_DEVICEMANAGER->DeviceUnregister(name);
+ if (treeItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, treeItem);
+ treeItem=0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/DeviceView.h b/Src/Plugins/Library/ml_pmp/DeviceView.h
new file mode 100644
index 00000000..a930834e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceView.h
@@ -0,0 +1,257 @@
+#ifndef __DEVICEVIEW_H_
+#define __DEVICEVIEW_H_
+
+//#define _WIN32_WINNT 0x0400
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <shellapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/ipc_pe.h"
+#include "LinkedQueue.h"
+#include "pmp.h"
+#include "resource1.h"
+#include "config.h"
+#include "transfer_thread.h"
+#include "api__ml_pmp.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+#include <bfc/platform/types.h>
+#include "../devices/ifc_device.h"
+#include "../devices/ifc_deviceactivity.h"
+#include "pmp.h"
+#include <bfc/multipatch.h>
+#include <vector>
+
+#define COMMITTIMERID 0x2345
+
+extern C_Config * gen_mlconfig;
+extern LinkedQueue cloudTransferQueue, cloudFinishedTransfers;
+extern int cloudTransferProgress;
+extern winampMediaLibraryPlugin plugin;
+
+wchar_t *guessTitles(const wchar_t *filename, int *tracknum,wchar_t **artist, wchar_t **album,wchar_t **title); // free result after using artist, etc
+wchar_t* GetDefaultSaveToFolder(wchar_t* path_to_store);
+
+#define AVERAGEBASIS 3
+class TransferContext
+{
+public:
+ TransferContext() : dev(0), transfer_thread(NULL)
+ {
+ numTransfers=0;
+ start=0;
+ end=0;
+ InitializeCriticalSection(&transfer_lock);
+ killer = CreateEvent(NULL, TRUE, FALSE, 0);
+ notifier = CreateEvent(NULL, FALSE, FALSE, 0);
+ paused = 0;
+ ZeroMemory(&times,sizeof(times));
+ }
+
+ ~TransferContext()
+ {
+ DeleteCriticalSection(&transfer_lock);
+ CloseHandle(killer);
+ CloseHandle(notifier);
+ }
+
+ void WaitForKill()
+ {
+ SetEvent(notifier);
+ WaitForSingleObject(killer, INFINITE);
+ }
+ void DoOneTransfer(HANDLE handle);
+ bool IsPaused();
+ void Pause();
+ void Resume();
+
+ static bool IsAllPaused();
+ static void PauseAll();
+ static void ResumeAll();
+
+ int numTransfers;
+ int times[AVERAGEBASIS];
+ time_t start, end;
+ DeviceView *dev;
+ volatile size_t paused;
+ HANDLE killer, notifier;
+ CRITICAL_SECTION transfer_lock;
+ ThreadID *transfer_thread;
+
+ static volatile size_t paused_all;
+};
+
+enum
+{
+ PATCH_IFC_DEVICE,
+ PATCH_IFC_DEVICEACTIVITY,
+};
+
+class DeviceView : public MultiPatch<PATCH_IFC_DEVICE, ifc_device>,
+ public MultiPatch<PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity>
+{
+public://protected:
+ HNAVITEM treeItem, videoTreeItem, queueTreeItem;
+ int queueActiveIcon;
+ int isCloudDevice;
+ std::vector<HNAVITEM> playlistTreeItems;
+ LinkedQueue finishedTransfers;
+
+ HNAVITEM AddPlaylistNode(int id);
+ int AddTrackToTransferQueue(DeviceView * device, itemRecordW * item, bool dupeCheck, bool forceDupe=false); // true on success
+ int AddTrackToTransferQueue(CopyInst * inst);
+public:
+ TransferContext transferContext;
+ int videoView;
+ int SyncConnectionDefault;
+ prefsDlgRecW devPrefsPage;
+ int currentTransferProgress; // percentage
+ double transferRate;
+ int threadKillswitch;
+ LinkedQueue transferQueue;
+ bool commitNeeded;
+ C_Config * config;
+ Device * dev;
+ int metadata_fields;
+ DeviceView(Device *dev);
+
+ void SetVideoView(BOOL enabled);
+ bool GetTransferFromMlSupported(int dataType);
+ intptr_t TransferFromML(int type,void* data, int unsupportedReturn, int supportedReturn, int playlist=0);
+ intptr_t TransferFromDrop(HDROP hDrop, int playlist=0);
+ intptr_t MessageProc(int message_type, intptr_t param1, intptr_t param2, intptr_t param3);
+ void DevicePropertiesChanges();
+ int CreatePlaylist(wchar_t * name=NULL, bool silent=false);
+ void RenamePlaylist(int id);
+ bool DeletePlaylist(int playlistId, bool deleteFiles, bool verbal);
+ int AddFileListToTransferQueue(char ** files, int num, int playlist=0);
+ int AddFileListToTransferQueue(wchar_t ** files, int num, int playlist=0);
+ int AddItemListToTransferQueue(itemRecordListW * items, int playlist=0);
+ int AddItemListToTransferQueue(C_ItemList * items, int playlist=0);
+ void TransferPlaylist(wchar_t * file, wchar_t * name=NULL); // name=NULL when its from the ML and we must find out ourself...
+ int TransferTracksToPlaylist(C_ItemList *itemRecords, int plid);
+ void TransferAddCloudPlaylist(wchar_t * file, wchar_t * name0);
+ int DeleteTracks(C_ItemList * tracks, HWND centerWindow);
+ void Sync(bool silent = false);
+ void CloudSync(bool silent = false);
+ void Autofill();
+ void Eject();
+ bool PlayTracks(C_ItemList * tracks, int startPlaybackAt, bool enqueue, bool msgIfImpossible, HWND parent=NULL); // returns false if failed/unsupported
+ bool PlayPlaylist(int playlistId, bool enqueue, bool msgIfImpossible, HWND parent=NULL);
+ void CopyTracksToHardDrive(songid_t * tracks, int numTracks);
+ void CopyTracksToHardDrive(C_ItemList * tracks);
+ void CopyPlaylistToLibrary(int plnum);
+ void OnActivityStarted();
+ void OnActivityChanged();
+ void OnActivityFinished();
+ void OnNameChanged(const wchar_t *new_name);
+//private:
+ void RegisterViews(HNAVITEM parent);
+ void Unregister();
+
+ BOOL DisplayDeviceContextMenu(HNAVITEM item, HWND hostWindow, POINT pt);
+ BOOL DisplayPlaylistContextMenu(int playlistId, HNAVITEM item, HWND hostWindow, POINT pt);
+
+// navigation
+ BOOL Navigation_IsPlaylistItem(HNAVITEM item, int *playlistId);
+ HWND Navigation_CreateViewCb(HNAVITEM item, HWND parentWindow);
+ BOOL Navigation_ShowContextMenuCb(HNAVITEM item, HWND hostWindow, POINT pt);
+ BOOL Navigation_ClickCb(HNAVITEM item, int actionType, HWND hostWindow);
+ int Navigation_DropTargetCb(HNAVITEM item, unsigned int dataType, void *data);
+ BOOL Navigation_TitleEditBeginCb(HNAVITEM item);
+ BOOL Navigation_TitleEditEndCb(HNAVITEM item, const wchar_t *title);
+ BOOL Navigation_KeyDownCb(HNAVITEM item, NMTVKEYDOWN *keyData, HWND hwnd);
+
+ size_t GetPlaylistName(wchar_t *buffer, size_t bufferSize, int playlistId, const wchar_t *defaultName, BOOL quoteSpaces);
+
+public:
+ /* ifc_device */
+ int QueryInterface(GUID interface_guid, void **object);
+ const char *GetName();
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height);
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize);
+
+ const char *GetType();
+ const char *GetDisplayType();
+ const char *GetConnection();
+
+ 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 GetDropSupported(unsigned int dataType);
+ HRESULT Drop(void *data, unsigned int dataType);
+
+ HRESULT SetDisplayName(const wchar_t *displayName, bool force);
+
+ HRESULT GetModel(wchar_t *buffer, size_t bufferSize);
+ HRESULT GetStatus(wchar_t *buffer, size_t bufferSize);
+
+ void UpdateActivityState();
+ void UpdateSpaceInfo(BOOL updateUsedSpace, BOOL notifyChanges);
+
+ /* ifc_deviceactivity */
+ BOOL GetActive();
+ BOOL GetCancelable();
+ HRESULT GetProgress(unsigned int *percentCompleted);
+ HRESULT Activity_GetDisplayName(wchar_t *buffer, size_t bufferMax);
+ HRESULT Activity_GetStatus(wchar_t *buffer, size_t bufferMax);
+ HRESULT Cancel(HWND hostWindow);
+
+ /* Dispatchable */
+ size_t AddRef()
+ {
+ return InterlockedIncrement((LONG*)&ref_count);
+ }
+
+ size_t Release()
+ {
+ if (0 == ref_count)
+ return ref_count;
+
+ LONG r = InterlockedDecrement((LONG*)&ref_count);
+ if (0 == r)
+ delete(this);
+
+ return r;
+ }
+
+private:
+ RECVS_MULTIPATCH;
+ std::vector<ifc_deviceevent*> event_handlers;
+ char name[128];
+ size_t ref_count;
+ ~DeviceView();
+ const char *connection_type;
+ const char *display_type;
+ BOOL navigationItemCreated;
+ BOOL activityRunning;
+ unsigned int currentProgress;
+ uint64_t usedSpace;
+ uint64_t totalSpace;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/Filters.cpp b/Src/Plugins/Library/ml_pmp/Filters.cpp
new file mode 100644
index 00000000..19e56861
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/Filters.cpp
@@ -0,0 +1,829 @@
+#include "ArtistAlbumLists.h"
+#include "Filters.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "metadata_utils.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+
+extern int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+
+#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
+static wchar_t GetIndex(wchar_t *name) {
+ SKIP_THE_AND_WHITESPACE(name);
+ return towupper(name[0]);
+}
+
+void Filter::addToGroup(Device *dev, songid_t song, FilterItem * group) {
+ group->length += dev->getTrackLength(song);
+ group->size += dev->getTrackSize(song);
+ group->numTracks++;
+
+ wchar_t buf[16] = {0};
+ dev->getTrackExtraInfo(song, L"cloud", buf, 16);
+ int status = _wtoi(buf);
+ // local and to be uploaded (4) are the same here
+ if (status == 4)
+ {
+ status = 0;
+ }
+
+ group->cloudState += status;
+}
+
+void Filter::AddDefaultColumns(Device * dev, C_ItemList * fields, C_Config * config, int columnStart, bool cloud) {
+ if(nextFilter) fields->Add(new ListField(columnStart+40, 50 ,nextFilter->namePlural, config));
+ fields->Add(new ListField(columnStart+41, 50 , WASABI_API_LNGSTRINGW(IDS_TRACKS), config));
+ if (cloud) fields->Add(new ListField(columnStart+52, 27, WASABI_API_LNGSTRINGW(IDS_CLOUD), config));
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if (!fieldsBits) fieldsBits = -1;
+ if (fieldsBits & SUPPORTS_SIZE) fields->Add(new ListField(columnStart+50, 50, WASABI_API_LNGSTRINGW(IDS_SIZE), config, true));
+ if (fieldsBits & SUPPORTS_LENGTH) fields->Add(new ListField(columnStart+51, 50 ,WASABI_API_LNGSTRINGW(IDS_LENGTH), config, true));
+}
+
+void FilterItem::GetDefaultColumnsCellText(int col, wchar_t*buf, int buflen) {
+ switch (col)
+ {
+ case 40: wsprintf(buf, L"%d", numNextFilter); break;
+ case 41: wsprintf(buf, L"%d", (independentTracks ? independentTracks : numTracks)); break;
+ case 50: WASABI_API_LNG->FormattedSizeString(buf, buflen, (independentSize ? independentSize : size)); break;
+ case 51:
+ {
+ __int64 l = (independentLength ? independentLength : length) / 1000;
+ int x = (int)l / 60;
+ int y = (int)l % 60;
+ wsprintf(buf, L"%d:%02d", x, y);
+ }
+ break;
+ case 52:
+ {
+ int state = (independentCloudState ? independentCloudState : cloudState);
+ if (state % (independentTracks ? independentTracks : numTracks))
+ {
+ wsprintf(buf, L"%d", 2);
+ }
+ else
+ {
+ wsprintf(buf, L"%d", state / (independentTracks ? independentTracks : numTracks));
+ }
+ }
+ break;
+ }
+}
+
+int FilterItem::DefaultSortAction(FilterItem *that, int use_by, int use_dir) {
+ int v = 0;
+ if(use_by==50) { RETIFNZ(this->size - that->size); }
+ if(use_by==51) { RETIFNZ(this->length - that->length); }
+ if(use_by==52) {
+ if (numTracks > 0)
+ {
+ int state = (independentCloudState ? independentCloudState : cloudState);
+ int thatState = (that->independentCloudState ? that->independentCloudState : that->cloudState);
+ if (state % (independentTracks ? independentTracks : numTracks))
+ {
+ if (thatState % (that->independentTracks ? that->independentTracks : that->numTracks))
+ {
+ v = 0;
+ }
+ else
+ {
+ v = (2) - (thatState / (that->independentTracks ? that->independentTracks : that->numNextFilter));
+ }
+ }
+ else
+ {
+ if (thatState % (that->independentTracks ? that->independentTracks : that->numTracks))
+ {
+ v = (state / (independentTracks ? independentTracks : numTracks)) - (2);
+ }
+ else
+ {
+ v = (state / (independentTracks ? independentTracks : numTracks)) - (thatState / (that->independentTracks ? that->independentTracks : that->numNextFilter));
+ }
+ }
+ }
+ RETIFNZ(v);
+ }
+ if(use_by==40) {
+ v=this->numNextFilter - that->numNextFilter;
+ RETIFNZ(v)
+ }
+ if(use_by==41) {
+ if(this->independentTracks || that->independentTracks) v = this->independentTracks - that->independentTracks;
+ else v=this->numTracks - that->numTracks;
+ RETIFNZ(v)
+ }
+ return 0;
+}
+
+FilterItem::~FilterItem() {
+ if(independentNextFilter) delete independentNextFilter; independentNextFilter=0;
+ if(nextFilter) delete nextFilter; nextFilter=0;
+}
+
+class ArtistFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-100,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 100: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_ARTIST),buflen); break;
+ default: GetDefaultColumnsCellText(col-100,buf,buflen); break;
+ }
+ }
+ };
+ ArtistFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTISTS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(100, 170,WASABI_API_LNGSTRINGW(IDS_ARTIST),config));
+ AddDefaultColumns(dev,fields,config,100,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackArtist(a,astr,256);
+ dev->getTrackArtist(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackArtist(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ //ArtistFI * group = (ArtistFI*)group0;
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackArtist(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumFilter : public Filter {
+public:
+ class AlbumFI : public FilterItem {
+ public:
+ AlbumFI(wchar_t *name0) : FilterItem(), yearHigh(0), yearLow(0) { name = _wcsdup(name0); }
+ virtual ~AlbumFI() { free(name); }
+ wchar_t *name;
+ int yearHigh, yearLow;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((AlbumFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-200,use_dir); if(v) return v; }
+ AlbumFI *that = (AlbumFI*)that0;
+ int v;
+ if(use_by==201) { //year
+ v=this->yearHigh - that->yearHigh;
+ RETIFNZ(v)
+ }
+ //by name
+ v = STRCMP_NULLOK(this->name,that->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 200: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_ALBUM),buflen); break;
+ case 201:
+ if(yearHigh>0 && yearLow>0) {
+ if(yearHigh == yearLow) wsprintf(buf,L"%d",yearHigh);
+ else if(yearHigh/100 == yearLow/100) wsprintf(buf,L"%d-%02d",yearLow,yearHigh%100);
+ else wsprintf(buf,L"%d-%d",yearLow,yearHigh);
+ }
+ break;
+ default: GetDefaultColumnsCellText(col-200,buf,buflen); break;
+ }
+ }
+ };
+
+ AlbumFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUMS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ fields->Add(new ListField(200, 170,WASABI_API_LNGSTRINGW(IDS_ALBUM),config));
+ if(!fieldsBits || (fieldsBits & SUPPORTS_YEAR))
+ fields->Add(new ListField(201, 50 ,WASABI_API_LNGSTRINGW(IDS_YEAR),config));
+ AddDefaultColumns(dev,fields,config,200,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbum(a,astr,256);
+ dev->getTrackAlbum(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbum(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ Filter::addToGroup(dev,song,group0);
+ int y = dev->getTrackYear(song);
+ if(y>0) {
+ if(y > group->yearHigh || group->yearHigh<=0) group->yearHigh=y;
+ if(y < group->yearLow || group->yearLow<=0) group->yearLow=y;
+ }
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbum(song,buf,256);
+ AlbumFI * group = new AlbumFI(buf);
+ group->yearHigh = group->yearLow = dev->getTrackYear(song);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+
+class GenreFilter : public Filter {
+public:
+ class GenreFI : public FilterItem {
+ public:
+ GenreFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~GenreFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((GenreFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-300,use_dir); if(v) return v; }
+ GenreFI *a=this,*b = (GenreFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 300: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_GENRE),buflen); break;
+ default: GetDefaultColumnsCellText(col-300,buf,buflen); break;
+ }
+ }
+ };
+ GenreFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_GENRE));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_GENRES)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(300, 170,WASABI_API_LNGSTRINGW(IDS_GENRE),config));
+ AddDefaultColumns(dev,fields,config,300,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackGenre(a,astr,256);
+ dev->getTrackGenre(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ GenreFI * group = (GenreFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackGenre(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ //GenreFI * group = (GenreFI*)group0;
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackGenre(song,buf,256);
+ GenreFI * group = new GenreFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class ArtistIndexFilter : public Filter {
+public:
+ class ArtistIndexFI : public FilterItem {
+ public:
+ ArtistIndexFI(wchar_t *name0) : FilterItem() { name = GetIndex(name0); }
+ virtual ~ArtistIndexFI() { }
+ wchar_t name;
+ virtual bool isWithoutGroup() { return !name; }
+ virtual int compareTo(FilterItem *that) { return name - ((ArtistIndexFI*)that)->name; }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-400,use_dir); if(v) return v; }
+ ArtistIndexFI *that = (ArtistIndexFI*)that0;
+ int v;
+ //by name
+ v = this->name - that->name;
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 400:
+ {
+ if(name) { buf[0] = name; buf[1]=0; }
+ else WASABI_API_LNGSTRINGW_BUF(IDS_NO_ARTIST,buf,buflen);
+ }
+ break;
+ default: GetDefaultColumnsCellText(col-400,buf,buflen); break;
+ }
+ }
+ };
+
+ ArtistIndexFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEX));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEXES)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(400, 170,WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEX),config));
+ AddDefaultColumns(dev,fields,config,400,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackArtist(a,astr,256);
+ dev->getTrackArtist(b,bstr,256);
+ return GetIndex(astr) - GetIndex(bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistIndexFI * group = (ArtistIndexFI*)group0;
+ wchar_t astr[256]=L"";
+ dev->getTrackArtist(song,astr,256);
+ return GetIndex(astr) == group->name;
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackArtist(song,buf,256);
+ ArtistIndexFI * group = new ArtistIndexFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumArtistIndexFilter : public Filter {
+public:
+ class ArtistIndexFI : public FilterItem {
+ public:
+ ArtistIndexFI(wchar_t *name0) : FilterItem() { name = GetIndex(name0); }
+ virtual ~ArtistIndexFI() { }
+ wchar_t name;
+ virtual bool isWithoutGroup() { return !name; }
+ virtual int compareTo(FilterItem *that) { return name - ((ArtistIndexFI*)that)->name; }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-900,use_dir); if(v) return v; }
+ ArtistIndexFI *that = (ArtistIndexFI*)that0;
+ int v;
+ //by name
+ v = this->name - that->name;
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 900:
+ {
+ if(name) { buf[0] = name; buf[1]=0; }
+ else WASABI_API_LNGSTRINGW_BUF(IDS_NO_ALBUM_ARTIST,buf,buflen);
+ }
+ break;
+ default: GetDefaultColumnsCellText(col-900,buf,buflen); break;
+ }
+ }
+ };
+
+ AlbumArtistIndexFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST_INDEX));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEXES)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(900, 170,WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST_INDEX),config));
+ AddDefaultColumns(dev,fields,config,900,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbumArtist(a,astr,256);
+ dev->getTrackAlbumArtist(b,bstr,256);
+ return GetIndex(astr) - GetIndex(bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistIndexFI * group = (ArtistIndexFI*)group0;
+ wchar_t astr[256]=L"";
+ dev->getTrackAlbumArtist(song,astr,256);
+ return GetIndex(astr) == group->name;
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbumArtist(song,buf,256);
+ ArtistIndexFI * group = new ArtistIndexFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class YearFilter : public Filter {
+public:
+ class YearFI : public FilterItem {
+ public:
+ int year;
+ YearFI(int _year) : FilterItem(), year(_year) { if(year<0) year=0; }
+ virtual ~YearFI() { }
+ virtual bool isWithoutGroup() { return year<=0; }
+ virtual int compareTo(FilterItem *that) { return year - ((YearFI*)that)->year; }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-500,use_dir); if(v) return v; }
+ YearFI *a=this,*b = (YearFI*)that0;
+ int v;
+ v = a->year - b->year;
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 500: if(year>0) wsprintf(buf,L"%d",year); else WASABI_API_LNGSTRINGW_BUF(IDS_NO_YEAR,buf,buflen); break;
+ default: GetDefaultColumnsCellText(col-500,buf,buflen); break;
+ }
+ }
+ };
+ YearFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_YEAR));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_YEARS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(500, 170,WASABI_API_LNGSTRINGW(IDS_YEAR),config));
+ AddDefaultColumns(dev,fields,config,500,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ int w = dev->getTrackYear(a);
+ int e = dev->getTrackYear(b);
+ if(w<=0) w=0;
+ if(e<=0) e=0;
+ return w - e;
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ YearFI * group = (YearFI*)group0;
+ int y = dev->getTrackYear(song);
+ if(group->isWithoutGroup() && y<=0) return true;
+ return group->year == y;
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ YearFI * group = new YearFI(dev->getTrackYear(song));
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumArtistFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-600,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 600: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_ALBUM_ARTIST),buflen); break;
+ default: GetDefaultColumnsCellText(col-600,buf,buflen); break;
+ }
+ }
+ };
+ AlbumArtistFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTISTS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(600, 170,WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST),config));
+ AddDefaultColumns(dev,fields,config,600,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbumArtist(a,astr,256);
+ dev->getTrackAlbumArtist(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbumArtist(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbumArtist(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class PublisherFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-700,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 700: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_PUBLISHER),buflen); break;
+ default: GetDefaultColumnsCellText(col-700,buf,buflen); break;
+ }
+ }
+ };
+ PublisherFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_PUBLISHER));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_PUBLISHERS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(700, 170,WASABI_API_LNGSTRINGW(IDS_PUBLISHER),config));
+ AddDefaultColumns(dev,fields,config,700,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackPublisher(a,astr,256);
+ dev->getTrackPublisher(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackPublisher(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackPublisher(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class ComposerFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-800,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 800: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_COMPOSER),buflen); break;
+ default: GetDefaultColumnsCellText(col-800,buf,buflen); break;
+ }
+ }
+ };
+ ComposerFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_COMPOSER));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_COMPOSERS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(800, 170,WASABI_API_LNGSTRINGW(IDS_COMPOSER),config));
+ AddDefaultColumns(dev,fields,config,800,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackComposer(a,astr,256);
+ dev->getTrackComposer(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackComposer(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackComposer(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumArtFilter : public Filter {
+public:
+ class AlbumFI : public FilterItem {
+ public:
+ AlbumFI(Device* dev, wchar_t *artist0, wchar_t *album0, int year, wchar_t *genre0, int rating0, pmpart_t art) :
+ FilterItem(), dev(dev), yearHigh(year), yearLow(year), art(art)
+ {
+ artist = _wcsdup(artist0); album = _wcsdup(album0); genre = _wcsdup(genre0);
+ if(rating0 > 0) rating = rating0;
+ }
+ virtual ~AlbumFI() { free(artist); free(album); free(genre); if(art) dev->releaseArt(art); }
+ Device *dev;
+ wchar_t *artist;
+ wchar_t *album;
+ int yearHigh, yearLow;
+ wchar_t *genre;
+ int rating;
+ pmpart_t art;
+ virtual bool isWithoutGroup() { return !artist[0] && !album[0]; }
+ virtual int compareTo(FilterItem *that) {
+ int x = STRCMP_NULLOK(this->artist,((AlbumFI*)that)->artist);
+ if(x) return x;
+ return STRCMP_NULLOK(this->album,((AlbumFI*)that)->album);
+ }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-1000,use_dir); if(v) return v; }
+ AlbumFI *that = (AlbumFI*)that0;
+ __int64 v;
+ if(use_by==1086) {
+ v=this->size - that->size;
+ RETIFNZ(v)
+ }
+ if(use_by==1085) {
+ v=this->length - that->length;
+ RETIFNZ(v)
+ }
+ if(use_by==1084) {
+ v=this->rating - that->rating;
+ RETIFNZ(v)
+ }
+ if(use_by==1083) {
+ v=STRCMP_NULLOK(this->genre,that->genre);
+ RETIFNZ(v)
+ }
+ if(use_by==1082) { //year
+ v=this->yearHigh - that->yearHigh;
+ RETIFNZ(v)
+ }
+ if(use_by==1001) {
+ v = STRCMP_NULLOK(this->album,that->album);
+ RETIFNZ(v)
+ }
+ //by name
+ v = STRCMP_NULLOK(this->artist,that->artist);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 1000: lstrcpyn(buf,artist[0]?artist:WASABI_API_LNGSTRINGW(IDS_NO_ARTIST),buflen); break;
+ case 1001: lstrcpyn(buf,album[0]?album:WASABI_API_LNGSTRINGW(IDS_NO_ALBUM),buflen); break;
+ case 1082:
+ if(yearHigh>0 && yearLow>0) {
+ if(yearHigh == yearLow) wsprintf(buf,L"%d",yearHigh);
+ else if(yearHigh/100 == yearLow/100) wsprintf(buf,L"%d-%02d",yearLow,yearHigh%100);
+ else wsprintf(buf,L"%d-%d",yearLow,yearHigh);
+ }
+ break;
+ case 1083: lstrcpyn(buf,genre[0]?genre:WASABI_API_LNGSTRINGW(IDS_NO_GENRE),buflen); break;
+ case 1084:
+ if(numTracks > 0) {
+ wchar_t r[] = L"\u2605\u2605\u2605\u2605\u2605";
+ double d = double(rating) / double(numTracks);
+ int rat = int(d);
+ if(d - double(rat) >= 0.5 && rat<5) rat++;
+ if(rat>0 && rat<=5) r[rat]=0;
+ else r[0]=0;
+ lstrcpyn(buf,r,buflen);
+ }
+ break;
+ case 1085: {__int64 l=length/1000; int x = (int)l/60; int y = (int)l%60; wsprintf(buf,L"%d:%02d",x,y); } break;
+ case 1086: WASABI_API_LNG->FormattedSizeString(buf, buflen, size); break;
+ default: GetDefaultColumnsCellText(col-1000,buf,buflen); break;
+ }
+ }
+ virtual pmpart_t GetArt() {return art;}
+ };
+ int mode;
+ AlbumArtFilter() : Filter(),mode(0) { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUMS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits=-1;
+ fields->Add(new ListField(1000, 90,WASABI_API_LNGSTRINGW(IDS_ARTIST),config,0));
+ fields->Add(new ListField(1001, 90,WASABI_API_LNGSTRINGW(IDS_ALBUM),config,0));
+ if(fieldsBits & SUPPORTS_YEAR) fields->Add(new ListField(1082, 50 ,WASABI_API_LNGSTRINGW(IDS_YEAR),config,0));
+ if(fieldsBits & SUPPORTS_GENRE) fields->Add(new ListField(1083, 50 ,WASABI_API_LNGSTRINGW(IDS_GENRE),config,!(mode==1 || mode==2)));
+ if(fieldsBits & SUPPORTS_RATING) fields->Add(new ListField(1084, 50 ,WASABI_API_LNGSTRINGW(IDS_RATING),config,!(mode==1 || mode==2)));
+ if(fieldsBits & SUPPORTS_LENGTH) fields->Add(new ListField(1085, 50 ,WASABI_API_LNGSTRINGW(IDS_LENGTH),config,mode!=2));
+ if(fieldsBits & SUPPORTS_SIZE) fields->Add(new ListField(1086, 50 ,WASABI_API_LNGSTRINGW(IDS_SIZE),config,mode!=2));
+ fields->Add(new ListField(1041, 50 ,WASABI_API_LNGSTRINGW(IDS_TRACKS),config));
+ //AddDefaultColumns(dev,fields,config,1000);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbumArtist(a,astr,256);
+ if(!astr[0]) dev->getTrackArtist(a,astr,256);
+ dev->getTrackAlbumArtist(b,bstr,256);
+ if(!bstr[0]) dev->getTrackArtist(b,bstr,256);
+ int x = STRCMP_NULLOK(astr,bstr);
+ if(x) return x;
+ dev->getTrackAlbum(a,astr,256);
+ dev->getTrackAlbum(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ wchar_t bufa[256]=L"",bufb[256]=L"";
+ dev->getTrackAlbumArtist(song,bufa,256);
+ if(!bufa[0]) dev->getTrackArtist(song,bufa,256);
+ dev->getTrackAlbum(song,bufb,256);
+ return !STRCMP_NULLOK(bufa,group->artist) && !STRCMP_NULLOK(bufb,group->album);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ Filter::addToGroup(dev,song,group0);
+ int y = dev->getTrackYear(song);
+ if(y>0) {
+ if(y > group->yearHigh || group->yearHigh<=0) group->yearHigh=y;
+ if(y < group->yearLow || group->yearLow<=0) group->yearLow=y;
+ }
+ if(!group->art) group->art = dev->getArt(song);
+ else {
+ int w=0,h=0;
+ dev->getArtNaturalSize(group->art,&w,&h);
+ pmpart_t newart = dev->getArt(song);
+ int nw=0,nh=0;
+ dev->getArtNaturalSize(newart,&nw,&nh);
+ if(nw > w && nh > h) {
+ dev->releaseArt(group->art);
+ group->art = newart;
+ } else dev->releaseArt(newart);
+ }
+ int r = dev->getTrackRating(song);
+ if(r > 0) group->rating+=r;
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t bufa[256]=L"";
+ wchar_t bufb[256]=L"";
+ wchar_t bufc[256]=L"";
+ dev->getTrackAlbumArtist(song,bufa,256);
+ if(!bufa[0]) dev->getTrackArtist(song,bufa,256);
+ dev->getTrackAlbum(song,bufb,256);
+ dev->getTrackGenre(song,bufc,256);
+ AlbumFI * group = new AlbumFI(dev,bufa,bufb,dev->getTrackYear(song),bufc,dev->getTrackRating(song),dev->getArt(song));
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+ virtual bool HaveTopItem() {return false;}
+ virtual void SetMode(int m) {mode=m;}
+};
+
+Filter * getFilter(wchar_t *name) {
+ if(name) {
+ if(!_wcsicmp(L"Artist",name)) return new ArtistFilter(); // 100
+ if(!_wcsicmp(L"Album",name)) return new AlbumFilter(); //200
+ if(!_wcsicmp(L"Genre",name)) return new GenreFilter(); //300
+ if(!_wcsicmp(L"Artist Index",name)) return new ArtistIndexFilter(); //400
+ if(!_wcsicmp(L"Year",name)) return new YearFilter(); //500
+ if(!_wcsicmp(L"Album Artist",name)) return new AlbumArtistFilter(); //600
+ if(!_wcsicmp(L"Publisher",name)) return new PublisherFilter(); //700
+ if(!_wcsicmp(L"Composer",name)) return new ComposerFilter(); //800
+ if(!_wcsicmp(L"Album Artist Index",name)) return new AlbumArtistIndexFilter(); //900
+ if(!_wcsicmp(L"Album Art",name)) return new AlbumArtFilter(); //1000
+ }
+ return new ArtistFilter();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/Filters.h b/Src/Plugins/Library/ml_pmp/Filters.h
new file mode 100644
index 00000000..c6be1f54
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/Filters.h
@@ -0,0 +1,58 @@
+#ifndef _FILTERS_H
+#define _FILTERS_H
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <time.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "pmp.h"
+#include "SkinnedListView.h"
+#include "config.h"
+
+class FilterItem {
+public:
+ FilterItem() : numTracks(0), independentTracks(0), nextFilter(0), independentNextFilter(0),
+ numNextFilter(0), size(0), length(0), cloudState(0), independentCloudState(0),
+ independentSize(0), independentLength(0) {}
+ virtual ~FilterItem();
+ int numTracks, cloudState;
+ __int64 size, length;
+ int independentTracks, independentCloudState;
+ __int64 independentSize, independentLength;
+ C_ItemList * independentNextFilter;
+ C_ItemList * nextFilter;
+ int numNextFilter;
+ virtual int compareTo(FilterItem *that)=0;
+ virtual int compareTo2(FilterItem *that, int use_by, int use_dir)=0;
+ virtual void GetCellText(int col, wchar_t * buf, int buflen)=0;
+ void GetDefaultColumnsCellText(int col, wchar_t*buf, int buflen);
+ int DefaultSortAction(FilterItem *that, int use_by, int use_dir);
+ virtual bool isWithoutGroup()=0;
+ virtual pmpart_t GetArt(){return NULL;}
+};
+
+class Filter {
+public:
+ Filter() : nextFilter(0), namePlural(0), name(0) {}
+ virtual ~Filter() { free(name); free(namePlural); }
+ virtual void AddColumns(Device * dev,C_ItemList * fields,C_Config * config, bool cloud = false)=0;
+ void AddDefaultColumns(Device * dev,C_ItemList * fields,C_Config * config, int columnStart, bool cloud = false);
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b)=0;
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group)=0;
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group);
+ virtual FilterItem * newGroup(Device *dev, songid_t song)=0;
+ virtual bool HaveTopItem(){return true;}
+ virtual void SetMode(int mode){}
+ Filter * nextFilter;
+ wchar_t * namePlural;
+ wchar_t * name;
+};
+
+#endif //_FILTERS_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/IconStore.cpp b/Src/Plugins/Library/ml_pmp/IconStore.cpp
new file mode 100644
index 00000000..c5ef3eea
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/IconStore.cpp
@@ -0,0 +1,237 @@
+#include "IconStore.h"
+#include "resource1.h"
+#include "..\..\General\gen_ml/ml.h"
+
+extern winampMediaLibraryPlugin plugin;
+IconStore icon_store;
+
+static int IconStore_RegisterBitmap(HMLIMGLST imageList, int index, HINSTANCE module, const wchar_t *iconName)
+{
+ MLIMAGESOURCE imageSource;
+ MLIMAGELISTITEM listItem;
+
+ imageSource.cbSize = sizeof(imageSource);
+ imageSource.hInst = module;
+ imageSource.lpszName = iconName;
+ imageSource.type = SRC_TYPE_PNG;
+ imageSource.bpp = 32;
+ imageSource.flags = ISF_FORCE_BPP;
+
+ if (NULL == module && FALSE == IS_INTRESOURCE(iconName))
+ imageSource.flags |= ISF_LOADFROMFILE;
+
+ listItem.cbSize = sizeof(listItem);
+ listItem.hmlil = imageList;
+ listItem.filterUID = MLIF_FILTER3_UID;
+ listItem.pmlImgSource = &imageSource;
+ listItem.mlilIndex = index;
+
+ if (listItem.mlilIndex >= 0)
+ {
+ if (FALSE == MLImageList_Replace(plugin.hwndLibraryParent, &listItem))
+ return -1;
+ return listItem.mlilIndex;
+ }
+
+ return MLImageList_Add(plugin.hwndLibraryParent, &listItem);
+}
+
+static BOOL IconStore_IsResourceNameEqual(const wchar_t *name1, const wchar_t *name2)
+{
+ if (FALSE != IS_INTRESOURCE(name1) || FALSE != IS_INTRESOURCE(name2))
+ {
+ return (name1 == name2);
+ }
+
+ return (CSTR_EQUAL == CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT),
+ NORM_IGNORECASE, name1, -1, name2, -1));
+}
+
+static void IconStore_FreeResourceName(wchar_t *name)
+{
+ if (FALSE == IS_INTRESOURCE(name))
+ free(name);
+}
+
+IconStore::IconStore()
+{
+ playlist_icon_index = -1;
+ video_icon_index = -1;
+ device_icon_index = -1;
+ for (int i = 0; i < 4; i++)
+ {
+ active_queue_icon[i] = queue_icon_index[i] = 0;
+ }
+}
+
+IconStore::~IconStore()
+{
+ size_t index;
+
+ index = iconList.size();
+ while(index--)
+ {
+ IconStore_FreeResourceName(iconList[index].name);
+ }
+}
+
+int IconStore::GetPlaylistIcon()
+{
+ if (-1 == playlist_icon_index)
+ {
+ playlist_icon_index = IconStore_RegisterBitmap(MLNavCtrl_GetImageList(plugin.hwndLibraryParent), -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDR_PLAYLIST_ICON));
+ }
+
+ return playlist_icon_index;
+}
+
+int IconStore::GetVideoIcon()
+{
+ if (-1 == video_icon_index)
+ {
+ MLIMAGELISTTAG imageTag;
+
+ imageTag.hmlil = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ imageTag.nTag = 102; // video node image tag registered by ml_local
+
+ if (FALSE != MLImageList_GetIndexFromTag(plugin.hwndLibraryParent, &imageTag))
+ video_icon_index = imageTag.mlilIndex;
+ else
+ video_icon_index = IconStore_RegisterBitmap(imageTag.hmlil, -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDR_VIDEO_ICON));
+ }
+
+ return video_icon_index;
+}
+
+int IconStore::GetDeviceIcon()
+{
+ if (-1 == device_icon_index)
+ {
+ HMLIMGLST imageList;
+
+ imageList = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ if (NULL != imageList)
+ {
+ device_icon_index = IconStore_RegisterBitmap(imageList, -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDR_DEVICE_ICON));
+ }
+ }
+
+ return device_icon_index;
+}
+
+int IconStore::GetQueueIcon(int iconIndex)
+{
+ if (!queue_icon_index[iconIndex])
+ {
+ HMLIMGLST imageList;
+
+ imageList = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ if (NULL != imageList)
+ {
+ queue_icon_index[iconIndex] = IconStore_RegisterBitmap(imageList, -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDB_XFER_QUEUE_16 + iconIndex));
+ }
+ }
+
+ return queue_icon_index[iconIndex];
+}
+
+void IconStore::ReleaseResourceIcon(int iconIndex)
+{
+ if (-1 == iconIndex)
+ return;
+
+ size_t index = iconList.size();
+ while(index--)
+ {
+ ResourceIcon *icon = &iconList[index];
+ if (icon->index == iconIndex)
+ {
+ if (0 != icon->ref)
+ {
+ icon->ref--;
+ if (0 == icon->ref)
+ {
+ IconStore_FreeResourceName(icon->name);
+ icon->name = NULL;
+ icon->module = NULL;
+ }
+ }
+ break;
+ }
+ }
+}
+
+int IconStore::GetResourceIcon(HINSTANCE module, const wchar_t *name)
+{
+ ResourceIcon *icon;
+ size_t index;
+
+ if (module == plugin.hDllInstance &&
+ FALSE != IS_INTRESOURCE(name) &&
+ name == MAKEINTRESOURCE(IDR_DEVICE_ICON))
+ {
+ return GetDeviceIcon();
+ }
+
+ index = iconList.size();
+ while(index--)
+ {
+ icon = &iconList[index];
+ if (icon->module == module &&
+ FALSE != IconStore_IsResourceNameEqual(icon->name, name))
+ {
+ icon->ref++;
+ return icon->index;
+ }
+ }
+
+ return RegisterResourceIcon(module, name);
+}
+
+int IconStore::RegisterResourceIcon(HINSTANCE module, const wchar_t *name)
+{
+ ResourceIcon *icon, iconData;
+ HMLIMGLST imageList;
+ size_t index;
+
+ imageList = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ if (NULL == imageList)
+ return -1;
+
+ index = iconList.size();
+ while(index--)
+ {
+ icon = &iconList[index];
+ if (0 == icon->ref)
+ {
+ break;
+ }
+ }
+ if ((size_t)-1 == index)
+ {
+ icon = &iconData;
+ icon->index = -1;
+ }
+
+ if (FALSE != IS_INTRESOURCE(name))
+ icon->name = (wchar_t*)name;
+ else
+ {
+ icon->name = _wcsdup(name);
+ if (NULL == icon->name)
+ return -1;
+ }
+
+ icon->ref = 1;
+ icon->module = module;
+ icon->index = IconStore_RegisterBitmap(imageList, icon->index, icon->module, icon->name);
+
+ if (-1 != icon->index && &iconData == icon)
+ iconList.push_back(iconData);
+
+ return icon->index;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/IconStore.h b/Src/Plugins/Library/ml_pmp/IconStore.h
new file mode 100644
index 00000000..4e3b379f
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/IconStore.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <vector>
+
+class IconStore
+{
+public:
+ IconStore();
+ ~IconStore();
+
+ int GetPlaylistIcon();
+ int GetVideoIcon();
+ int GetDeviceIcon();
+ int GetQueueIcon(int iconIndex = 0);
+ int GetResourceIcon(HINSTANCE module, const wchar_t *name);
+ void ReleaseResourceIcon(int iconIndex);
+
+private:
+ int RegisterResourceIcon(HINSTANCE module, const wchar_t *name);
+
+private:
+ typedef struct ResourceIcon
+ {
+ size_t ref;
+ int index;
+ wchar_t *name;
+ HINSTANCE module;
+ } ResourceIcon;
+
+ int playlist_icon_index;
+ int video_icon_index;
+ int device_icon_index;
+ int queue_icon_index[4];
+ int active_queue_icon[4];
+
+ std::vector<ResourceIcon> iconList;
+};
+
+extern IconStore icon_store; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/LinkedQueue.cpp b/Src/Plugins/Library/ml_pmp/LinkedQueue.cpp
new file mode 100644
index 00000000..17e9251f
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/LinkedQueue.cpp
@@ -0,0 +1,125 @@
+#include "LinkedQueue.h"
+
+
+LinkedQueue::LinkedQueue() {
+ size=0;
+ head=NULL;
+ tail=NULL;
+ bm=NULL;
+ bmpos=0;
+ InitializeCriticalSection(&cs);
+}
+
+void LinkedQueue::lock() {
+ EnterCriticalSection(&cs);
+ //wchar_t buf[100]; wsprintf(buf,L"Lock taken by %x",GetCurrentThreadId()); OutputDebugString(buf);
+}
+void LinkedQueue::unlock() {
+ LeaveCriticalSection(&cs);
+ //wchar_t buf[100]; wsprintf(buf,L"Lock released by %x",GetCurrentThreadId()); OutputDebugString(buf);
+}
+
+LinkedQueue::~LinkedQueue() {
+ lock();
+ QueueElement * q=head;
+ while(q) { QueueElement *p=q; q=q->next; delete p; }
+ unlock();
+ DeleteCriticalSection(&cs);
+}
+
+void LinkedQueue::Offer(void * e) {
+ lock();
+ if(size==0) { size++; head=tail=new QueueElement(e); unlock(); return; }
+ tail->next=new QueueElement(e);
+ tail->next->prev=tail;
+ tail=tail->next;
+ size++;
+ bm=NULL;
+ unlock();
+}
+
+void * LinkedQueue::Poll() {
+ lock();
+ if(size == 0) { unlock(); return NULL; }
+ size--;
+ void * r = head->elem;
+ QueueElement * q = head;
+ head=head->next;
+ if(head!=NULL) head->prev=NULL;
+ else tail=NULL;
+ delete q;
+ bm=NULL;
+ unlock();
+ return r;
+}
+
+void * LinkedQueue::Peek() {
+ lock();
+ void * ret=head?head->elem:NULL;
+ unlock();
+ return ret;
+}
+
+QueueElement * LinkedQueue::Find(int x) {
+ if(x>=size || x<0) return NULL;
+ if(x == 0) return head;
+ if(x == size-1) return tail;
+ if(!bm) { bm=head; bmpos=0; }
+ int diffh = x;
+ int difft = (size-1) - x;
+ int diffbm = x - bmpos;
+ diffbm>0?diffbm:-diffbm;
+ if(diffh < difft && diffh < diffbm) { bm=head; bmpos=0; }
+ else if(diffh >= difft && diffbm >= difft) { bm=tail; bmpos=size-1; }
+ while(bmpos > x && bm) { bm=bm->prev; bmpos--; }
+ while(bmpos < x && bm) { bm=bm->next; bmpos++; }
+ return bm;
+}
+
+void * LinkedQueue::Get(int pos) {
+ lock();
+ QueueElement * e = Find(pos);
+ unlock();
+ return e?e->elem:NULL;
+}
+
+void LinkedQueue::Set(int pos, void * val) {
+ lock();
+ QueueElement * e = Find(pos);
+ if(e) e->elem=val;
+ unlock();
+}
+
+void* LinkedQueue::Del(int pos) {
+ lock();
+ QueueElement * e = Find(pos);
+ if(!e) { unlock(); return NULL; }
+ else if(size == 1) head=tail=NULL;
+ else if(e==head) {
+ head=head->next;
+ head->prev=NULL;
+ }
+ else if(e==tail) {
+ tail=tail->prev;
+ tail->next=NULL;
+ }
+ else {
+ e->prev->next = e->next;
+ e->next->prev = e->prev;
+ }
+ size--;
+ bm=NULL;
+ unlock();
+ void * ret = e->elem;
+ delete e;
+ return ret;
+}
+
+int LinkedQueue::GetSize() {
+ return size;
+ /*
+ lock();
+ int s = size;
+ unlock();
+ return s;*/
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/LinkedQueue.h b/Src/Plugins/Library/ml_pmp/LinkedQueue.h
new file mode 100644
index 00000000..84310efc
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/LinkedQueue.h
@@ -0,0 +1,41 @@
+#ifndef _LINKEDQUEUE_H_
+#define _LINKEDQUEUE_H_
+
+#include <windows.h>
+
+class LinkedQueue;
+class QueueElement;
+
+class QueueElement {
+public:
+ QueueElement * next;
+ QueueElement * prev;
+ void * elem;
+ QueueElement(void * e) { next=NULL; prev=NULL; elem=e; }
+};
+
+
+class LinkedQueue {
+protected:
+ QueueElement * head;
+ QueueElement * tail;
+ QueueElement * bm;
+ int bmpos;
+ int size;
+ QueueElement * Find(int pos);
+ CRITICAL_SECTION cs;
+public:
+ LinkedQueue();
+ ~LinkedQueue();
+ int GetSize();
+ void Offer(void * e);
+ void *Poll();
+ void *Peek();
+ void *Get(int pos);
+ void Set(int pos, void * val);
+ void *Del(int pos);
+ void lock();
+ void unlock();
+};
+
+#endif //_LINKEDQUEUE_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/PmpDevice.cpp b/Src/Plugins/Library/ml_pmp/PmpDevice.cpp
new file mode 100644
index 00000000..3ef7eae6
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/PmpDevice.cpp
@@ -0,0 +1,557 @@
+#include "main.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include "../nu/refcount.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "DeviceCommands.h"
+#include "resource1.h"
+#include <strsafe.h>
+
+// known commands
+static const char *DEVICE_CMD_VIEW_OPEN = "view_open";
+static const char *DEVICE_CMD_SYNC = "sync";
+static const char *DEVICE_CMD_AUTOFILL = "autofill";
+static const char *DEVICE_CMD_PLAYLIST_CREATE = "playlist_create";
+static const char *DEVICE_CMD_RENAME = "rename";
+static const char *DEVICE_CMD_PREFERENCES = "preferences";
+static const char *DEVICE_CMD_EJECT = "eject";
+static const char *DEVICE_CMD_REMOVE = "remove";
+static const char *DEVICE_CMD_TRANSFER = "transfer";
+static const char *DEVICE_CMD_HIDE = "hide";
+
+extern void UpdateDevicesListView(bool softUpdate);
+
+// we're going to share the command enum stuff for all devices
+
+class PortableDeviceType : public ifc_devicetype
+{
+public:
+ PortableDeviceType()
+ {
+ }
+ const char *GetName()
+ {
+ return "portable";
+ }
+
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize)
+ {
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLE_DEVICE_TYPE, buffer, bufferSize);
+ return S_OK;
+ }
+protected:
+
+#define CBCLASS PortableDeviceType
+ START_DISPATCH_INLINE;
+ CB(API_GETNAME, GetName);
+ CB(API_GETICON, GetIcon);
+ CB(API_GETDISPLAYNAME, GetDisplayName);
+ END_DISPATCH;
+#undef CBCLASS
+};
+
+class USBDeviceConnection : public ifc_deviceconnection
+{
+public:
+ USBDeviceConnection()
+ {
+ }
+ const char *GetName()
+ {
+ return "usb";
+ }
+
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+ {
+ if (FALSE == FormatResProtocol(MAKEINTRESOURCE(IDB_USB),
+ L"PNG", buffer, bufferSize))
+ {
+ return E_FAIL;
+ }
+
+ return S_OK;
+ }
+
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize)
+ {
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_CONNECTION_USB, buffer, bufferSize);
+ return S_OK;
+ }
+protected:
+
+#define CBCLASS USBDeviceConnection
+ START_DISPATCH_INLINE;
+ CB(API_GETNAME, GetName);
+ CB(API_GETICON, GetIcon);
+ CB(API_GETDISPLAYNAME, GetDisplayName);
+ END_DISPATCH;
+#undef CBCLASS
+};
+
+static PortableDeviceType portable_device_type;
+static USBDeviceConnection usb_connection;
+static PortableCommand registered_commands[] =
+{
+ PortableCommand(DEVICE_CMD_VIEW_OPEN, IDS_DEVICE_CMD_VIEW_OPEN, IDS_DEVICE_CMD_VIEW_OPEN_DESC),
+ PortableCommand(DEVICE_CMD_SYNC, IDS_DEVICE_CMD_SYNC, IDS_DEVICE_CMD_SYNC_DESC),
+ PortableCommand(DEVICE_CMD_TRANSFER, IDS_DEVICE_CMD_TRANSFER, IDS_DEVICE_CMD_TRANSFER_DESC),
+ PortableCommand(DEVICE_CMD_EJECT, IDS_DEVICE_CMD_EJECT, IDS_DEVICE_CMD_EJECT_DESC),
+ PortableCommand(DEVICE_CMD_REMOVE, IDS_DEVICE_CMD_REMOVE, IDS_DEVICE_CMD_REMOVE_DESC),
+ PortableCommand(DEVICE_CMD_RENAME, IDS_DEVICE_CMD_RENAME, IDS_DEVICE_CMD_RENAME_DESC),
+ PortableCommand(DEVICE_CMD_AUTOFILL, IDS_DEVICE_CMD_AUTOFILL, IDS_DEVICE_CMD_AUTOFILL_DESC),
+ PortableCommand(DEVICE_CMD_PLAYLIST_CREATE, IDS_DEVICE_CMD_PLAYLIST_CREATE, IDS_DEVICE_CMD_PLAYLIST_CREATE_DESC),
+ PortableCommand(DEVICE_CMD_PREFERENCES, IDS_DEVICE_CMD_PREFERENCES, IDS_DEVICE_CMD_PREFERENCES_DESC),
+ PortableCommand(DEVICE_CMD_HIDE, IDS_DEVICE_CMD_HIDE, IDS_DEVICE_CMD_HIDE),
+};
+
+static ifc_devicecommand * _cdecl
+Devices_RegisterCommand(const char *name, void *user)
+{
+ for(size_t i = 0; i < sizeof(registered_commands)/sizeof(*registered_commands); i++)
+ {
+ if (name == registered_commands[i].GetName())
+ {
+ return &registered_commands[i];
+ }
+ }
+ return NULL;
+}
+
+void Devices_Init()
+{
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ /* register 'portable' device type */
+ ifc_devicetype *type = &portable_device_type;
+ AGAVE_API_DEVICEMANAGER->TypeRegister(&type, 1);
+
+ /* register 'usb' connection type */
+ ifc_deviceconnection *connection = &usb_connection;
+ AGAVE_API_DEVICEMANAGER->ConnectionRegister(&connection, 1);
+
+
+ /* register commands */
+ const char *commands[sizeof(registered_commands)/sizeof(*registered_commands)];
+ for(size_t i = 0; i < sizeof(registered_commands)/sizeof(*registered_commands); i++)
+ {
+ commands[i] = registered_commands[i].GetName();
+ }
+ AGAVE_API_DEVICEMANAGER->CommandRegisterIndirect(commands, sizeof(registered_commands)/sizeof(*registered_commands), Devices_RegisterCommand, NULL);
+ }
+}
+
+int DeviceView::QueryInterface(GUID interface_guid, void **object)
+{
+ if (interface_guid == IFC_Device)
+ {
+ AddRef();
+ *object = (ifc_device *)this;
+ return 0;
+ }
+ return 1;
+}
+
+const char *DeviceView::GetName()
+{
+ return name;
+}
+
+HRESULT DeviceView::GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+{
+ buffer[0]=0;
+ dev->extraActions(DEVICE_GET_ICON, width, height, (intptr_t)buffer);
+ if (buffer[0] == 0)
+ return E_NOTIMPL;
+ else
+ return S_OK;
+
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::GetDisplayName(wchar_t *buffer, size_t bufferSize)
+{
+ // TODO sometimes this is erroring on loading
+ dev->getPlaylistName(0, buffer, bufferSize);
+ return S_OK;
+}
+
+const char *DeviceView::GetType()
+{
+ return "portable";
+}
+
+const char *DeviceView::GetDisplayType()
+{
+ return display_type;
+}
+
+const char *DeviceView::GetConnection()
+{
+ return connection_type;
+}
+
+BOOL DeviceView::GetHidden()
+{
+ return FALSE;
+}
+
+HRESULT DeviceView::GetTotalSpace(uint64_t *size)
+{
+ UpdateSpaceInfo(FALSE, TRUE);
+ *size = dev->getDeviceCapacityTotal();
+ return S_OK;
+}
+
+HRESULT DeviceView::GetUsedSpace(uint64_t *size)
+{
+ if (NULL == size)
+ return E_POINTER;
+
+ UpdateSpaceInfo(TRUE, TRUE);
+ *size = usedSpace;
+
+ return S_OK;
+}
+
+BOOL DeviceView::GetAttached()
+{
+ return TRUE; // ml_pmp devices are by default attached
+}
+
+HRESULT DeviceView::Attach(HWND hostWindow)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::Detach(HWND hostWindow)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::EnumerateCommands(ifc_devicesupportedcommandenum **enumerator, DeviceCommandContext context)
+{
+ DeviceCommandInfo commands[32];
+ size_t count;
+
+ if (NULL == enumerator)
+ return E_POINTER;
+
+ count = 0;
+
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue == NULL)
+ return E_POINTER;
+
+ // return E_NOTIMPL;
+ if (context == DeviceCommandContext_View)
+ {
+ if (0 == txQueue->GetSize())
+ {
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_SYNC, DeviceCommandFlag_Primary);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_AUTOFILL, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PLAYLIST_CREATE, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PREFERENCES, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_EJECT, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_REMOVE, DeviceCommandFlag_None);
+ }
+ }
+ else
+ {
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_VIEW_OPEN, DeviceCommandFlag_Primary);
+
+ DeviceCommandFlags flags = DeviceCommandFlag_None;
+ if (0 != txQueue->GetSize())
+ flags |= DeviceCommandFlag_Disabled;
+
+ if (0 != dev->extraActions(DEVICE_SYNC_UNSUPPORTED,0,0,0))
+ flags |= DeviceCommandFlag_Disabled;
+ SetDeviceCommandInfo(&commands[count++], (!isCloudDevice ? DEVICE_CMD_SYNC : DEVICE_CMD_TRANSFER), flags | DeviceCommandFlag_Group);
+ if (!isCloudDevice) SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_AUTOFILL, flags);
+
+ flags = DeviceCommandFlag_None;
+ if (0 == dev->extraActions(DEVICE_PLAYLISTS_UNSUPPORTED,0,0,0))
+ {
+ // TODO remove once we've got cloud playlists implemented
+ if (!isCloudDevice)
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PLAYLIST_CREATE, flags | DeviceCommandFlag_Group);
+ }
+
+ // adds a specific menu item to hide the 'local library' source
+ if (isCloudDevice)
+ {
+ char name[128] = {0};
+ if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0))
+ {
+ if (!strcmp(name, "local_desktop"))
+ {
+ flags = DeviceCommandFlag_None | DeviceCommandFlag_Group;
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_HIDE, flags);
+ }
+ }
+ }
+
+ bool has_rename = true;
+ flags = DeviceCommandFlag_None;
+ if (0 == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ has_rename = false;
+ else
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_RENAME, flags | DeviceCommandFlag_Group);
+
+ flags = (!has_rename ? DeviceCommandFlag_Group : DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PREFERENCES, flags);
+ if (!dev->extraActions(DEVICE_DOES_NOT_SUPPORT_REMOVE,0,0,0))
+ SetDeviceCommandInfo(&commands[count++], (!isCloudDevice ? DEVICE_CMD_EJECT : DEVICE_CMD_REMOVE), flags | DeviceCommandFlag_Group);
+ }
+
+ *enumerator = new DeviceCommandEnumerator(commands, count);
+ if (NULL == *enumerator)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+HRESULT DeviceView::SendCommand(const char *command, HWND hostWindow, ULONG_PTR param)
+{
+ if (!strcmp(command, DEVICE_CMD_EJECT) || !strcmp(command, DEVICE_CMD_REMOVE))
+ {
+ Eject();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_SYNC) || !strcmp(command, DEVICE_CMD_TRANSFER))
+ {
+ if (!this->isCloudDevice) Sync();
+ else CloudSync();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_AUTOFILL))
+ {
+ Autofill();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_RENAME))
+ {
+ if (NULL != treeItem)
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, treeItem);
+ else
+ RenamePlaylist(0);
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_PLAYLIST_CREATE))
+ {
+ CreatePlaylist();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_PREFERENCES))
+ {
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE,(WPARAM)&devPrefsPage);
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_HIDE))
+ {
+ static int IPC_CLOUD_HIDE_LOCAL = -1;
+ if (IPC_CLOUD_HIDE_LOCAL == -1)
+ IPC_CLOUD_HIDE_LOCAL = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudLocal", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ SENDWAIPC(plugin.hwndWinampParent, IPC_CLOUD_HIDE_LOCAL, 0);
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::GetCommandFlags(const char *command, DeviceCommandFlags *flags)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::GetActivity(ifc_deviceactivity **activity)
+{
+ LinkedQueue * txQueue = getTransferQueue();
+ if (txQueue == NULL || txQueue->GetSize() == 0)
+ {
+ *activity = 0;
+ return S_FALSE;
+ }
+ AddRef();
+ *activity = this;
+ return S_OK;
+}
+
+HRESULT DeviceView::Advise(ifc_deviceevent *handler)
+{
+ event_handlers.push_back(handler);
+ return S_OK;
+}
+
+HRESULT DeviceView::Unadvise(ifc_deviceevent *handler)
+{
+ //event_handlers.eraseObject(handler);
+ auto it = std::find(event_handlers.begin(), event_handlers.end(), handler);
+ if (it != event_handlers.end())
+ {
+ event_handlers.erase(it);
+ }
+
+ return S_OK;
+}
+
+extern C_ItemList devices;
+extern void UpdateDevicesListView(bool softupdate);
+void DeviceView::SetNavigationItem(void *navigationItem)
+{
+ if (navigationItem)
+ RegisterViews((HNAVITEM)navigationItem);
+}
+
+BOOL DeviceView::GetActive()
+{
+ LinkedQueue * txQueue = getTransferQueue();
+ if (txQueue == NULL || txQueue->GetSize() == 0)
+ return FALSE;
+ return TRUE;
+}
+
+BOOL DeviceView::GetCancelable()
+{
+ return FALSE;
+}
+
+HRESULT DeviceView::GetProgress(unsigned int *percentCompleted)
+{
+ LinkedQueue * txQueue = getTransferQueue();
+ LinkedQueue * finishedTX = getFinishedTransferQueue();
+ int txProgress = getTransferProgress();
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ double num = (100.0 * (double)size) - (double)txProgress;
+ double total = (double)100 * size + 100 * (finishedTX ? finishedTX->GetSize() : 0);
+
+ double percent = (0 != total) ? (((total - num) * 100) / total) : 0;
+
+ *percentCompleted = (unsigned int)percent;
+ return S_OK;
+}
+
+HRESULT DeviceView::Activity_GetDisplayName(wchar_t *buffer, size_t bufferMax)
+{
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERRING, buffer, bufferMax);
+ return S_OK;
+}
+
+HRESULT DeviceView::Activity_GetStatus(wchar_t *buffer, size_t bufferMax)
+{
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERRING_DESC, buffer, bufferMax);
+ return S_OK;
+}
+
+HRESULT DeviceView::Cancel(HWND hostWindow)
+{
+// threadKillswitch = 1; // TODO: i think this is how to do it
+ //transferContext.WaitForKill();
+ return S_OK;
+}
+
+HRESULT DeviceView::GetDropSupported(unsigned int dataType)
+{
+ if (dataType == ML_TYPE_ITEMRECORDLISTW
+ || dataType == ML_TYPE_ITEMRECORDLIST
+ || dataType == ML_TYPE_PLAYLIST
+ || dataType == ML_TYPE_PLAYLISTS
+ || dataType == ML_TYPE_FILENAMES
+ || dataType == ML_TYPE_FILENAMESW)
+ return S_OK;
+ return E_FAIL;
+}
+
+HRESULT DeviceView::Drop(void *data, unsigned int dataType)
+{
+ return (HRESULT)TransferFromML(dataType,data,E_FAIL,S_OK);
+}
+
+HRESULT DeviceView::SetDisplayName(const wchar_t *displayName, bool force = 0)
+{
+ if((0 == force && 0 == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0)))
+ return E_FAIL;
+
+ dev->setPlaylistName(0, displayName);
+ free(devPrefsPage.name);
+ devPrefsPage.name = _wcsdup(displayName);
+ SENDWAIPC(plugin.hwndWinampParent, IPC_UPDATE_PREFS_DLGW, (WPARAM)&devPrefsPage);
+
+ DevicePropertiesChanges();
+ UpdateDevicesListView(false);
+
+ OnNameChanged(displayName);
+
+ return S_OK;
+}
+
+HRESULT DeviceView::GetModel(wchar_t *buffer, size_t bufferSize)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ buffer[0] = L'\0';
+
+ if(0 == dev->extraActions(DEVICE_GET_MODEL, (intptr_t)buffer, bufferSize, 0))
+ return E_NOTIMPL;
+
+ return S_OK;
+}
+
+HRESULT DeviceView::GetStatus(wchar_t *buffer, size_t bufferSize)
+{
+ return E_NOTIMPL;
+}
+
+#define CBCLASS DeviceView
+START_MULTIPATCH;
+START_PATCH(PATCH_IFC_DEVICE)
+M_CB(PATCH_IFC_DEVICE, ifc_device, QUERYINTERFACE, QueryInterface);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETNAME, GetName);
+M_CB(PATCH_IFC_DEVICE, ifc_device, ifc_deviceobject::API_GETDISPLAYNAME, GetDisplayName);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETICON, GetIcon);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETTYPE, GetType);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETDISPLAYTYPE, GetDisplayType);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETCONNECTION, GetConnection);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETTOTALSPACE, GetTotalSpace);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETUSEDSPACE, GetUsedSpace);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_ENUMERATECOMMANDS, EnumerateCommands);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_SENDCOMMAND, SendCommand);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETATTACHED, GetAttached);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_ATTACH, Attach);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_DETACH, Detach);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_ADVISE, Advise);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_UNADVISE, Unadvise);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_CREATEVIEW, CreateView);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETACTIVITY, GetActivity);
+M_VCB(PATCH_IFC_DEVICE, ifc_device, API_SETNAVIGATIONITEM, SetNavigationItem);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETDROPSUPPORTED, GetDropSupported);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_DROP, Drop);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_SETDISPLAYNAME, SetDisplayName);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETMODEL, GetModel);
+M_CB(PATCH_IFC_DEVICE, ifc_device, ifc_device::API_GETSTATUS, GetStatus);
+M_CB(PATCH_IFC_DEVICE, ifc_device, ADDREF, AddRef);
+M_CB(PATCH_IFC_DEVICE, ifc_device, RELEASE, Release);
+NEXT_PATCH(PATCH_IFC_DEVICEACTIVITY)
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_GETACTIVE, GetActive);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_GETCANCELABLE, GetCancelable);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_GETPROGRESS, GetProgress);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, ifc_deviceactivity::API_GETDISPLAYNAME, Activity_GetDisplayName);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, ifc_deviceactivity::API_GETSTATUS, Activity_GetStatus);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_CANCEL, Cancel);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, ADDREF, AddRef);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, RELEASE, Release);
+END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/PmpDevice.h b/Src/Plugins/Library/ml_pmp/PmpDevice.h
new file mode 100644
index 00000000..6f70f09b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/PmpDevice.h
@@ -0,0 +1 @@
+#pragma once
diff --git a/Src/Plugins/Library/ml_pmp/SkinnedListView.cpp b/Src/Plugins/Library/ml_pmp/SkinnedListView.cpp
new file mode 100644
index 00000000..348bbd67
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/SkinnedListView.cpp
@@ -0,0 +1,856 @@
+#include "main.h"
+#include "SkinnedListView.h"
+#include "resource1.h"
+#include "api__ml_pmp.h"
+#include "./local_menu.h"
+#include "../replicant/nx/nxstring.h"
+#include <strsafe.h>
+
+extern DeviceView * currentViewedDevice;
+extern winampMediaLibraryPlugin plugin;
+extern HMENU m_context_menus;
+extern HINSTANCE cloud_hinst;
+extern int IPC_GET_CLOUD_HINST;
+
+static bool doneFirstInit=false;
+int (*wad_getColor)(int idx);
+
+#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
+extern int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+
+SkinnedListView::SkinnedListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu)
+ : enableHeaderMenu(enableHeaderMenu), skinlistview_handle(0), contents(0), headerWindow(0)
+{
+ if(!doneFirstInit) {
+ *(void **)&wad_getColor=(void*)SendMessage(libraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ doneFirstInit=true;
+ }
+ this->dlgitem = dlgitem;
+ this->contents = lc;
+ this->libraryParent = libraryParent;
+}
+
+void SkinnedListView::UpdateList(bool softUpdate) {
+ if(!softUpdate) {
+ ListView_SetItemCount(listview.getwnd(),0);
+ ListView_SetItemCount(listview.getwnd(),(contents ? contents->GetNumRows() : 0));
+ }
+ ListView_RedrawItems(listview.getwnd(),0,(contents ? contents->GetNumRows() - 1 : 0));
+}
+
+int SkinnedListView::GetFindItemColumn() {
+ return contents->GetSortColumn();
+}
+
+HMENU SkinnedListView::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu) {
+ HMENU menu;
+
+ menu = GetSubMenu(themenu, (FALSE != isFilter) ? 9 : 8);
+ if (NULL == menu)
+ return NULL;
+
+ if(isFilter)
+ {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i, count;
+ unsigned int filterMarker;
+
+ filterMarker = (((unsigned char)(1+filterNum)) << 24);
+ count = GetMenuItemCount(menu);
+ for(i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(menu,i,TRUE,&m))
+ {
+ m.wID = filterMarker | (m.wID & 0x00FFFFFF);
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ }
+ }
+
+ wchar_t conf[100] = {0};
+ StringCchPrintf(conf, ARRAYSIZE(conf), L"media_scroll_%d",filterNum);
+ bool enablescroll = c->ReadInt(conf,0)!=0;
+
+ CheckMenuItem(menu,
+ filterMarker | (ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR & 0x00FFFFFF),
+ MF_BYCOMMAND | (enablescroll ? MF_CHECKED : MF_UNCHECKED));
+ }
+
+ return menu;
+}
+
+void SkinnedListView::ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent) {
+ int mid = (r >> 24);
+ if(!isFilter && mid) return;
+ if(isFilter && mid-1 != filterNum) return;
+ r &= 0xFFFF;
+ switch(r) {
+ case ID_HEADERWND_CUSTOMIZECOLUMNS:
+ {
+ contents->CustomizeColumns(listview.getwnd(),FALSE);
+ while(ListView_DeleteColumn(listview.getwnd(),0));
+ for(int i=0; i < contents->GetNumColumns(); i++)
+ listview.AddCol(contents->GetColumnTitle(i),contents->GetColumnWidth(i));
+ }
+ break;
+
+ case ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR:
+ {
+ wchar_t conf[100] = {0};
+ StringCchPrintf(conf, ARRAYSIZE(conf), L"media_scroll_%d",filterNum);
+ bool enablescroll = !c->ReadInt(conf,0);
+ c->WriteInt(conf,enablescroll?1:0);
+
+ if (FALSE != MLSkinnedScrollWnd_ShowHorzBar(listview.getwnd(), enablescroll))
+ {
+ RECT rect;
+ if(FALSE != GetWindowRect(listview.getwnd(), &rect))
+ {
+ OffsetRect(&rect, -rect.left, -rect.top);
+
+ SetWindowPos(listview.getwnd(), NULL, 0, 0, rect.right - 1, rect.bottom,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ SetWindowPos(listview.getwnd(), NULL, 0, 0, rect.right, rect.bottom,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+
+ RedrawWindow(listview.getwnd(), NULL, NULL,
+ RDW_INVALIDATE | RDW_ERASE | RDW_FRAME |
+ RDW_ERASENOW | RDW_UPDATENOW);
+ }
+ }
+ }
+ break;
+ }
+}
+
+void SkinnedListView::InitializeFilterData(int filterNum, C_Config *config)
+{
+ wchar_t buffer[64] = {0};
+ BOOL enableHorzScrollbar;
+
+ if (NULL == config)
+ return;
+
+ if (filterNum < 0)
+ return;
+
+ if(FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), L"media_scroll_%d",filterNum)))
+ return;
+
+ enableHorzScrollbar = (FALSE != config->ReadInt(buffer, FALSE));
+ if (FALSE != MLSkinnedScrollWnd_ShowHorzBar(listview.getwnd(), enableHorzScrollbar))
+ {
+ SetWindowPos(listview.getwnd(), NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
+ }
+}
+
+LRESULT SkinnedListView::pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (uMsg == WM_NOTIFY)
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ switch (l->code)
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
+ RECT r = {0};
+ if (lvh.iSubItem)
+ ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r);
+ else
+ {
+ ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS);
+ r.right = r.left + ListView_GetColumnWidth(hwnd, contents->cloudcol);
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2);
+ SetWindowPos(tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ static wchar_t tt_buf[256] = {L""};
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
+ static int last_item = -1;
+
+ if (last_item == lvh.iItem)
+ {
+ lpnmtdi->lpszText = tt_buf;
+ return 0;
+ }
+
+ if (contents->cloud_cache[lvh.iItem] == 4)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else
+ {
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRACK_AVAILABLE, tt_buf, ARRAYSIZE(tt_buf));
+
+ int message = 0x405;
+ wchar_t value[1024] = {0};
+ songid_t s = contents->GetTrack(lvh.iItem);
+ currentViewedDevice->dev->getTrackExtraInfo(s, L"filepath", value, ARRAYSIZE(value));
+ if (!value[0])
+ {
+ message = 0x407;
+ currentViewedDevice->dev->getTrackExtraInfo(s, L"metahash", value, ARRAYSIZE(value));
+ }
+
+ nx_string_t *out_devicenames = 0;
+ size_t num_names = mlplugin->MessageProc(message, (INT_PTR)&value, (INT_PTR)&out_devicenames, 0);
+ if (num_names > 0)
+ {
+ for (size_t i = 0; i < num_names; i++)
+ {
+ if (i > 0) StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), L", ");
+ StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), out_devicenames[i]->string);
+ }
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ if (out_devicenames)
+ NXStringRelease(*out_devicenames);
+ }
+ }
+ }
+ }
+ last_item = lvh.iItem;
+ lpnmtdi->lpszText = tt_buf;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+
+LRESULT SkinnedListView::pmp_listview_alt(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (uMsg == WM_NOTIFY)
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ switch (l->code)
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem > 0 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
+ RECT r = {0};
+ if (lvh.iSubItem)
+ ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r);
+ else
+ {
+ ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS);
+ r.right = r.left + ListView_GetColumnWidth(hwnd, contents->cloudcol);
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2);
+ SetWindowPos(tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ static wchar_t tt_buf[256] = {L""};
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem > 0 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
+ static int last_item = -1;
+
+ if (last_item == lvh.iItem)
+ {
+ lpnmtdi->lpszText = tt_buf;
+ return 0;
+ }
+
+ wchar_t temp[8] = {0};
+ contents->GetCellText(lvh.iItem, lvh.iSubItem, temp, 8);
+ int status = _wtoi(temp);
+ if (status == 0 || status == 4)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_ALL_TRACKS_PLAYABLE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (status == 1)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_ALL_TRACKS_PLAYABLE_HERE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (status == 2)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SOME_TRACKS_PLAYABLE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (status == 3)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_TRACKS_PLAYABLE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ last_item = lvh.iItem;
+ lpnmtdi->lpszText = tt_buf;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+BOOL SkinnedListView::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ #if defined(_UNICODE) || defined(UNICODE)
+ SendMessage(hwndDlg,CCM_SETUNICODEFORMAT,TRUE,0);
+ #endif
+
+ HWND list = GetDlgItem(hwndDlg, dlgitem);
+ headerWindow = (HWND)SendMessageW(list, LVM_GETHEADER, 0, 0L);
+ listview.setwnd(list);
+
+ // setup tooltip handling as needed
+ if (dlgitem == IDC_LIST_ARTIST || dlgitem == IDC_LIST_ALBUM || dlgitem == IDC_LIST_ALBUM2)
+ {
+ if (!GetPropW(list, L"pmp_list_proc")) {
+ SetPropW(list, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(list, GWLP_WNDPROC, (LONG_PTR)this->pmp_listview_alt));
+ SetPropW(list, L"pmp_list_info", (HANDLE)this->contents);
+ }
+ }
+ else if (dlgitem != IDC_LIST_TRANSFERS)
+ {
+ if (!GetPropW(list, L"pmp_list_proc")) {
+ SetPropW(list, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(list, GWLP_WNDPROC, (LONG_PTR)this->pmp_listview));
+ SetPropW(list, L"pmp_list_info", (HANDLE)this->contents);
+ }
+ }
+
+ if(!wParam)
+ {
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ m.hwndToSkin = listview.getwnd();
+ MLSkinWindow(libraryParent, &m);
+ }
+
+ if (contents)
+ {
+ for(int i=0; i < contents->GetNumColumns(); i++)
+ {
+ if (contents->cloud)
+ {
+ listview.AddCol((i == contents->cloudcol ? L"" : contents->GetColumnTitle(i)),contents->GetColumnWidth(i));
+ }
+ else
+ {
+ listview.AddCol(contents->GetColumnTitle(i),contents->GetColumnWidth(i));
+ }
+ }
+ if(contents->GetSortColumn() != -1) // display sort arrow
+ SendMessage(headerWindow,WM_ML_IPC,MAKEWPARAM(contents->GetSortColumn(),!contents->GetSortDirection()),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ UpdateList();
+ }
+ }
+ break;
+
+ case WM_DISPLAYCHANGE:
+ ListView_SetTextColor(listview.getwnd(),wad_getColor?wad_getColor(WADLG_ITEMFG):RGB(0xff,0xff,0xff));
+ ListView_SetBkColor(listview.getwnd(),wad_getColor?wad_getColor(WADLG_ITEMBG):RGB(0x00,0x00,0x00));
+ ListView_SetTextBkColor(listview.getwnd(),wad_getColor?wad_getColor(WADLG_ITEMBG):RGB(0x00,0x00,0x00));
+ listview.SetFont((HFONT)SendMessage(libraryParent, WM_ML_IPC, 66, ML_IPC_SKIN_WADLG_GETFUNC));
+ break;
+
+ case WM_DESTROY:
+ if (contents)
+ {
+ for(int i=0; i<contents->GetNumColumns(); i++)
+ if(contents->GetColumnWidth(i) != listview.GetColumnWidth(i))
+ contents->ColumnResize(i,listview.GetColumnWidth(i));
+ }
+ break;
+
+ case WM_NOTIFYFORMAT:
+ return NFR_UNICODE;
+
+ case WM_CONTEXTMENU:
+ {
+ POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
+ HWND hwndFromChild = WindowFromPoint(pt);
+ if (enableHeaderMenu && hwndFromChild == ListView_GetHeader(listview.getwnd())) {
+ if(contents->CustomizeColumns(listview.getwnd(), TRUE)) {
+ while (ListView_DeleteColumn(listview.getwnd(), 0));
+ for(int i=0; i < contents->GetNumColumns(); i++)
+ listview.AddCol(contents->GetColumnTitle(i),contents->GetColumnWidth(i));
+ }
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==dlgitem) {
+ switch(l->code) {
+ case NM_DBLCLK:
+ break;
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case 0x41: //A
+ if(GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ int num=listview.GetCount();
+ for(int x = 0; x < num; x ++) listview.SetSelected(x);
+ }
+ break;
+ case 0x2E: //Delete
+ break;
+ }
+ break;
+
+ case LVN_ODFINDITEM:
+ {
+ NMLVFINDITEM *t = (NMLVFINDITEM *)lParam;
+ int i=t->iStart;
+ if (i >= contents->GetNumRows()) i=0;
+
+ int cnt=contents->GetNumRows()-i;
+ if (t->lvfi.flags & LVFI_WRAP) cnt+=i;
+
+ while (cnt-->0) {
+ wchar_t tmp[128]=L"";
+ wchar_t *name=0;
+
+ contents->GetCellText(i,GetFindItemColumn(),tmp,sizeof(tmp)/sizeof(wchar_t));
+ name = tmp;
+
+ if (!name) name=L"";
+ else SKIP_THE_AND_WHITESPACE(name)
+
+ if (t->lvfi.flags & (4|LVFI_PARTIAL)) {
+ if (!_wcsnicmp(name,t->lvfi.psz,lstrlen(t->lvfi.psz))) {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else if (t->lvfi.flags & LVFI_STRING) {
+ if (!STRCMP_NULLOK(name,t->lvfi.psz)) {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ if (++i == contents->GetNumRows()) i=0;
+ }
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ break;
+
+ case LVN_GETDISPINFOW:
+ {
+ NMLVDISPINFO *lpdi = (NMLVDISPINFO*) lParam;
+ int item=lpdi->item.iItem;
+ if (item < 0 || contents && item >= contents->GetNumRows()) return 0;
+ if (lpdi->item.mask & LVIF_TEXT) {
+ lpdi->item.pszText[0]=0;
+ contents->GetCellText(item,lpdi->item.iSubItem,lpdi->item.pszText,lpdi->item.cchTextMax);
+ // will cache the cloud status for use in drawing later on (not ideal but it'll do for now)
+ if (lpdi->item.iSubItem == contents->cloudcol) contents->cloud_cache[item] = _wtoi(lpdi->item.pszText);
+ }
+ }
+ break;
+
+ case LVN_COLUMNCLICK:
+ {
+ NMLISTVIEW *p=(NMLISTVIEW*)lParam;
+ contents->ColumnClicked(p->iSubItem);
+ if(contents->GetSortColumn() != -1) {
+ SendMessage(headerWindow,WM_ML_IPC,MAKEWPARAM(contents->GetSortColumn(),!contents->GetSortDirection()),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ }
+ UpdateList();
+ }
+ break;
+ }
+ }
+
+ switch(l->code) {
+ case HDN_ITEMCHANGING:
+ {
+ if (headerWindow == l->hwndFrom)
+ {
+ LPNMHEADERW phdr = (LPNMHEADERW)lParam;
+ if (phdr->pitem && (HDI_WIDTH & phdr->pitem->mask) && phdr->iItem == contents->cloudcol)
+ {
+ INT width = phdr->pitem->cxy;
+ if (MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &width))
+ {
+ phdr->pitem->cxy = width;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+ListContents::~ListContents() {
+ for(int i=0; i<fields.GetSize(); i++) delete ((ListField*)fields.Get(i));
+ for(int i=0; i<hiddenfields.GetSize(); i++) delete ((ListField*)hiddenfields.Get(i));
+}
+
+static int sortFunc_cols(const void *elem1, const void *elem2) {
+ ListField * a = *(ListField **)elem1;
+ ListField * b = *(ListField **)elem2;
+ return a->pos - b->pos;
+}
+
+void ListContents::SortColumns() {
+ for(int i=0; i<fields.GetSize(); i++){
+ ListField *l = (ListField*)fields.Get(i);
+ if(l->hidden) {
+ hiddenfields.Add(l);
+ fields.Del(i--);
+ }
+ }
+ qsort(fields.GetAll(),fields.GetSize(),sizeof(void*),sortFunc_cols);
+}
+
+typedef struct CustomizeColumnsCreateParam
+{
+ ListContents *list;
+ HWND ownerWindow;
+} CustomizeColumnsCreateParam;
+
+static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND m_curlistbox_hwnd, m_availlistbox_hwnd;
+ static ListContents *list;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ CustomizeColumnsCreateParam *param;
+ HWND centerWindow;
+
+ m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1);
+ m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ param = (CustomizeColumnsCreateParam*)lParam;
+ if (NULL != param)
+ {
+ list = param->list;
+ centerWindow = param->ownerWindow;
+ if (NULL == centerWindow)
+ centerWindow = CENTER_OVER_ML_VIEW;
+ }
+ else
+ {
+ list = NULL;
+ centerWindow = CENTER_OVER_ML_VIEW;
+ }
+
+ if (FALSE != CenterWindow(hwndDlg, centerWindow))
+ {
+ if (FALSE == IS_INTRESOURCE(centerWindow))
+ {
+ wchar_t buffer[64] = {0};
+
+ if (FALSE != GetClassName(centerWindow, buffer, ARRAYSIZE(buffer)) &&
+ CSTR_EQUAL == CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT),
+ NORM_IGNORECASE, buffer, -1, L"SysListView32", -1))
+ {
+ RECT rect;
+ long top = 0;
+
+ if (FALSE != GetClientRect(centerWindow, &rect))
+ {
+ MapWindowPoints(centerWindow, HWND_DESKTOP, (POINT*)&rect, 2);
+ top = rect.top;
+
+ HWND headerWindow = (HWND)SendMessage(centerWindow, LVM_GETHEADER, 0, 0L);
+ if (NULL != headerWindow &&
+ (0 != (WS_VISIBLE & GetWindowLongPtr(headerWindow, GWL_STYLE))) &&
+ GetWindowRect(headerWindow, &rect))
+ {
+ if (rect.top == top)
+ top = rect.bottom;
+ }
+ }
+
+ top += 12;
+ if (FALSE != GetWindowRect(hwndDlg, &rect) &&
+ rect.top != top)
+ {
+ SetWindowPos(hwndDlg, NULL, rect.left, top, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
+ }
+ }
+ }
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+ }
+ }
+ case WM_USER + 32:
+ {
+ int i;
+ for (i=0; i<list->fields.GetSize(); i++) {
+ ListField * l = (ListField *)list->fields.Get(i);
+ int r = SendMessage(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l);
+ }
+ for (i=0; i<list->hiddenfields.GetSize(); i++) {
+ ListField * l = (ListField *)list->hiddenfields.Get(i);
+ int r = SendMessage(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name);
+ SendMessage(m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l);
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_DEFS:
+ SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ list->ResetColumns();
+ SendMessage(hwndDlg, WM_USER + 32, 0, 0);
+ break;
+ case IDC_LIST2:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON2:
+ //add column
+ {
+ for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i--, 0);
+ int r = SendMessage(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0);
+ }
+ break;
+ case IDC_LIST1:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON3:
+ //remove column
+ {
+ for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+ int r = SendMessage(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessage(m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0);
+ }
+ break;
+ case IDC_BUTTON4:
+ //move column up
+ {
+ for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i - 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0);
+ int r = (INT)SendMessage(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON5:
+ //move column down
+ {
+ int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (int i = l - 2;i >= 0;i--)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i + 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0);
+ int r = (INT)SendMessage(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDOK:
+ // read and apply changes...
+ {
+ while(list->fields.GetSize()) list->fields.Del(0);
+ while(list->hiddenfields.GetSize()) list->hiddenfields.Del(0);
+ wchar_t buf[100] = {0};
+ int i;
+ int l = (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ list->fields.Add(c);
+ c->pos=i;
+ c->hidden=false;
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colPos_%d",c->field);
+ list->config->WriteInt(buf,i);
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colHidden_%d",c->field);
+ list->config->WriteInt(buf,0);
+ }
+ l = (INT)SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ list->hiddenfields.Add(c);
+ c->hidden=true;
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colHidden_%d",c->field);
+ list->config->WriteInt(buf,1);
+ }
+ list->SortColumns();
+ }
+ EndDialog(hwndDlg, 1);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+bool ListContents::CustomizeColumns(HWND parent, BOOL showmenu) {
+ if(!fields.GetSize()) return false;
+ if(showmenu) {
+ HMENU menu = GetSubMenu(m_context_menus, 8);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, parent, NULL);
+ if(r != ID_HEADERWND_CUSTOMIZECOLUMNS) return false;
+ }
+
+ CustomizeColumnsCreateParam param;
+ param.list = this;
+ param.ownerWindow = parent;
+
+ bool r = !!WASABI_API_DIALOGBOXPARAMW(IDD_CUSTCOLUMNS, parent, custColumns_dialogProc,(LPARAM)&param);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(parent), (this->cloudcol = -1)); // reset the cloud status column so it'll be correctly removed
+ if (cloud)
+ {
+ // not pretty but it'll allow us to know the current
+ // position of the cloud column for drawing purposes
+ for(int i = 0; i < fields.GetSize(); i++)
+ {
+ if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud"))
+ {
+ this->cloudcol = ((ListField *)fields.Get(i))->pos;
+ // update the cloud column
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(parent), this->cloudcol);
+ break;
+ }
+ }
+ }
+
+ return r;
+}
+
+ListField::ListField(int field, int defwidth, wchar_t * name, C_Config * config, bool hidden):field(field),name(name),hiddenDefault(hidden) {
+ wchar_t buf[100] = {0};
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colWidth_%d",field);
+ width = config->ReadInt(buf,defwidth);
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colPos_%d",field);
+ pos = config->ReadInt(buf,field==-1?-1:(field%100));
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colHidden_%d",field);
+ this->hidden = config->ReadInt(buf,hidden?1:0)!=0;
+ this->name = _wcsdup(name);
+}
+
+void ListField::ResetPos() { pos = ((field==-1)?-1:(pos%100)); hidden = hiddenDefault;}
+
+void ListContents::ResetColumns() {
+ while(hiddenfields.GetSize()) { fields.Add(hiddenfields.Get(0)); hiddenfields.Del(0); }
+ for (int i=0; i<fields.GetSize(); i++) ((ListField*)fields.Get(i))->ResetPos();
+ SortColumns();
+}
+
+int ListContents::GetSortDirection() { return TRUE; }
+int ListContents::GetSortColumn() { return -1; }
+void ListContents::ColumnClicked(int col) {}
+void ListContents::ColumnResize(int col, int newWidth) {}
+void ListContents::GetInfoString(wchar_t * buf) { buf[0]=0; } \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/SkinnedListView.h b/Src/Plugins/Library/ml_pmp/SkinnedListView.h
new file mode 100644
index 00000000..3682f9c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/SkinnedListView.h
@@ -0,0 +1,86 @@
+#ifndef _SKINNEDLISTVIEW_H_
+#define _SKINNEDLISTVIEW_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "pmp.h"
+#include "../nu/listview.h"
+#include <map>
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "config.h"
+
+class ListField {
+public:
+ int field;
+ int width;
+ int pos;
+ wchar_t * name;
+ bool hidden, hiddenDefault;
+ ListField() : field(0), width(0), pos(0), name(0), hidden(0), hiddenDefault(0) {};
+ ListField(int field, int defwidth, wchar_t * name, C_Config * config, bool hidden=false);
+ ~ListField() { free(name); }
+ void ResetPos();
+};
+
+class ListContents {
+public:
+ C_ItemList fields, hiddenfields;
+ C_Config * config;
+ int cloud, cloudcol;
+ typedef std::map<size_t, int> CloudCache;
+ CloudCache cloud_cache;
+ Device *dev;
+ ListContents() : config(NULL), cloud(0), cloudcol(-1), dev(0) {};
+ virtual ~ListContents();
+ virtual int GetNumColumns()=0;
+ virtual int GetNumRows()=0;
+ virtual wchar_t * GetColumnTitle(int num)=0;
+ virtual int GetColumnWidth(int num)=0;
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)=0;
+ virtual int GetSortColumn();
+ virtual int GetSortDirection(); // return true for acending.
+ virtual void ColumnClicked(int col); // sort contents and update list
+ virtual void ColumnResize(int col, int newWidth); // called once only just before deconstuctor if changed
+ virtual void GetInfoString(wchar_t * buf);
+ void SortColumns();
+ virtual bool CustomizeColumns(HWND parent, BOOL showmenu);
+ virtual void ResetColumns();
+ virtual pmpart_t GetArt(int row){return NULL;}
+ virtual void SetMode(int mode){}
+ virtual songid_t GetTrack(int pos)=0;
+};
+
+class PrimaryListContents : public ListContents {
+public:
+ virtual void RemoveTrack(songid_t song){}
+};
+
+class SkinnedListView {
+protected:
+ HWND libraryParent, headerWindow;
+ int dlgitem;
+ bool enableHeaderMenu;
+public:
+ int skinlistview_handle;
+ ListContents * contents;
+ W_ListView listview;
+ SkinnedListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu=true);
+ virtual ~SkinnedListView(){}
+ virtual BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ static LRESULT pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ static LRESULT pmp_listview_alt(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ virtual void UpdateList(bool softUpdate=false);
+ virtual int GetFindItemColumn();
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+ virtual void InitializeFilterData(int filterNum, C_Config *config);
+};
+
+#endif //_SKINNEDLISTVIEW_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/api__ml_pmp.h b/Src/Plugins/Library/ml_pmp/api__ml_pmp.h
new file mode 100644
index 00000000..44cc638d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/api__ml_pmp.h
@@ -0,0 +1,56 @@
+#ifndef NULLSOFT_ML_PMP_API_H
+#define NULLSOFT_ML_PMP_API_H
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../playlist/api_playlists.h"
+extern api_playlists *playlistsApi;
+#define AGAVE_API_PLAYLISTS playlistsApi
+
+#include "../ml_local/api_mldb.h"
+extern api_mldb *mldbApi;
+#define AGAVE_API_MLDB mldbApi
+
+#include <api/syscb/api_syscb.h>
+extern api_syscb *sysCallbackApi;
+#define WASABI_API_SYSCB sysCallbackApi
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include <api/memmgr/api_memmgr.h>
+extern api_memmgr *memoryManager;
+#define WASABI_API_MEMMGR memoryManager
+
+#include "../ml_wire/api_podcasts.h"
+extern api_podcasts *podcastsApi;
+#define AGAVE_API_PODCASTS podcastsApi
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include "../nu/threadpool/api_threadpool.h"
+extern api_threadpool *threadPoolApi;
+#define WASABI_API_THREADPOOL threadPoolApi
+
+#include "../devices/api_devicemanager.h"
+extern api_devicemanager *deviceManagerApi;
+#define AGAVE_API_DEVICEMANAGER deviceManagerApi
+
+#include "../Agave/AlbumArt/api_albumart.h"
+extern api_albumart *albumArtApi;
+#define AGAVE_API_ALBUMART albumArtApi
+
+#include "../Agave/Metadata/api_metadata.h"
+extern api_metadata *metadataApi;
+#define AGAVE_API_METADATA metadataApi
+
+#include <api/service/svcs/svc_imgload.h>
+
+#endif // !NULLSOFT_ML_PMP_API_H
diff --git a/Src/Plugins/Library/ml_pmp/autofill.cpp b/Src/Plugins/Library/ml_pmp/autofill.cpp
new file mode 100644
index 00000000..f8eb47a4
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/autofill.cpp
@@ -0,0 +1,432 @@
+/*
+** This is adapted from the autofill code in ml_ipod. It was originally written by and is
+** Copyright (C) Will Fisher and Justin Frankel. It remains under the following licence:
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include <windows.h>
+#include <time.h>
+#include "../../General/gen_ml/ml.h"
+#include "pmp.h"
+#include "../../General/gen_ml/itemlist.h"
+#include "DeviceView.h"
+#include "mt19937ar.h"
+#include "config.h"
+#include "../nu/AutoChar.h"
+#include "metadata_utils.h"
+#include "api__ml_pmp.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+extern C_ItemList * getSelectedItems(bool all=false);
+
+typedef struct {
+ wchar_t * albumName;
+ int rating;
+ float ratingf;
+ int lastplayed;
+ int size;
+ bool album;
+ itemRecordW * ice;
+} AutoFillItem;
+
+static void randomizeList(void *list, int elems, int elemsize) {
+ if (elems < 2) return;
+ if (elemsize < 1) return;
+ char *plist=(char*)list;
+ int p;
+ char *tmp=(char*)calloc(elemsize, sizeof(char));
+ if (!tmp) return;
+ for (p = 0; p < elems; p ++) {
+ int np=genrand_int31()%elems;
+ if (p != np) {
+ np*=elemsize;
+ int pp=p*elemsize;
+ memcpy(tmp,plist+pp,elemsize);
+ memcpy(plist+pp,plist+np,elemsize);
+ memcpy(plist+np,tmp,elemsize);
+ }
+ }
+ free(tmp);
+}
+
+#define atoi_NULLOK(s) ((s)?atoi(s):0)
+#define STRCMP_NULLOK(a,b) _wcsicmp(a?a:L"",b?b:L"")
+
+static int sortFunc_icealbums(const void *elem1, const void *elem2) {
+ itemRecordW * a = (itemRecordW *)elem1;
+ itemRecordW * b = (itemRecordW *)elem2;
+ return STRCMP_NULLOK(a->album,b->album);
+}
+
+
+static void autoFill_songfilter(Device * dev, C_ItemList * list, itemRecordListW * results) {
+ if (results) {
+ for(int i=0; i < results->Size; i++) {
+ AutoFillItem * a = (AutoFillItem *)calloc(sizeof(AutoFillItem),1);
+ a->rating = results->Items[i].rating;
+ a->lastplayed = (int)results->Items[i].lastplay;
+ a->size = (int)dev->getTrackSizeOnDevice(&results->Items[i]);
+ a->album = false;
+ a->ice = &results->Items[i];
+ a->albumName=L"arse";
+ list->Add(a);
+ }
+ }
+}
+
+static void autoFill_songfilter(Device * dev, C_ItemList * list, C_ItemList * results) {
+ for(int i=0; i < results->GetSize(); i++) {
+ itemRecordW *r = (itemRecordW*)results->Get(i);
+ AutoFillItem * a = (AutoFillItem *)calloc(sizeof(AutoFillItem),1);
+ a->rating = r->rating;
+ a->lastplayed = (int)r->lastplay;
+ a->size = (int)dev->getTrackSizeOnDevice(r);
+ a->album = false;
+ a->ice = r;
+ a->albumName=L"arse";
+ list->Add(a);
+ }
+}
+
+static void autoFill_albumfilter(Device * dev, C_ItemList * list, itemRecordListW * results, C_ItemList * res = NULL) {
+ if (results)
+ qsort(results->Items,results->Size,sizeof(itemRecordW),sortFunc_icealbums);
+ AutoFillItem * curalbum = NULL;
+ wchar_t * albumName=NULL;
+ int albumSize=0;
+ __int64 lastplayacc=0;
+ int size = res?res->GetSize():(results?results->Size:0);
+ int i;
+ for(i=0; i < size; i++) {
+ itemRecordW * item = res?(itemRecordW*)res->Get(i):&results->Items[i];
+ if(!curalbum || STRCMP_NULLOK(item->album,albumName) != 0) {
+ if(curalbum && albumSize) {
+ curalbum->lastplayed = (int)(lastplayacc / (__int64)albumSize);
+ curalbum->ratingf /= (float)albumSize;
+ list->Add(curalbum);
+ }
+ AutoFillItem * a = (AutoFillItem *)calloc(sizeof(AutoFillItem),1);
+ a->ratingf = (float)item->rating;
+ lastplayacc = item->lastplay;
+ a->size = (int)dev->getTrackSizeOnDevice(item);
+ a->album = true;
+ a->albumName = item->album;
+ curalbum = a;
+ albumName = a->albumName;
+ albumSize++;
+ } else {
+ albumSize++;
+ curalbum->size += (int)dev->getTrackSizeOnDevice(item);
+ curalbum->ratingf += (float)item->rating;
+ lastplayacc += item->lastplay;
+ }
+ }
+ if(curalbum && albumSize) {
+ curalbum->lastplayed = (int)(lastplayacc / (__int64)albumSize);
+ curalbum->ratingf /= (float)albumSize;
+ list->Add(curalbum);
+ }
+
+ //FUCKO: think of something clever to make the ratings relevant
+ for(i=0; i < list->GetSize(); i++) {
+ AutoFillItem * a = (AutoFillItem *)list->Get(i);
+ a->rating = (int)(a->ratingf + 0.5);
+ if(a->rating > 5) a->rating = 5;
+ }
+}
+
+// FUCKO lame O(n^2) gayness. Make this better.
+static void autoFill_addAlbums(C_ItemList * albums, itemRecordListW * songs, C_ItemList * dest) {
+ if (songs) {
+ for(int i=0; i < songs->Size; i++)
+ for(int j=0; j < albums->GetSize(); j++)
+ if(STRCMP_NULLOK(songs->Items[i].album,(wchar_t *)albums->Get(j))==0)
+ dest->Add(&songs->Items[i]);
+ }
+}
+
+static int sortFunc_autofill(const void *elem1, const void *elem2)
+{
+ AutoFillItem *a=(AutoFillItem *)*(void **)elem1;
+ AutoFillItem *b=(AutoFillItem *)*(void **)elem2;
+ return a->lastplayed - b->lastplayed;
+}
+
+itemRecordListW * generateAutoFillList(DeviceView * dev, C_Config * config)
+{
+ // Settings for an autofill
+ int lastAutofill=config->ReadInt(L"LastAutoFill",(int)time(NULL));
+ int fillpc = config->ReadInt(L"FillPercent",90);; // fill device 90% full.
+ bool byAlbum = dev->dev->getDeviceCapacityTotal() > (__int64)1500000000;
+ byAlbum = config->ReadInt(L"AlbumAutoFill",byAlbum?1:0)==1;
+ wchar_t * afquery = _wcsdup(config->ReadString(L"AutoFillQuery",L"length > 30"));
+ wchar_t * squery = _wcsdup(config->ReadString(L"SyncQuery",L"type = 0"));
+ char * tmp2 = AutoCharDup(config->ReadString(L"AutoFillRatings",L"")); // ratings ratio string (eg "3:1:1:1:0:0")
+ int len = 2*strlen(tmp2)+2;
+ char * tmp = (char*)calloc(len,sizeof(char));
+ strncpy(tmp,tmp2,len); free(tmp2);
+
+ int ratingsRatios[6]={0,0,0,0,0,0,};
+ bool useratings=true;
+ if(tmp[0]==0) useratings=false;
+ int i=0;
+ int ratingsRatiosTotal=0;
+ if(useratings) {
+ int len = strlen(tmp);
+ while(tmp[++i]!=0) if(tmp[i]==':') tmp[i]=0;
+ tmp[i+1]=0;
+ char * p = &tmp[0];
+ for(i=0; i<6; i++) {
+ if(p > len + tmp) break;
+ if(*p == 0) break;
+ ratingsRatios[i] = atoi_NULLOK(p);
+ ratingsRatiosTotal+=ratingsRatios[i];
+ p+=strlen(p)+1;
+ }
+ }
+ if(ratingsRatiosTotal==0) {
+ ratingsRatiosTotal=6;
+ for(i=0; i<6; i++) ratingsRatios[i]=1;
+ }
+
+ //construct query
+ wchar_t query[2048]=L"";
+ if(afquery[0]==0) wsprintf(query,L"%s",squery);
+ else wsprintf(query,L"(%s) AND (%s)",squery,afquery);
+
+ //run query
+ itemRecordListW *items = 0;
+ itemRecordListW *results = AGAVE_API_MLDB?AGAVE_API_MLDB->Query(query):0;
+ if (results)
+ {
+ C_ItemList * songList = new C_ItemList;
+
+ if(!byAlbum) autoFill_songfilter(dev->dev,songList, results);
+ else autoFill_albumfilter(dev->dev,songList, results);
+
+ //dump into ratings "bins"
+ C_ItemList * songs[7];
+ for(i=0; i<7; i++) songs[i] = new C_ItemList;
+ for(i=0; i<songList->GetSize(); i++) {
+ if(useratings) {
+ int rating = ((AutoFillItem*)songList->Get(i))->rating;
+ switch (rating) {
+ case 1: songs[1]->Add(songList->Get(i)); break;
+ case 2: songs[2]->Add(songList->Get(i)); break;
+ case 3: songs[3]->Add(songList->Get(i)); break;
+ case 4: songs[4]->Add(songList->Get(i)); break;
+ case 5: songs[5]->Add(songList->Get(i)); break;
+ default: songs[0]->Add(songList->Get(i)); break;
+ }
+ } else songs[0]->Add(songList->Get(i));
+ }
+
+ for(i=0; i<6; i++)
+ {
+ randomizeList(songs[i]->GetAll(),songs[i]->GetSize(),sizeof(void*)); // randomize
+ qsort(songs[i]->GetAll(),songs[i]->GetSize(),sizeof(void*),sortFunc_autofill); // sort by date
+ }
+
+
+ __int64 sizeToFill, totalSize=0;
+
+ sizeToFill = (((__int64)fillpc) * dev->dev->getDeviceCapacityTotal()) / ((__int64)100);
+ //sizeToFill = dev->dev->getDeviceCapacityTotal();
+
+ C_ItemList * alreadyIn = new C_ItemList;
+ C_ItemList * notGoingIn = new C_ItemList;
+ C_ItemList * songsToSend = new C_ItemList;
+
+ if(!byAlbum) {
+
+ int numTracks = dev->dev->getPlaylistLength(0);
+ /* Hmm. This should make autofill just replace selected songs
+ ** gonna leave it out for now, until i have good ipod shuffle (et al) support.
+ C_ItemList * selected = getSelectedItems(false);
+ int j=0;
+ if(selected) {
+ if(selected->GetSize() > 0 && selected->GetSize() < numTracks) {
+ for(i=0; i<numTracks; i++) {
+ if(j < selected->GetSize()) if((songid_t)selected->Get(j) == dev->dev->getPlaylistTrack(0,i)) {
+ j++;
+ notGoingIn->Add(selected->Get(j));
+ } else alreadyIn->Add(dev->dev->getPlaylistTrack(0,i));
+ }
+ }
+ delete selected;
+ }
+ */
+ // otherwise replace ones played since last autofill, unless none played, in which case replace all
+ if(notGoingIn->GetSize() == 0) {
+ delete alreadyIn;
+ alreadyIn = new C_ItemList;
+ if(lastAutofill > 0)
+ {
+ for(i=0; i<numTracks; ++i)
+ if(dev->dev->getTrackLastPlayed(dev->dev->getPlaylistTrack(0,i)) >= lastAutofill) break;
+
+ if (i >= numTracks) // this means nothing has been played, so in this case we set everything to be replaced
+ {
+ for(i=0; i<numTracks; ++i) notGoingIn->Add((void*)dev->dev->getPlaylistTrack(0,i));
+ }
+ else
+ {
+ for(i=0; i < numTracks; ++i)
+ {
+ songid_t s = dev->dev->getPlaylistTrack(0,i);
+ if(dev->dev->getTrackLastPlayed(s) <= lastAutofill) alreadyIn->Add((void*)s);
+ }
+ }
+ }
+ }
+
+ //remove our already selected songs from the bins
+ for(i=0; i<notGoingIn->GetSize(); ++i) {
+ songid_t s = (songid_t)notGoingIn->Get(i);
+ for (int b=0; b < 6; b ++)
+ {
+ for(int j=0; j<songs[b]->GetSize(); ++j)
+ {
+ if(!compareItemRecordAndSongId(((AutoFillItem *)songs[b]->Get(j))->ice,s, dev->dev))
+ { // FUCKO
+ songs[6]->Add(songs[b]->Get(j));
+ songs[b]->Del(j);
+ b=6;
+ break;
+ }
+ }
+ }
+ }
+ for(i=0; i<alreadyIn->GetSize(); ++i) {
+ songid_t s = (songid_t)alreadyIn->Get(i);
+ for (int b= 0; b < 6; b ++)
+ {
+ for(int j=0; j<songs[b]->GetSize(); ++j)
+ {
+ if(!compareItemRecordAndSongId(((AutoFillItem *)songs[b]->Get(j))->ice,s, dev->dev))
+ {
+ songsToSend->Add(((AutoFillItem *)songs[b]->Get(j))->ice);
+ songs[b]->Del(j);
+ b=6;
+ break;
+ }
+ }
+ }
+
+ __int64 size = (__int64)dev->dev->getTrackSize(s);
+
+ if(totalSize + size >= sizeToFill) break;
+ totalSize += size;
+ }
+ } // end if(!byAlbum)
+
+ //select the rest!
+ C_ItemList * albumsToSend = new C_ItemList;
+
+ while(true) {
+ int bin=0;
+ if (useratings)
+ {
+ int val = genrand_int31() % ratingsRatiosTotal;
+ bin=-1;
+ while(val>=0 && bin < 5) val -= ratingsRatios[++bin];
+
+ if (bin > 5) bin=5;
+ else if(bin<0) bin=0;
+
+ bin = 5-bin; // work in reverse mapped bin now (since ratingsRatio is 5-0)
+
+ int sbin=bin;
+ while(bin<6&&songs[bin]->GetSize()<=0) { bin++; } // if our bin is empty, go to a higher bin
+
+ if (bin > 5) // out of higher rated bins, go lower
+ {
+ bin=sbin-1; // start at one lower than where we started
+ while (bin >= 0 && songs[bin]->GetSize()<=0) bin--; // if our bin is still empty, go to a lower bin
+ }
+
+ if (bin < 0) bin = 6;
+ } else bin = songs[0]->GetSize()?0:6;
+ if(!songs[bin])
+ break;
+ int bsize=songs[bin]->GetSize();
+ if (bsize<1)
+ break;
+
+ // JF> will had a %(bsize/2) here effectively, which I think is too major.
+ // I propose a nice simple weighted thing, where stuff near the beginning is more likely
+ // to be picked.
+ int snum = genrand_int31() % bsize;
+ if (genrand_int31()&1) snum/=2; // half the time, just use the first half (less recently played items)
+
+ int size = ((AutoFillItem *)songs[bin]->Get(snum))->size;
+ if(size <= 0)
+ {
+ songs[bin]->Del(snum);
+ continue;
+ }
+ totalSize += (__int64)size;
+
+ /*{
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"not full. must fill: %d, so far: %d, this track: %d",(int)sizeToFill,(int)totalSize,(int)size);
+ OutputDebugString(buf);
+ }*/
+
+ if(totalSize >= sizeToFill)
+ {
+ /*OutputDebugString(L"full");*/
+ break;
+ }
+
+ if(byAlbum) {
+ albumsToSend->Add(((AutoFillItem *)songs[bin]->Get(snum))->albumName);
+ } else {
+ songsToSend->Add(((AutoFillItem *)songs[bin]->Get(snum))->ice);
+ }
+ songs[bin]->Del(snum);
+ }
+
+ // dump our albums into songsToSend
+ if(albumsToSend->GetSize() > 0)
+ autoFill_addAlbums(albumsToSend, results, songsToSend);
+
+ items = new itemRecordListW;
+ items->Alloc = songsToSend->GetSize();
+ items->Size = songsToSend->GetSize();
+ items->Items = (itemRecordW*)calloc(items->Alloc, sizeof(itemRecordW));
+
+ for(i=0; i<songsToSend->GetSize(); ++i) copyRecord(&items->Items[i],(itemRecordW*)songsToSend->Get(i));
+
+ // clear stuff up
+ for(i=0; i < songList->GetSize(); i++) free(songList->Get(i));
+ for(i=0; i<7; ++i) delete songs[i];
+ delete songsToSend;
+ delete albumsToSend;
+ delete songList;
+
+ delete notGoingIn;
+ delete alreadyIn;
+
+ AGAVE_API_MLDB->FreeRecordList(results); //free memory
+ }
+
+ free(afquery);
+ free(squery);
+ free(tmp);
+
+ return items;
+}
diff --git a/Src/Plugins/Library/ml_pmp/banner.cpp b/Src/Plugins/Library/ml_pmp/banner.cpp
new file mode 100644
index 00000000..9e3861c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/banner.cpp
@@ -0,0 +1,216 @@
+#include ".\banner.h"
+#include "..\gen_ml\graphics.h"
+
+
+MLBanner::MLBanner(void)
+{
+ bmpBck = NULL;
+ bmpLogo = NULL;
+ bmpLogoMask = NULL;
+ bmpBanner = NULL;
+
+ oldWndProc = NULL;
+
+ color1 = RGB(0,0,0);
+ color2 = RGB(255,255,255);
+
+ hInstance = NULL;
+ logoResId = 0;
+ bgndResId = 0;
+ m_hwnd = 0;
+
+ SetRect(&rcBanner, 0,0,0,0);
+}
+MLBanner::~MLBanner(void)
+{
+ DestroyImages();
+ SetWindowLongPtr(m_hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc);
+ oldWndProc = NULL;
+}
+
+void MLBanner::SetColors(int color1, int color2)
+{
+ this->color1 = color1;
+ this->color2 = color2;
+ ReloadImages();
+}
+
+void MLBanner::SetImages(HINSTANCE hInstance, int bgndResId, int logoResId)
+{
+ this->hInstance = hInstance;
+ this->logoResId = logoResId;
+ this->bgndResId = bgndResId;
+ ReloadImages();
+}
+
+void MLBanner::ReloadImages(void)
+{
+ DestroyImages();
+ if (hInstance)
+ {
+ if (bgndResId)
+ {
+ bmpBck = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(bgndResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpBck) bmpBck = PatchBitmapColors24(bmpBck, color1, color2, Filter1);
+ }
+ if (logoResId)
+ {
+ bmpLogo = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(logoResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpLogo)
+ {
+ bmpLogoMask = CreateBitmapMask(bmpLogo, 1,1);
+ }
+ }
+ }
+
+}
+
+void MLBanner::DestroyImages(void)
+{
+ if (bmpBck) DeleteObject(bmpBck);
+ bmpBck = NULL;
+
+ if (bmpLogo) DeleteObject(bmpLogo);
+ bmpLogo = NULL;
+
+ if (bmpLogoMask) DeleteObject(bmpLogoMask);
+ bmpLogoMask = NULL;
+
+ if (bmpBanner) DeleteObject(bmpBanner);
+ bmpBanner = NULL;
+}
+
+
+
+void MLBanner::UpdateBunnerBmp(void)
+{
+ if (bmpBanner) DeleteObject(bmpBanner);
+
+ HDC hdc = GetDC(m_hwnd);
+
+ bmpBanner = CreateCompatibleBitmap(hdc, rcBanner.right, rcBanner.bottom);
+ HDC memDstDC = CreateCompatibleDC (hdc);
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ SelectObject(memDstDC, bmpBanner);
+ SelectObject(memSrcDC, bmpBck);
+
+ for (int i = 0; i < rcBanner.right; i++)
+ {
+ BitBlt(memDstDC,
+ i,0,
+ 1, rcBanner.bottom,
+ memSrcDC,
+ 0,0,
+ SRCCOPY);
+
+ }
+
+ BITMAP bm;
+ GetObject(bmpLogo, sizeof(BITMAP), &bm);
+
+ SelectObject(memSrcDC, bmpLogoMask);
+ BitBlt(memDstDC,
+ 6,
+ max(2, (rcBanner.bottom - bm.bmHeight) / 2),
+ min(rcBanner.right - 4, bm.bmWidth),
+ min(rcBanner.bottom - 2, bm.bmHeight),
+ memSrcDC,
+ 0,0,
+ SRCAND);
+
+ SelectObject(memSrcDC, bmpLogo);
+ BitBlt(memDstDC,
+ 6,
+ max(2, (rcBanner.bottom - bm.bmHeight) / 2),
+ min(rcBanner.right - 4, bm.bmWidth),
+ min(rcBanner.bottom - 2, bm.bmHeight),
+ memSrcDC,
+ 0,0,
+ SRCPAINT);
+
+ ReleaseDC(m_hwnd, hdc);
+ DeleteDC(memDstDC);
+ DeleteDC(memSrcDC);
+
+}
+
+void MLBanner::Init(HWND hwnd)
+{
+ m_hwnd = hwnd;
+ SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)this);
+ oldWndProc= (WNDPROC) SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newWndProc);
+ UpdateBunnerBmp();
+}
+
+INT_PTR CALLBACK MLBanner::newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ MLBanner *banner = (MLBanner*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(uMsg)
+ {
+ case WM_SIZE:
+ if (SIZE_MINIMIZED != wParam)
+ {
+ SetRect(&banner->rcBanner, 0,0,LOWORD(lParam),HIWORD(lParam));
+ banner->UpdateBunnerBmp();
+ }
+ break;
+ case WM_ERASEBKGND:
+ {
+ HDC hdc = GetDC(hwndDlg);
+ if (banner->bmpBanner)
+ {
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ SelectObject(memSrcDC, banner->bmpBanner);
+ StretchBlt( hdc,
+ banner->rcBanner.left,
+ banner->rcBanner.top,
+ banner->rcBanner.right - banner->rcBanner.left,
+ banner->rcBanner.bottom - banner->rcBanner.top,
+ memSrcDC,
+ banner->rcBanner.left,
+ banner->rcBanner.top,
+ banner->rcBanner.right - banner->rcBanner.left,
+ banner->rcBanner.bottom - banner->rcBanner.top,
+ SRCCOPY);
+ DeleteDC(memSrcDC);
+ }
+ ReleaseDC(hwndDlg, hdc);
+ }
+ return TRUE;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT pt;
+ HDC hdc = BeginPaint(hwndDlg, &pt);
+ if (!banner->bmpBanner)
+ {
+ SetRect(&banner->rcBanner, 0,0,pt.rcPaint.right - pt.rcPaint.left, pt.rcPaint.bottom - pt.rcPaint.top);
+ banner->UpdateBunnerBmp();
+ }
+ if (banner->bmpBanner)
+ {
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ SelectObject(memSrcDC, banner->bmpBanner);
+ StretchBlt( hdc,
+ pt.rcPaint.left,
+ pt.rcPaint.top,
+ pt.rcPaint.right - pt.rcPaint.left,
+ pt.rcPaint.bottom - pt.rcPaint.top,
+ memSrcDC,
+ pt.rcPaint.left,
+ pt.rcPaint.top,
+ pt.rcPaint.right - pt.rcPaint.left,
+ pt.rcPaint.bottom - pt.rcPaint.top,
+ SRCCOPY);
+ DeleteDC(memSrcDC);
+ ValidateRect(hwndDlg, &pt.rcPaint);
+ }
+ EndPaint(hwndDlg, &pt);
+ }
+ break;
+ }
+
+ return CallWindowProc(banner->oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/banner.h b/Src/Plugins/Library/ml_pmp/banner.h
new file mode 100644
index 00000000..85ce0fc1
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/banner.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOFT_ML_BANNER_HEADER
+#define NULLSOFT_ML_BANNER_HEADER
+
+#include <windows.h>
+
+class MLBanner
+{
+public:
+ MLBanner(void);
+ ~MLBanner(void);
+
+public:
+
+ void SetColors(int color1, int color2);
+ void SetImages(HINSTANCE hInstance, int bgndResId, int logoResId);
+ void Init(HWND hwnd);
+ void ReloadImages(void);
+
+protected:
+ void DestroyImages(void);
+ void UpdateBunnerBmp(void);
+ static INT_PTR CALLBACK newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+private:
+
+ HWND m_hwnd;
+ HBITMAP bmpBck;
+ HBITMAP bmpLogo;
+ HBITMAP bmpLogoMask;
+ HBITMAP bmpBanner;
+
+ WNDPROC oldWndProc;
+
+ HINSTANCE hInstance;
+ int logoResId;
+ int bgndResId;
+
+ int color1;
+ int color2;
+
+ RECT rcBanner;
+};
+
+#endif // NULLSOFT_ML_BANNER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/config.cpp b/Src/Plugins/Library/ml_pmp/config.cpp
new file mode 100644
index 00000000..c14f110d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/config.cpp
@@ -0,0 +1,50 @@
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+
+#include "config.h"
+
+C_Config::~C_Config()
+{
+ free(m_inifile);
+ free(m_section);
+}
+
+C_Config::C_Config(wchar_t *ini, wchar_t *section, C_Config * globalWrite) : globalWrite(globalWrite)
+{
+ m_strbuf[0]=0;
+ m_inifile=_wcsdup(ini);
+ m_section=_wcsdup(section);
+}
+
+void C_Config::WriteInt(wchar_t *name, int value, wchar_t *section)
+{
+ wchar_t buf[32] = {0};
+ wsprintf(buf,L"%d",value);
+ WriteString(name,buf,section);
+}
+
+int C_Config::ReadInt(wchar_t *name, int defvalue, wchar_t *section)
+{
+ return GetPrivateProfileInt(section?section:m_section,name,defvalue,m_inifile);
+}
+
+wchar_t *C_Config::WriteString(wchar_t *name, wchar_t *string, wchar_t *section)
+{
+ if(globalWrite && !section) globalWrite->WriteString(name,string,m_section);
+ WritePrivateProfileString(section?section:m_section,name,string,m_inifile);
+ return name;
+}
+
+wchar_t *C_Config::ReadString(wchar_t *name, wchar_t *defstr, wchar_t *section)
+{
+ static wchar_t foobuf[] = L"___________config_lameness___________";
+ m_strbuf[0]=0;
+ GetPrivateProfileString(section?section:m_section,name,foobuf,m_strbuf,sizeof(m_strbuf)/sizeof(wchar_t),m_inifile);
+ if (!lstrcmp(foobuf,m_strbuf)) return defstr;
+
+ m_strbuf[sizeof(m_strbuf)/sizeof(wchar_t)-1]=0;
+ return m_strbuf;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/config.h b/Src/Plugins/Library/ml_pmp/config.h
new file mode 100644
index 00000000..1343fe6f
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/config.h
@@ -0,0 +1,21 @@
+#ifndef _C_CONFIG_H_
+#define _C_CONFIG_H_
+
+class C_Config
+{
+ public:
+ C_Config(wchar_t *ini,wchar_t *section=L"ml_pmp", C_Config * globalWrite=NULL);
+ ~C_Config();
+ void WriteInt(wchar_t *name, int value, wchar_t *section=NULL);
+ wchar_t *WriteString(wchar_t *name, wchar_t *string, wchar_t *section=NULL);
+ int ReadInt(wchar_t *name, int defvalue, wchar_t *section=NULL);
+ wchar_t *ReadString(wchar_t *name, wchar_t *defvalue, wchar_t *section=NULL);
+ wchar_t *GetIniFile(){return m_inifile;}
+ private:
+ wchar_t m_strbuf[8192];
+ wchar_t *m_inifile;
+ wchar_t *m_section;
+ C_Config * globalWrite;
+};
+
+#endif//_C_CONFIG_H_
diff --git a/Src/Plugins/Library/ml_pmp/editinfo.cpp b/Src/Plugins/Library/ml_pmp/editinfo.cpp
new file mode 100644
index 00000000..cbd40630
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/editinfo.cpp
@@ -0,0 +1,790 @@
+#include "main.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include <api/service/waServiceFactory.h>
+#include <api/service/svcs/svc_imgload.h>
+#include <api/service/svcs/svc_imgwrite.h>
+#include <api/memmgr/api_memmgr.h>
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+
+extern C_ItemList devices;
+extern HWND hwndMediaView;
+
+static C_ItemList * editItems;
+static Device * editDevice;
+
+typedef struct {
+ int w;
+ int h;
+ ARGB32 * data;
+} editinfo_image;
+
+static INT_PTR CALLBACK editInfo_commit_dialogProc(HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static int i;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ i=0;
+ SetWindowText(hwnd,WASABI_API_LNGSTRINGW(IDS_SETTING_METADATA));
+ SendDlgItemMessage(hwnd,IDC_PROGRESS,PBM_SETRANGE,0,MAKELPARAM(0, editItems->GetSize()));
+ SetTimer(hwnd,1,5,NULL);
+
+ if (FALSE != CenterWindow(hwnd, (HWND)lParam))
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+
+ break;
+ case WM_TIMER:
+ if(wParam == 1) {
+ KillTimer(hwnd,1);
+ HWND hwndDlg = GetParent(hwnd);
+ editinfo_image * image = (editinfo_image *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ SendDlgItemMessage(hwnd,IDC_PROGRESS,PBM_SETPOS,i,0);
+ if(i < editItems->GetSize()) {
+ int metadata_edited=0;
+ songid_t song=(songid_t)editItems->Get(i);
+ time_t t; time(&t);
+ editDevice->setTrackLastUpdated(song,t);
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ARTIST)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_ARTIST,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackArtist(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_TITLE)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_TITLE,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackTitle(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ALBUM)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUM,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackAlbum(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_GENRE)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_GENRE,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackGenre(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_TRACK)) {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_TRACK,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ int n=_wtoi(blah);
+ if (n <= 0) n=-1;
+ editDevice->setTrackTrackNum(song,n);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_DISC)) {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_DISC,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ int n=_wtoi(blah);
+ if (n <= 0) n=-1;
+ editDevice->setTrackDiscNum(song,n);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_YEAR)) {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_YEAR,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ int n=_wtoi(blah);
+ if (n <= 0) n=-1;
+ editDevice->setTrackYear(song,n);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ALBUMARTIST)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUMARTIST,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackAlbumArtist(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_PUBLISHER)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_PUBLISHER,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackPublisher(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_COMPOSER)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_COMPOSER,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackComposer(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ALBUMART)) {
+ if(!image) editDevice->setArt(song,NULL,0,0);
+ else editDevice->setArt(song,image->data,image->w,image->h);
+ }
+
+ if (metadata_edited)
+ {
+ editDevice->extraActions(DEVICE_DONE_SETTING, (intptr_t)song,0,0);
+ }
+ SetTimer(hwnd,1,5,NULL);
+ }
+ else {
+ editDevice->commitChanges();
+ EndDialog(hwnd,0);
+ }
+ i++;
+ }
+ break;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_ABORT) {
+ editDevice->commitChanges();
+ EndDialog(hwnd,-1);
+ }
+ break;
+ }
+ return 0;
+}
+
+static ARGB32 * loadImg(const void * data, int len, int *w, int *h) {
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for(int i=0; i<n; i++) {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if(sf) {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if(l) {
+ if(l->testData(data,len)) {
+ ARGB32* ret = l->loadImage(data,len,w,h);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+static void * loadFile(const wchar_t * file, int &len) {
+ len=0;
+ FILE * f = _wfopen(file,L"rb");
+ if(!f) return 0;
+ fseek(f,0,2);
+ len = ftell(f);
+ if(!len) {fclose(f); return 0;}
+ fseek(f,0,0);
+ void * data = calloc(len, sizeof(void*));
+ fread(data,len,1,f);
+ fclose(f);
+ return data;
+}
+
+static ARGB32 * loadImgFromFile(const wchar_t * file, int *w, int *h) {
+ int len;
+ void * d = loadFile(file,len);
+ if(!d) return 0;
+ ARGB32 * im = loadImg(d,len,w,h);
+ free(d);
+ return im;
+}
+
+static void * writeImg(const ARGB32 *data, int w, int h, int *length, const wchar_t *ext) {
+ if(!ext || !*ext) return NULL;
+ if(*ext == L'.') ext++;
+ FOURCC imgwrite = svc_imageWriter::getServiceType();
+ int n = plugin.service->service_getNumServices(imgwrite);
+ for(int i=0; i<n; i++) {
+ waServiceFactory *sf = plugin.service->service_enumService(imgwrite,i);
+ if(sf) {
+ svc_imageWriter * l = (svc_imageWriter*)sf->getInterface();
+ if(l) {
+ if(wcsstr(l->getExtensions(),ext)) {
+ void* ret = l->convert(data,32,w,h,length);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+static void writeFile(const wchar_t *file, void * data, int length) {
+ FILE *f = _wfopen(file,L"wb");
+ if(!f) return;
+ fwrite(data,length,1,f);
+ fclose(f);
+}
+
+static void writeImageToFile(ARGB32 * img, int w, int h, const wchar_t *file) {
+ int length=0;
+ void * data = writeImg(img,w,h,&length,wcsrchr(file,L'.'));
+ if(data) {
+ writeFile(file,data,length);
+ WASABI_API_MEMMGR->sysFree(data);
+ }
+}
+
+static void enableArt(HWND hwndDlg, bool combo, BOOL enable) {
+ if(combo) EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUMART),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_ARTINFO),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PICTUREHOLDER),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_ART_CHANGE),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_ART_CLEAR),enable);
+}
+
+static int checkEditInfoClick(HWND hwndDlg, POINT p, int item, int check, bool art=false) {
+ if(!IsWindowEnabled(GetDlgItem(hwndDlg,check))) return 0;
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg, item), &r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r.right);
+ if (PtInRect(&r, p) && !IsDlgButtonChecked(hwndDlg, check)) {
+ CheckDlgButton(hwndDlg, check, TRUE);
+ if(art) enableArt(hwndDlg,false,TRUE);
+ else EnableWindow(GetDlgItem(hwndDlg, item), TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);
+ PostMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, item), TRUE);
+ return 1;
+ }
+ return 0;
+}
+
+static HBITMAP getBitmap(const editinfo_image * image, HWND parent) {
+ BITMAPINFO info={0};
+ info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ info.bmiHeader.biWidth = image->w;
+ info.bmiHeader.biHeight = -image->h;
+ info.bmiHeader.biPlanes = 1;
+ info.bmiHeader.biBitCount = 32;
+ info.bmiHeader.biCompression = BI_RGB;
+ HDC dc = GetDC(parent);
+ HBITMAP bm = CreateCompatibleBitmap(dc,image->w,image->h);
+ SetDIBits(dc,bm,0,image->h,image->data,&info,DIB_RGB_COLORS);
+ ReleaseDC(parent,dc);
+ return bm;
+}
+
+static HBITMAP getBitmap(pmpart_t art, Device * dev, HWND parent) {
+ int w,h;
+ editDevice->getArtNaturalSize(art,&w,&h);
+ if(w == 0 || h == 0) return NULL;
+ ARGB32 * bits = (ARGB32 *)calloc(w * h, sizeof(ARGB32));
+ if(!bits) return NULL;
+ editDevice->getArtData(art,bits);
+ editinfo_image im={w,h,bits};
+ HBITMAP bm = getBitmap(&im,parent);
+ free(bits);
+ return bm;
+}
+
+static void setBitmap(const editinfo_image * image, HWND hwndDlg, bool init=false) {
+ if(!init) {
+ CheckDlgButton(hwndDlg,IDC_CHECK_ALBUMART,BST_CHECKED);
+ enableArt(hwndDlg,false,TRUE);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_ART_CLEAR,0),0);
+ }
+ HQSkinBitmap temp(image->data, image->w, image->h); // wrap into a SkinBitmap (no copying involved)
+ BltCanvas newImage(90,90);
+ temp.stretch(&newImage, 0, 0, 90, 90);
+ editinfo_image i = {90,90,(ARGB32*)newImage.getBits()};
+ HBITMAP bm = getBitmap(&i,hwndDlg);
+ HBITMAP bmold = (HBITMAP)SendDlgItemMessage(hwndDlg,IDC_PICTUREHOLDER,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)bm);
+ if(bmold) DeleteObject(bmold);
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)image);
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"%dx%d",image->w,image->h);
+ SetDlgItemText(hwndDlg,IDC_ARTINFO,buf);
+}
+
+static void GetSize(HBITMAP bm,int &w, int &h,HWND hwndDlg) {
+ HDC dc = GetDC(hwndDlg);
+ BITMAPINFO info={0};
+ info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ GetDIBits(dc,bm,0,0,NULL,&info,DIB_RGB_COLORS);
+ w = abs(info.bmiHeader.biWidth);
+ h = abs(info.bmiHeader.biHeight);
+ ReleaseDC(hwndDlg,dc);
+}
+
+static void setBitmap(HBITMAP bm, HWND hwndDlg) {
+ editinfo_image* image = (editinfo_image*)calloc(1, sizeof(editinfo_image));
+ GetSize(bm,image->w,image->h,hwndDlg);
+ image->data = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*image->w*image->h);
+ BITMAPINFO info={0};
+ info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ info.bmiHeader.biWidth = image->w;
+ info.bmiHeader.biHeight = -image->h;
+ info.bmiHeader.biPlanes = 1;
+ info.bmiHeader.biBitCount = 32;
+ info.bmiHeader.biCompression = BI_RGB;
+ HDC dc = GetDC(hwndDlg);
+ GetDIBits(dc,bm,0,image->h,image->data,&info,DIB_RGB_COLORS);
+ ReleaseDC(hwndDlg,dc);
+ setBitmap(image,hwndDlg);
+}
+
+static INT_PTR CALLBACK editInfo_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ wchar_t last_artist[2048]=L"", last_title[2048]=L"", last_album[2048]=L"", last_genre[2048]=L"", last_albumartist[2048]=L"", last_publisher[2048]=L"", last_composer[2048]=L"";
+ pmpart_t last_albumart=NULL;
+ int last_year=-1, last_track=-1, last_disc=-1;
+ bool disable_artist=0, disable_title=0, disable_album=0, disable_genre=0, disable_year=0, disable_track=0, disable_disc=0, disable_albumartist=0, disable_publisher=0, disable_composer=0, disable_albumart=0;
+ int fieldBits = (int)editDevice->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldBits) fieldBits = -1;
+ disable_artist = (fieldBits & SUPPORTS_ARTIST)==0;
+ disable_title = (fieldBits & SUPPORTS_TITLE)==0;
+ disable_album = (fieldBits & SUPPORTS_ALBUM)==0;
+ disable_genre = (fieldBits & SUPPORTS_GENRE)==0;
+ disable_year = (fieldBits & SUPPORTS_YEAR)==0;
+ disable_track = (fieldBits & SUPPORTS_TRACKNUM)==0;
+ disable_disc = (fieldBits & SUPPORTS_DISCNUM)==0;
+ disable_albumartist = (fieldBits & SUPPORTS_ALBUMARTIST)==0;
+ disable_publisher = (fieldBits & SUPPORTS_PUBLISHER)==0;
+ disable_composer = (fieldBits & SUPPORTS_COMPOSER)==0;
+ disable_albumart = (fieldBits & SUPPORTS_ALBUMART)==0;
+
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
+
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ARTIST),!disable_artist);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_TITLE),!disable_title);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUM),!disable_album);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_GENRE),!disable_genre);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_YEAR),!disable_year);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_TRACK),!disable_track);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_DISC),!disable_disc);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUMARTIST),!disable_albumartist);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_PUBLISHER),!disable_publisher);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_COMPOSER),!disable_composer);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUMART),!disable_albumart);
+ //enableArt(hwndDlg,true,!disable_albumart);
+
+ int l=editItems->GetSize();
+ for(int i=0;i<l;i++) {
+ wchar_t buf[2048]=L"";
+ songid_t song=(songid_t)editItems->Get(i);
+ if(!disable_artist)
+ {
+ buf[0]=0;
+ editDevice->getTrackArtist(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_artist[0]) lstrcpyn(last_artist,buf,2048);
+ else if(lstrcmp(buf,last_artist)) disable_artist=true;
+ }
+ if(!disable_albumartist)
+ {
+ buf[0]=0;
+ editDevice->getTrackAlbumArtist(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_albumartist[0]) lstrcpyn(last_albumartist,buf,2048);
+ else if(lstrcmp(buf,last_albumartist)) disable_albumartist=true;
+ }
+ if(!disable_publisher)
+ {
+ buf[0]=0;
+ editDevice->getTrackPublisher(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_publisher[0]) lstrcpyn(last_publisher,buf,2048);
+ else if(lstrcmp(buf,last_publisher)) disable_publisher=true;
+ }
+ if(!disable_composer)
+ {
+ buf[0]=0;
+ editDevice->getTrackComposer(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_composer[0]) lstrcpyn(last_composer,buf,2048);
+ else if(lstrcmp(buf,last_composer)) disable_composer=true;
+ }
+ if(!disable_title)
+ {
+ buf[0]=0;
+ editDevice->getTrackTitle(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_title[0])
+ lstrcpyn(last_title,buf,2048);
+ else if(lstrcmp(buf,last_title)) disable_title=true;
+ }
+ if(!disable_album)
+ {
+ buf[0]=0;
+ editDevice->getTrackAlbum(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_album[0]) lstrcpyn(last_album,buf,2048);
+ else if(lstrcmp(buf,last_album)) disable_album=true;
+ }
+ if(!disable_genre)
+ {
+ buf[0]=0;
+ editDevice->getTrackGenre(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_genre[0]) lstrcpyn(last_genre,buf,2048);
+ else if(lstrcmp(buf,last_genre)) disable_genre=true;
+ }
+ if(!disable_year)
+ {
+ int val=editDevice->getTrackYear(song);
+ if(val <= 0);
+ else if(last_year==-1) last_year=val;
+ else if(last_year!=val) disable_year=true;
+ }
+ if(!disable_track)
+ {
+ int val=editDevice->getTrackTrackNum(song);
+ if(val <= 0);
+ else if(last_track==-1) last_track=val;
+ else if(last_track!=val) disable_track=true;
+ }
+ if(!disable_disc)
+ {
+ int val=editDevice->getTrackDiscNum(song);
+ if(val <= 0);
+ else if(last_disc==-1) last_disc=val;
+ else if(last_disc!=val) disable_disc=true;
+ }
+ if(!disable_albumart)
+ {
+ pmpart_t a = editDevice->getArt(song);
+ if(!a);
+ else if(!last_albumart) { editDevice->releaseArt(last_albumart); last_albumart = a; }
+ else if(!editDevice->artIsEqual(a,last_albumart)) { disable_albumart=true; editDevice->releaseArt(a); editDevice->releaseArt(last_albumart); last_albumart=0; }
+ }
+ }
+ if(!disable_artist && last_artist) SetDlgItemText(hwndDlg,IDC_EDIT_ARTIST,last_artist);
+ if(!disable_albumartist && last_albumartist) SetDlgItemText(hwndDlg,IDC_EDIT_ALBUMARTIST,last_albumartist);
+ if(!disable_publisher && last_publisher) SetDlgItemText(hwndDlg,IDC_EDIT_PUBLISHER,last_publisher);
+ if(!disable_composer && last_composer) SetDlgItemText(hwndDlg,IDC_EDIT_COMPOSER,last_composer);
+ if(!disable_title && last_title) SetDlgItemText(hwndDlg,IDC_EDIT_TITLE,last_title);
+ if(!disable_album && last_album) SetDlgItemText(hwndDlg,IDC_EDIT_ALBUM,last_album);
+ if(!disable_genre && last_genre) SetDlgItemText(hwndDlg,IDC_EDIT_GENRE,last_genre);
+ if(!disable_year && last_year>0) {
+ wchar_t tmp[64] = {0};
+ wsprintf(tmp,L"%d",last_year);
+ SetDlgItemText(hwndDlg,IDC_EDIT_YEAR,tmp);
+ }
+ if(!disable_track && last_track>0) {
+ wchar_t tmp[64] = {0};
+ wsprintf(tmp,L"%d",last_track);
+ SetDlgItemText(hwndDlg,IDC_EDIT_TRACK,tmp);
+ }
+ if(!disable_disc && last_disc>0) {
+ wchar_t tmp[64] = {0};
+ wsprintf(tmp,L"%d",last_disc);
+ SetDlgItemText(hwndDlg,IDC_EDIT_DISC,tmp);
+ }
+ if(!disable_albumart && last_albumart) {
+ // save copy of image
+ editinfo_image* image = (editinfo_image*)calloc(1, sizeof(editinfo_image));
+ editDevice->getArtNaturalSize(last_albumart,&image->w,&image->h);
+ image->data = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*image->w*image->h);
+ editDevice->getArtData(last_albumart,image->data);
+ setBitmap(image,hwndDlg,true);
+ editDevice->releaseArt(last_albumart);
+ }
+ else SetWindowLongPtr(hwndDlg,GWLP_USERDATA,0);
+
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+
+ }
+ break;
+ case WM_LBUTTONDOWN:
+ {
+ POINTS p = MAKEPOINTS(lParam);
+ POINT p2 = {p.x, p.y};
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ARTIST, IDC_CHECK_ARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TITLE, IDC_CHECK_TITLE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUM, IDC_CHECK_ALBUM)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TRACK, IDC_CHECK_TRACK)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_DISC, IDC_CHECK_DISC)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_GENRE, IDC_CHECK_GENRE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_YEAR, IDC_CHECK_YEAR)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_PICTUREHOLDER, IDC_CHECK_ALBUMART,true)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_ART_CLEAR, IDC_CHECK_ALBUMART,true)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_ART_CHANGE, IDC_CHECK_ALBUMART,true)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_ARTINFO, IDC_CHECK_ALBUMART,true)) break;
+ }
+ break;
+ case WM_RBUTTONDOWN:
+ {
+ POINTS pts = MAKEPOINTS(lParam);
+ POINT p = {pts.x, pts.y};
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_PICTUREHOLDER), &r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r.right);
+ if(PtInRect(&r, p)) { // right click on picture holder
+ extern HMENU m_context_menus;
+ HMENU menu = GetSubMenu(m_context_menus, 11);
+ POINT p;
+ GetCursorPos(&p);
+ wchar_t artist[256]=L"";
+ wchar_t album[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUMARTIST,artist,256);
+ if(!artist[0])
+ GetDlgItemText(hwndDlg,IDC_EDIT_ARTIST,artist,256);
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUM,album,256);
+
+ bool canpaste=(!!IsClipboardFormatAvailable(CF_DIB));
+ bool hasimage= (GetWindowLongPtr(hwndDlg,GWLP_USERDATA) != 0);
+ EnableMenuItem(menu, ID_ARTEDITMENU_PASTE, MF_BYCOMMAND | (canpaste?MF_ENABLED:MF_GRAYED));
+ EnableMenuItem(menu, ID_ARTEDITMENU_COPY, MF_BYCOMMAND | (hasimage?MF_ENABLED:MF_GRAYED));
+ EnableMenuItem(menu, ID_ARTEDITMENU_DELETE, MF_BYCOMMAND | (hasimage?MF_ENABLED:MF_GRAYED));
+ EnableMenuItem(menu, ID_ARTEDITMENU_SAVEAS, MF_BYCOMMAND | (hasimage?MF_ENABLED:MF_GRAYED));
+
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ #if 0
+ bool candownload = (album[0] || artist[0]);
+ EnableMenuItem(menu, ID_ARTEDITMENU_DOWNLOAD, MF_BYCOMMAND | (candownload?MF_ENABLED:MF_GRAYED));
+ #else
+ DeleteMenu(menu, ID_ARTEDITMENU_DOWNLOAD, MF_BYCOMMAND);
+ #endif
+
+ int r = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, 0, hwndDlg, NULL);
+ switch(r) {
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ #if 0
+ case ID_ARTEDITMENU_DOWNLOAD:
+ {
+ artFetchData d = {sizeof(d),hwndDlg,artist,album,0};
+ int r = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(LPARAM)&d,IPC_FETCH_ALBUMART);
+ if(r == 0 && d.imgData && d.imgDataLen) // success, save art in correct location
+ {
+ int w=0,h=0;
+ ARGB32* bytes = loadImg(d.imgData,d.imgDataLen,&w,&h);
+ if(bytes)
+ {
+ editinfo_image *image = (editinfo_image *)calloc(1, sizeof(editinfo_image));
+ image->data = bytes;
+ image->w = w;
+ image->h = h;
+ setBitmap(image,hwndDlg);
+ }
+ WASABI_API_MEMMGR->sysFree(d.imgData);
+ }
+ }
+ break;
+ #endif
+ case ID_ARTEDITMENU_COPY:
+ {
+ if (!OpenClipboard(hwndDlg)) break;
+ EmptyClipboard();
+ HBITMAP bm = getBitmap((editinfo_image*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA),hwndDlg);
+ SetClipboardData(CF_BITMAP,bm);
+ CloseClipboard();
+ }
+ break;
+ case ID_ARTEDITMENU_PASTE:
+ {
+ if (!OpenClipboard(hwndDlg)) break;
+ HBITMAP bm = (HBITMAP)GetClipboardData(CF_BITMAP);
+ if(bm) setBitmap(bm,hwndDlg);
+ CloseClipboard();
+ }
+ break;
+ case ID_ARTEDITMENU_DELETE:
+ CheckDlgButton(hwndDlg,IDC_CHECK_ALBUMART,BST_CHECKED);
+ enableArt(hwndDlg,false,TRUE);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_ART_CLEAR,0),0);
+ break;
+ case ID_ARTEDITMENU_SAVEAS:
+ {
+
+ static int tests_run = 0;
+ wchar_t file[1024] = {0};
+ static wchar_t filter[1024] = {0}, *sff = filter;
+ OPENFILENAME fn = {0};
+ fn.lStructSize = sizeof(fn);
+ fn.hwndOwner = hwndDlg;
+ fn.lpstrFile = file;
+ fn.nMaxFile = 1020;
+
+ if(!tests_run)
+ {
+ tests_run = 1;
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ size_t size = 1024;
+ for (int i = 0, j = 0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if (sf)
+ {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if (l)
+ {
+ wchar_t *tests[] = {L"*.jpg",L"*.png",L"*.gif",L"*.bmp"};
+ for(int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ {
+ if (l->isMine(tests[i]))
+ {
+ j++;
+ int len = 0, tests_str[] = {IDS_JPEG_FILE,IDS_PNG_FILE,IDS_GIF_FILE,IDS_BMP_FILE};
+ WASABI_API_LNGSTRINGW_BUF(tests_str[i],sff,size);
+ size-=(len = lstrlenW(sff)+1);
+ sff+=len;
+ lstrcpynW(sff,tests[i],size);
+ size-=(len = lstrlenW(sff)+1);
+ sff+=len;
+ }
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ }
+
+ fn.lpstrFilter = filter;
+ fn.Flags = OFN_OVERWRITEPROMPT;
+ GetSaveFileName(&fn);
+ int l = wcslen(file);
+ if(l>4 && file[l-4]==L'.'); // we have an extention
+ else switch(fn.nFilterIndex) {
+ case 1: StringCchCat(file, ARRAYSIZE(file), L".jpg"); break;
+ case 2: StringCchCat(file, ARRAYSIZE(file), L".png"); break;
+ case 3: StringCchCat(file, ARRAYSIZE(file), L".gif"); break;
+ case 4: StringCchCat(file, ARRAYSIZE(file), L".bmp"); break;
+ }
+ editinfo_image *image = (editinfo_image *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ writeImageToFile(image->data,image->w,image->h,file);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+#define HANDLE_CONTROL(item, check) { int enabled = IsDlgButtonChecked(hwndDlg, check); EnableWindow(GetDlgItem(hwndDlg, item), enabled); EnableWindow(GetDlgItem(hwndDlg, IDOK), enabled); }
+ switch(LOWORD(wParam)) {
+
+ case IDC_CHECK_ARTIST: HANDLE_CONTROL(IDC_EDIT_ARTIST, IDC_CHECK_ARTIST); break;
+ case IDC_CHECK_TITLE: HANDLE_CONTROL(IDC_EDIT_TITLE, IDC_CHECK_TITLE); break;
+ case IDC_CHECK_ALBUM: HANDLE_CONTROL(IDC_EDIT_ALBUM, IDC_CHECK_ALBUM); break;
+ case IDC_CHECK_ALBUMARTIST: HANDLE_CONTROL(IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST); break;
+ case IDC_CHECK_COMPOSER: HANDLE_CONTROL(IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER); break;
+ case IDC_CHECK_PUBLISHER: HANDLE_CONTROL(IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER); break;
+ case IDC_CHECK_TRACK: HANDLE_CONTROL(IDC_EDIT_TRACK, IDC_CHECK_TRACK); break;
+ case IDC_CHECK_DISC: HANDLE_CONTROL(IDC_EDIT_DISC, IDC_CHECK_DISC); break;
+ case IDC_CHECK_GENRE: HANDLE_CONTROL(IDC_EDIT_GENRE, IDC_CHECK_GENRE); break;
+ case IDC_CHECK_YEAR: HANDLE_CONTROL(IDC_EDIT_YEAR, IDC_CHECK_YEAR); break;
+ //case IDC_CHECK_COMMENT: HANDLE_CONTROL(IDC_EDIT_COMMENT, IDC_CHECK_COMMENT); break;
+ //case IDC_CHECK_CATEGORY: HANDLE_CONTROL(IDC_EDIT_CATEGORY, IDC_CHECK_CATEGORY); break;
+ //case IDC_CHECK_DIRECTOR: HANDLE_CONTROL(IDC_EDIT_DIRECTOR, IDC_CHECK_DIRECTOR); break;
+ //case IDC_CHECK_PRODUCER: HANDLE_CONTROL(IDC_EDIT_PRODUCER, IDC_CHECK_PRODUCER); break;
+ //case IDC_CHECK_BPM: HANDLE_CONTROL(IDC_EDIT_BPM, IDC_CHECK_BPM); break;
+ //case IDC_CHECK_RATING: HANDLE_CONTROL(IDC_COMBO_RATING, IDC_CHECK_RATING); break;
+ case IDC_CHECK_ALBUMART:
+ {
+ int enabled = IsDlgButtonChecked(hwndDlg, IDC_CHECK_ALBUMART);
+ enableArt(hwndDlg, false, enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), enabled);
+ break;
+ }
+ case IDC_ART_CLEAR:
+ {
+ editinfo_image* image = (editinfo_image*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ if(image) {
+ if(image->data) WASABI_API_MEMMGR->sysFree(image->data);
+ free(image);
+ }
+ SetDlgItemText(hwndDlg,IDC_ARTINFO,WASABI_API_LNGSTRINGW(IDS_NO_IMAGE));
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,0);
+ HBITMAP old = (HBITMAP)SendDlgItemMessage(hwndDlg,IDC_PICTUREHOLDER,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)0);
+ DeleteObject(old);
+ }
+ break;
+ case IDC_ART_CHANGE:
+ {
+ static wchar_t fileExtensionsString[MAX_PATH] = {0};
+ wchar_t file[1024]=L"";
+ OPENFILENAME fn = {0};
+ fn.lStructSize = sizeof(fn);
+ fn.hwndOwner = hwndDlg;
+ fn.lpstrFile = file;
+ fn.nMaxFile = 1024;
+
+ if(!fileExtensionsString[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_IMAGE_FILES,fileExtensionsString,MAX_PATH);
+ wchar_t *temp=fileExtensionsString+lstrlenW(fileExtensionsString) + 1;
+
+ // query the available image loaders and build it against the supported formats
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for (int i=0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if (sf)
+ {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if (l)
+ {
+ wchar_t *tests[] = {L"*.jpg",L"*.png",L"*.gif",L"*.bmp"};
+ for(int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ {
+ if (l->isMine(tests[i]))
+ {
+ StringCchCatW(temp,MAX_PATH,tests[i]);
+ StringCchCatW(temp,MAX_PATH,L";");
+ }
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ *(temp = temp + lstrlenW(temp) + 1) = 0;
+ }
+
+ fn.lpstrFilter = fileExtensionsString;
+ fn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
+ GetOpenFileName(&fn);
+ editinfo_image *image = (editinfo_image *)calloc(1, sizeof(editinfo_image));
+ image->data = loadImgFromFile(file,&image->w,&image->h);
+ if(!image->data) { free(image); break; }
+ setBitmap(image,hwndDlg);
+ }
+ break;
+ case IDOK:
+ {
+ if(WASABI_API_DIALOGBOXPARAMW(IDD_PROGRESS,hwndDlg,editInfo_commit_dialogProc, (LPARAM)hwndDlg) == 0)
+ EndDialog(hwndDlg,0);
+ DeviceView * editDeviceView=NULL;
+ for(int i=0; i < devices.GetSize(); i++) if(((DeviceView*)devices.Get(i))->dev == editDevice) editDeviceView = (DeviceView*)devices.Get(i);
+ if(editDeviceView) editDeviceView->DevicePropertiesChanges();
+ SendMessage(hwndMediaView,WM_USER+2,1,0);
+ }
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ editinfo_image* image = (editinfo_image*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ if(image) {
+ if(image->data) WASABI_API_MEMMGR->sysFree(image->data);
+ free(image);
+ }
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,0);
+ HBITMAP bmold = (HBITMAP)SendDlgItemMessage(hwndDlg,IDC_PICTUREHOLDER,STM_GETIMAGE,IMAGE_BITMAP,0);
+ if(bmold) DeleteObject(bmold);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void editInfo(C_ItemList * items, Device * dev, HWND centerWindow) {
+ editItems = items;
+ editDevice = dev;
+ WASABI_API_DIALOGBOXPARAMW(IDD_EDIT_INFO, plugin.hwndLibraryParent, editInfo_dialogProc, (LPARAM)centerWindow);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/graphics.cpp b/Src/Plugins/Library/ml_pmp/graphics.cpp
new file mode 100644
index 00000000..8fbfaecb
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/graphics.cpp
@@ -0,0 +1,316 @@
+#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;
+ HFONT font;
+
+ 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;
+
+ if (NULL == sourceFont)
+ return NULL;
+
+
+ if (sizeof(lf) != GetObjectW(sourceFont, sizeof(lf), &lf))
+ return NULL;
+
+ if (0 != heightDeltaPt)
+ {
+ HDC hdc, hdcTmp;
+
+ hdc = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE | DCX_NORESETATTRS);
+ hdcTmp = NULL;
+
+ 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;
+ if (FALSE != GetTextMetricsW(hdcTmp, &tm))
+ {
+ INT basePt = MulDiv(tm.tmHeight - tm.tmInternalLeading, 72, pixelsY);
+ lf.lfHeight = -MulDiv((basePt + heightDeltaPt), pixelsY, 72);
+
+ }
+
+ 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.hwndLibraryParent, 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_pmp/graphics.h b/Src/Plugins/Library/ml_pmp/graphics.h
new file mode 100644
index 00000000..097dd83d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/graphics.h
@@ -0,0 +1,71 @@
+#ifndef _NULLSOFT_WINAMP_ML_PORTABLES_GRAPHICS_HEADER
+#define _NULLSOFT_WINAMP_ML_PORTABLES_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_PORTABLES_GRAPHICS_HEADER
diff --git a/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp b/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp
new file mode 100644
index 00000000..13536436
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp
@@ -0,0 +1,313 @@
+#include "main.h"
+
+#include "api/service/waservicefactory.h"
+#include "bfc/dispatch.h"
+
+#include "../Components/wac_network/wac_network_web_server_api.h"
+#include "../Components/wac_network/wac_network_onconncb_api.h"
+#include "../Components/wac_network/wac_network_connection_api.h"
+#include "../Components/wac_network/wac_network_http_server_api.h"
+#include "../Components/wac_network/wac_network_page_generator_api.h"
+
+#include "api__ml_pmp.h"
+
+#include "DeviceView.h"
+
+#include <wchar.h>
+
+#include "metadata_utils.h"
+
+#include "nu/AutoWide.h"
+#include "nu/AutoChar.h"
+
+#include <strsafe.h>
+
+void url_decode(char *in, char *out, int maxlen)
+{
+ while (in && *in && maxlen>1)
+ {
+ if (*in == '+')
+ {
+ in++;
+ *out++=' ';
+ }
+ else if (*in == '%' && in[1] != '%' && in[1])
+ {
+ int a=0;
+ int b=0;
+ for ( b = 0; b < 2; b ++)
+ {
+ int r=in[1+b];
+ if (r>='0'&&r<='9') r-='0';
+ else if (r>='a'&&r<='z') r-='a'-10;
+ else if (r>='A'&&r<='Z') r-='A'-10;
+ else break;
+ a*=16;
+ a+=r;
+ }
+ if (b < 2) *out++=*in++;
+ else { *out++=a; in += 3;}
+ }
+ else *out++=*in++;
+ maxlen--;
+ }
+ *out=0;
+}
+
+static void callbackThunk(void * callBackContext, wchar_t * status) {
+}
+
+typedef struct {
+ int status;
+ wchar_t * device; wchar_t * artist; wchar_t * album; wchar_t * title;
+ DeviceView * d; songid_t s;
+} FindTrackStruct;
+
+static VOID CALLBACK APC_FindTrack(ULONG_PTR dwParam) {
+ FindTrackStruct * f = (FindTrackStruct *)dwParam;
+ for(int i=0; i<devices.GetSize(); i++) {
+ f->d = (DeviceView*)devices.Get(i);
+ wchar_t buf[2048] = {0}; int len = 2047;
+ f->d->dev->getPlaylistName(0,buf,128);
+ if(wcscmp(buf,f->device)) continue;
+ int l = f->d->dev->getPlaylistLength(0);
+ for(int j=0; j<l; j++) {
+ f->s = f->d->dev->getPlaylistTrack(0,j);
+ f->d->dev->getTrackArtist(f->s,buf,len);
+ if(lstrcmpi(buf,f->artist)) continue;
+ f->d->dev->getTrackAlbum(f->s,buf,len);
+ if(lstrcmpi(buf,f->album)) continue;
+ f->d->dev->getTrackTitle(f->s,buf,len);
+ if(lstrcmpi(buf,f->title)) continue;
+ f->status = 2;
+ return;
+ }
+ }
+ f->status = 1;
+}
+
+bool findTrack(Device **dev, songid_t *song,wchar_t * device,wchar_t * artist,wchar_t * album,wchar_t * title) {
+ FindTrackStruct f = {0,device,artist,album,title,NULL,0};
+ SynchronousProcedureCall(APC_FindTrack,(ULONG_PTR)&f);
+ *dev = f.d->dev;
+ *song = f.s;
+ return f.status == 2;
+}
+
+bool copyFileFromDevice(Device * dev, songid_t song,wchar_t * fn) {
+ int k=0;
+ if(dev->copyToHardDriveSupported()) {
+ if(!dev->copyToHardDrive(song,fn,NULL,callbackThunk,&k))
+ return true;
+ }
+ return false;
+}
+
+class FilePageGenerator : public api_pagegenerator // public IPageGenerator
+{
+public:
+ virtual ~FilePageGenerator();
+ FilePageGenerator(wchar_t *fn, api_httpserv *serv);
+ size_t GetData(char *buf, int size); // return 0 when done
+ int is_error() { return !m_fp; }
+
+private:
+ FILE *m_fp;
+ int m_file_pos;
+ int m_file_len;
+ wchar_t *m_fn;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS FilePageGenerator
+START_DISPATCH;
+CB(API_PAGEGENERATOR_GETDATA, GetData);
+END_DISPATCH;
+#undef CBCLASS
+
+FilePageGenerator::FilePageGenerator(wchar_t *fn, api_httpserv *serv)
+{
+ m_fn = _wcsdup(fn);
+ m_file_pos=m_file_len=0;
+ m_fp=_wfopen(fn,L"rb");
+ if (m_fp)
+ {
+ int resume_end=0;
+ int resume_pos=0;
+ char *range = serv->getheader("Range");
+ if (range)
+ {
+ if (!_strnicmp(range,"bytes=",6))
+ {
+ range+=6;
+ char *t=range;
+ while (t && *t && (*t < '0' || *t > '9')) t++;
+ while (t && *t && *t >= '0' && *t <= '9')
+ {
+ resume_pos*=10;
+ resume_pos+=*t-'0';
+ t++;
+ }
+ if (*t != '-') resume_pos=0;
+ else if (t[1])
+ {
+ resume_end=atoi(t+1);
+ }
+ }
+ }
+
+ fseek(m_fp,0,SEEK_END);
+ m_file_len=ftell(m_fp);
+ char buf[512] = {0};
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Length: %d",m_file_len);
+ serv->set_reply_header(buf);
+
+ int m_file_len_orig=m_file_len;
+
+ if (resume_end && resume_end < m_file_len) m_file_len=resume_end;
+ if (resume_pos > 0 && resume_pos < m_file_len) m_file_pos = resume_pos;
+ fseek(m_fp,m_file_pos,SEEK_SET);
+
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Range: bytes=%d-%d/%d",resume_pos,resume_end,m_file_len_orig);
+ serv->set_reply_header(buf);
+ if (resume_pos != 0) serv->set_reply_string("HTTP/1.1 206 Partial Content");
+ }
+}
+
+FilePageGenerator::~FilePageGenerator()
+{
+ if (m_fp) fclose(m_fp);
+ if (m_fn) { _wunlink(m_fn); free(m_fn); }
+}
+
+size_t FilePageGenerator::GetData(char *buf, int size) // return 0 when done
+{
+ if (!m_fp) return 0;
+ if (m_file_pos+size > m_file_len) size=m_file_len - m_file_pos;
+ int l=fread(buf,1,size,m_fp);
+ m_file_pos+=l;
+ return l;
+}
+
+class onConnCB : public api_onconncb {
+public:
+ api_pagegenerator* onConnection(api_httpserv *serv, int port)
+ {
+ api_connection *c = serv->get_con();
+ if(c && c->GetRemoteAddress() != 0x0100007f) return 0; // if it's not from localhost, disregard.
+ serv->set_reply_header("Server:ml_pmp/1.0");
+ if (!strcmp(serv->get_request_file(),"/"))
+ {
+ // find temporary filename
+ wchar_t fn[MAX_PATH]={0};
+ wchar_t dir[MAX_PATH]={0};
+ GetTempPath(MAX_PATH,dir);
+ GetTempFileName(dir,L"pmp",0,fn);
+ _wunlink(fn);
+ {wchar_t * ext = wcsrchr(fn,L'.'); if(ext) *ext=0;}
+ // decode the parameters needed to find the track from the URL
+ char device[128]={0};
+ char artist[2048]={0};
+ char album[2048]={0};
+ char title[2048]={0};
+ char * p;
+ p = serv->get_request_parm("d");
+ if(p) url_decode(p,device,128);
+ p = serv->get_request_parm("a");
+ if(p) url_decode(p,artist,2048);
+ p = serv->get_request_parm("l");
+ if(p) url_decode(p,album,2048);
+ p = serv->get_request_parm("t");
+ if(p) url_decode(p,title,2048);
+ char * sc = strrchr(device,';');
+ char * ext = "mp3";
+ if(sc) { *sc=0; ext = sc+2; }
+ // find the track based on this info
+ Device * dev;
+ songid_t song;
+ if(findTrack(&dev,&song,AutoWide(device,CP_UTF8),AutoWide(artist,CP_UTF8),AutoWide(album,CP_UTF8),AutoWide(title,CP_UTF8))) {
+ if(dev->copyToHardDriveSupported()) {
+ // track found, can be copied back. Lets reply to the user
+ serv->set_reply_string("HTTP/1.1 200 OK");
+ char buf[150]="";
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Type:audio/%s",ext);
+ serv->set_reply_header(buf);
+ wchar_t title[128]={0};
+ getTitle(dev,song,fn,title,128);
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "icy-name:%s",(char*)AutoChar(title,CP_UTF8));
+ serv->set_reply_header(buf);
+ serv->send_reply();
+ // do the actual copy, and start streaming
+ if(copyFileFromDevice(dev,song,fn)) return new FilePageGenerator(fn,serv);
+ }
+ }
+ }
+ serv->set_reply_string("HTTP/1.1 404 NOT FOUND");
+ serv->send_reply();
+ return 0; // no data
+ }
+ void destroyConnection(api_pagegenerator *conn)
+ {
+ FilePageGenerator *realObject = static_cast<FilePageGenerator *>(conn);
+ delete realObject;
+ }
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS onConnCB
+START_DISPATCH;
+CB(API_ONCONNCB_ONCONNECTION,onConnection);
+VCB(API_ONCONNCB_DESTROYCONNECTION,destroyConnection);
+END_DISPATCH;
+#undef CBCLASS
+
+int serverPort = -1;
+
+static DWORD WINAPI ThreadFunc_Server(LPVOID lpParam) {
+ extern bool quitting;
+ if(quitting) return 0;
+ onConnCB *pOnConnCB;
+ int * killswitch = (int*)lpParam;
+ api_webserv *server=0;
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(webservGUID);
+ if (sf) server = (api_webserv *)sf->getInterface();
+ if(!server) return NULL;
+ pOnConnCB = new onConnCB;
+ server->SetConnectionCallback(pOnConnCB);
+ serverPort = 54387;
+ while(server->addListenPort(serverPort,0x0100007f) && serverPort < 54397) serverPort++;
+ if(serverPort >= 54397) { serverPort=-1; return NULL; }
+ while (killswitch && *killswitch == 0)
+ {
+ server->run();
+ server->run();
+ Sleep(20);
+ }
+ server->removeListenPort(serverPort);
+ sf->releaseInterface(server);
+ serverPort=-1;
+ delete pOnConnCB;
+ return NULL;
+}
+
+static int killswitch = 0;
+static HANDLE serverThread = NULL;
+
+void startServer() {
+ if(serverPort == -1) {
+ killswitch = 0;
+ DWORD dwThreadId;
+ serverThread = CreateThread(NULL, 0, ThreadFunc_Server, (LPVOID)&killswitch, 0, &dwThreadId);
+ }
+}
+
+void stopServer() {
+ if(serverThread) {
+ killswitch = 1;
+ WaitForSingleObject(serverThread,INFINITE);
+ CloseHandle(serverThread);
+ serverThread = NULL;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/local_menu.cpp b/Src/Plugins/Library/ml_pmp/local_menu.cpp
new file mode 100644
index 00000000..b299195c
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/local_menu.cpp
@@ -0,0 +1,189 @@
+#include "./local_menu.h"
+#include "../../General/gen_ml/ml.h"
+#include "../../General/gen_ml/menu.h"
+#include "./resource1.h"
+#include "../nu/menushortcuts.h"
+#include "api__ml_pmp.h"
+#include "main.h"
+
+extern winampMediaLibraryPlugin plugin;
+extern HWND hwndMediaView;
+
+#define RATING_MARKER MAKELONG(MAKEWORD('R','A'),MAKEWORD('T','E'))
+
+#define RATING_MINSPACECX 16
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x)/sizeof((x)))
+#endif
+
+static HMENU Menu_FindRatingByChild(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii, UINT childId)
+{
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii))
+ {
+ if (childId == pmii->wID) return hMenu;
+ if (NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingByChild(pmii->hSubMenu, pmi, pmii, childId);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ }
+ return NULL;
+}
+
+static HMENU Menu_FindRatingByMarker(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii)
+{
+ if (GetMenuInfo(hMenu, pmi) && RATING_MARKER == pmi->dwMenuData)
+ return hMenu;
+
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii) && NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingByMarker(pmii->hSubMenu, pmi, pmii);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ return NULL;
+}
+
+HMENU Menu_FindRatingMenu(HMENU hMenu, BOOL fUseMarker)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_SUBMENU;
+
+ if (FALSE == fUseMarker)
+ mii.fMask |= MIIM_ID;
+
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+
+ return (FALSE == fUseMarker) ?
+ Menu_FindRatingByChild(hMenu, &mi, &mii, ID_RATE_5) :
+ Menu_FindRatingByMarker(hMenu, &mi, &mii);
+}
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue)
+{
+ if (NULL == ratingMenu) return FALSE;
+
+ INT ratingList[] = { ID_RATE_0, ID_RATE_1, ID_RATE_2,
+ ID_RATE_3, ID_RATE_4, ID_RATE_5};
+
+ /// set rating marker
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+ mi.dwMenuData = RATING_MARKER;
+ if (!SetMenuInfo(ratingMenu, &mi))
+ return FALSE;
+
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+
+ UINT type, state;
+ for (INT i = 0; i < ARRAYSIZE(ratingList); i++)
+ {
+ mii.fMask = MIIM_STATE | MIIM_FTYPE;
+ if (GetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii))
+ {
+ if (ratingValue == i)
+ {
+ type = mii.fType | MFT_RADIOCHECK;
+ state = mii.fState | MFS_CHECKED;
+ }
+ else
+ {
+ type = mii.fType & ~MFT_RADIOCHECK;
+ state = mii.fState & ~MFS_CHECKED;
+ }
+
+ mii.fMask = 0;
+ if (type != mii.fType)
+ {
+ mii.fType = type;
+ mii.fMask |= MIIM_FTYPE;
+ }
+
+ if (state != mii.fState)
+ {
+ mii.fState = state;
+ mii.fMask |= MIIM_STATE;
+ }
+
+ if (0 != mii.fMask)
+ SetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii);
+ }
+ }
+ return TRUE;
+}
+
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu)
+{
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(hwndDlg, szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendMenuShortcuts(menu, szAccel, c, MSF_REPLACE);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_TRACKSLIST_PLAYSELECTION)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ }
+ else if (id == ID_TRACKSLIST_ENQUEUESELECTION)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos= i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ }
+ }
+
+ playItem.wID = ID_TRACKSLIST_ENQUEUESELECTION;
+ enqueueItem.wID = ID_TRACKSLIST_PLAYSELECTION;
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+INT Menu_TrackSkinnedPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ bool swapPlayEnqueue=false;
+ if (gen_mlconfig->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(hMenu);
+ swapPlayEnqueue = true;
+ }
+
+ SyncMenuWithAccelerators(hwndMediaView, hMenu);
+
+ if (swapPlayEnqueue)
+ SwapPlayEnqueueInMenu(hMenu);
+
+ return Menu_TrackPopupParam(plugin.hwndLibraryParent, hMenu, fuFlags, x, y,
+ hwnd, lptpm, (ULONG_PTR)Menu_FindRatingMenu(hMenu, TRUE));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/local_menu.h b/Src/Plugins/Library/ml_pmp/local_menu.h
new file mode 100644
index 00000000..d7b7dad0
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/local_menu.h
@@ -0,0 +1,15 @@
+#ifndef NULLOSFT_PORTABLES_PLUGIN_MENU_HEADER
+#define NULLOSFT_PORTABLES_PLUGIN_MENU_HEADER
+
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+INT Menu_TrackSkinnedPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm);
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue);
+HMENU Menu_FindRatingMenu(HMENU hMenu, BOOL fUseMarker);
+
+#endif //NULLOSFT_PORTABLES_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/main.cpp b/Src/Plugins/Library/ml_pmp/main.cpp
new file mode 100644
index 00000000..09bd908d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/main.cpp
@@ -0,0 +1,1454 @@
+#define PLUGIN_NAME "Nullsoft Portable Music Player Support"
+#define PLUGIN_VERSION L"2.25"
+
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../winamp/wa_ipc.h"
+#include "nu/ns_wc.h"
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "resource1.h"
+#include "pmp.h"
+#include "DeviceView.h"
+#include "pluginloader.h"
+#include "nu/AutoWide.h"
+#include "api__ml_pmp.h"
+#include "transcoder_imp.h"
+#include <api/service/waservicefactory.h>
+#include "config.h"
+#include "tataki/export.h"
+#include "nu/ServiceWatcher.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "mt19937ar.h"
+#include "./local_menu.h"
+#include "pmpdevice.h"
+#include "IconStore.h"
+#include "../replicant/nx/nxstring.h"
+#include <strsafe.h>
+#include "../nu/MediaLibraryInterface.h"
+#include <vector>
+
+#define MAINTREEID 5002
+#define PROGRESSTIMERID 1
+
+static int init();
+static void quit();
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+extern INT_PTR CALLBACK pmp_devices_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern void UpdateDevicesListView(bool softupdate);
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+void UpdateTransfersListView(bool softUpdate, CopyInst * item=NULL);
+
+static void CALLBACK ProgressTimerTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, unsigned long elapsed);
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_pmp.dll)",
+ init,
+ quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+C_ItemList devices;
+C_ItemList loadingDevices;
+extern HNAVITEM cloudQueueTreeItem;
+static HNAVITEM navigationRoot = NULL;
+static ATOM viewAtom = 0;
+int groupBtn = 1, customAllowed = 0, enqueuedef = 0;
+extern HWND hwndMediaView;
+extern DeviceView * currentViewedDevice;
+HMENU m_context_menus = NULL, m_context_menus2 = NULL;
+int prefsPageLoaded = 0, profile = 0;
+prefsDlgRecW prefsPage;
+prefsDlgRecW pluginsPrefsPage;
+C_Config * global_config;
+C_Config * gen_mlconfig;
+UINT genhotkeys_add_ipc;
+HANDLE hMainThread;
+UINT_PTR mainTreeHandle;
+extern HINSTANCE cloud_hinst;
+extern int IPC_GET_CLOUD_HINST, IPC_LIBRARY_PLAYLISTS_REFRESH;
+void deviceConnected(Device * dev);
+void deviceDisconnected(Device * dev);
+void deviceLoading(pmpDeviceLoading * load);
+void deviceNameChanged(Device * dev);
+
+//extern CRITICAL_SECTION listTransfersLock;
+CRITICAL_SECTION csenumdrives;
+
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_mldb *AGAVE_API_MLDB = 0;
+api_memmgr *WASABI_API_MEMMGR = 0;
+api_syscb *WASABI_API_SYSCB = 0;
+api_application *WASABI_API_APP = 0;
+api_podcasts *AGAVE_API_PODCASTS = 0;
+api_albumart *AGAVE_API_ALBUMART = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_threadpool *WASABI_API_THREADPOOL = 0;
+api_devicemanager *AGAVE_API_DEVICEMANAGER = 0;
+api_metadata *AGAVE_API_METADATA = 0;
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+
+static const GUID pngLoaderGUID =
+ { 0x5e04fb28, 0x53f5, 0x4032, { 0xbd, 0x29, 0x3, 0x2b, 0x87, 0xec, 0x37, 0x25 } };
+
+static svc_imageLoader *wasabiPngLoader = NULL;
+
+HWND mainMessageWindow = 0;
+static bool classRegistered=0;
+HWND CreateDummyWindow()
+{
+ if (!classRegistered)
+ {
+ WNDCLASSW wc = {0, };
+
+ wc.style = 0;
+ wc.lpfnWndProc = DeviceMsgProc;
+ wc.hInstance = plugin.hDllInstance;
+ wc.hIcon = 0;
+ wc.hCursor = NULL;
+ wc.lpszClassName = L"ml_pmp_window";
+
+ if (!RegisterClassW(&wc))
+ return 0;
+
+ classRegistered = true;
+ }
+ HWND dummy = CreateWindowW(L"ml_pmp_window", L"ml_pmp_window", 0, 0, 0, 0, 0, NULL, NULL, plugin.hDllInstance, NULL);
+
+ return dummy;
+}
+
+genHotkeysAddStruct hksync = { 0, HKF_UNICODE_NAME, WM_USER, 0, 0, "ml_pmp_sync", 0 };
+genHotkeysAddStruct hkautofill = { 0, HKF_UNICODE_NAME, WM_USER + 1, 0, 0, "ml_pmp_autofill", 0 };
+genHotkeysAddStruct hkeject = { 0, HKF_UNICODE_NAME, WM_USER + 2, 0, 0, "ml_pmp_eject", 0 };
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+HNAVITEM NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0)
+{
+ NAVCTRLFINDPARAMS find;
+ HNAVITEM item;
+
+ if (NULL == name)
+ return NULL;
+
+ if (NULL == plugin.hwndLibraryParent)
+ return NULL;
+
+ find.pszName = (wchar_t*)name;
+ find.cchLength = -1;
+ find.compFlags = NICF_INVARIANT;
+ find.fFullNameSearch = FALSE;
+
+ item = MLNavCtrl_FindItemByName(plugin.hwndLibraryParent, &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(plugin.hwndLibraryParent, item))
+ {
+ item = NULL;
+ }
+ }
+
+ return item;
+}
+
+HNAVITEM GetNavigationRoot(BOOL forceCreate)
+{
+ if (NULL == navigationRoot &&
+ FALSE != forceCreate)
+ {
+ NAVINSERTSTRUCT nis = {0};
+ wchar_t buffer[512] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLES, buffer, ARRAYSIZE(buffer));
+
+ nis.hParent = NULL;
+ nis.hInsertAfter = NCI_LAST;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_ITEMID | NIMF_PARAM;
+ nis.item.id = MAINTREEID;
+ nis.item.pszInvariant = L"Portables";
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.pszText = buffer;
+ nis.item.lParam = -1;
+
+ navigationRoot = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ if (NULL != navigationRoot)
+ {
+ SetTimer(mainMessageWindow, PROGRESSTIMERID, 250, ProgressTimerTickCb);
+ }
+ }
+
+ return navigationRoot;
+}
+
+
+static HNAVITEM GetNavigationItemFromMessage(int msg, INT_PTR param)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param) :
+ (HNAVITEM)param;
+}
+
+void Menu_ConvertRatingMenuStar(HMENU menu, UINT menu_id)
+{
+MENUITEMINFOW mi = {sizeof(mi), MIIM_DATA | MIIM_TYPE, MFT_STRING};
+wchar_t rateBuf[32], *rateStr = rateBuf;
+ mi.dwTypeData = rateBuf;
+ mi.cch = 32;
+ if(GetMenuItemInfoW(menu, menu_id, FALSE, &mi))
+ {
+ while(rateStr && *rateStr)
+ {
+ if(*rateStr == L'*') *rateStr = L'\u2605';
+ rateStr=CharNextW(rateStr);
+ }
+ SetMenuItemInfoW(menu, menu_id, FALSE, &mi);
+ }
+}
+
+static ServiceWatcher serviceWatcher;
+
+static int init()
+{
+ // if there are no pmp_*.dll then no reason to load
+ if (!testForDevPlugins())
+ return ML_INIT_FAILURE;
+
+ genrand_int31 = (int (*)())SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_RANDFUNC);
+
+ if (0 == viewAtom)
+ {
+ viewAtom = GlobalAddAtomW(L"WinampPortableMediaView");
+ if (0 == viewAtom)
+ return 2;
+ }
+
+ TranscoderImp::init();
+ InitializeCriticalSection(&csenumdrives);
+
+ Tataki::Init(plugin.service);
+ ServiceBuild( AGAVE_API_PLAYLISTS, api_playlistsGUID );
+ ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceBuild( WASABI_API_SYSCB, syscbApiServiceGuid );
+ ServiceBuild( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceBuild( AGAVE_API_STATS, AnonymousStatsGUID );
+ ServiceBuild( WASABI_API_THREADPOOL, ThreadPoolGUID );
+ ServiceBuild( AGAVE_API_DEVICEMANAGER, DeviceManagerGUID );
+ ServiceBuild( AGAVE_API_ALBUMART, albumArtGUID );
+ ServiceBuild( AGAVE_API_METADATA, api_metadataGUID );
+
+ // loader so that we can get the localisation service api for use
+ ServiceBuild( WASABI_API_LNG, languageApiGUID );
+ ServiceBuild( WASABI_API_MEMMGR, memMgrApiServiceGuid );
+
+ // no guarantee that AGAVE_API_MLDB will be available yet, so we'll start a watcher for it
+ serviceWatcher.WatchWith( plugin.service );
+ serviceWatcher.WatchFor( &AGAVE_API_MLDB, mldbApiGuid );
+ serviceWatcher.WatchFor( &AGAVE_API_PODCASTS, api_podcastsGUID );
+
+ WASABI_API_SYSCB->syscb_registerCallback( &serviceWatcher );
+
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ mediaLibrary.GetIniDirectory();
+ mediaLibrary.GetIniDirectoryW();
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlPMPLangGUID);
+
+ static wchar_t szDescription[256];
+ StringCbPrintfW(szDescription, ARRAYSIZE(szDescription), WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PMP_SUPPORT), PLUGIN_VERSION);
+ plugin.description = (char*)szDescription;
+
+ hMainThread = GetCurrentThread();
+ //InitializeCriticalSection(&listTransfersLock);
+ global_config = new C_Config( (wchar_t *)mediaLibrary.GetWinampIniW() );
+ profile = global_config->ReadInt( L"profile", 0, L"Winamp" );
+
+ gen_mlconfig = new C_Config( (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW ), L"gen_ml_config" );
+
+ m_context_menus = WASABI_API_LOADMENU( IDR_CONTEXTMENUS );
+ m_context_menus2 = WASABI_API_LOADMENU( IDR_CONTEXTMENUS );
+
+
+ HMENU rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,0),7);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,1),4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,2),7);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,7),5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ //subclass winamp window
+ mainMessageWindow = CreateDummyWindow();
+
+ prefsPage.hInst = WASABI_API_LNG_HINST;
+ prefsPage.dlgID = IDD_CONFIG_GLOBAL;
+ prefsPage.name = _wcsdup( WASABI_API_LNGSTRINGW( IDS_PORTABLES ) );
+ prefsPage.where = 6;
+ prefsPage.proc = global_config_dlgproc;
+
+ pluginsPrefsPage.hInst = WASABI_API_LNG_HINST;
+ pluginsPrefsPage.dlgID = IDD_CONFIG_PLUGINS;
+ pluginsPrefsPage.name = prefsPage.name;
+ pluginsPrefsPage.where = 1;
+ pluginsPrefsPage.proc = config_dlgproc_plugins;
+
+ // only insert the portables page if we've actually loaded a pmp_*.dll
+ int count = 0;
+ if(loadDevPlugins(&count) && count > 0)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&pluginsPrefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ else if (!count)
+ {
+ // and if there are none, then cleanup and also notify ml_devices.dll to
+ // shut-down as no need for it to keep running if there's not going to be
+ // anything else running (as unlikely we'd have use for it without ml_pmp
+ quit();
+
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(GetModuleHandleW(L"ml_devices.dll"), "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ mlplugin->quit();
+ }
+ }
+
+ return ML_INIT_FAILURE;
+ }
+
+ // we've got pmp_*.dll to load, so lets start the fun...
+ Devices_Init();
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ SetTimer(mainMessageWindow, PROGRESSTIMERID, 250, ProgressTimerTickCb);
+ }
+ else if (!global_config->ReadInt(L"HideRoot",0))
+ {
+ GetNavigationRoot(TRUE);
+ }
+
+ SetTimer(mainMessageWindow,COMMITTIMERID,5000,NULL);
+
+ IPC_LIBRARY_PLAYLISTS_REFRESH = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"ml_playlist_refresh", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+
+ // set up hotkeys...
+ genhotkeys_add_ipc = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&"GenHotkeysAdd",IPC_REGISTER_WINAMP_IPCMESSAGE);
+ hksync.wnd = hkautofill.wnd = hkeject.wnd = mainMessageWindow;
+ hksync.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_SYNC_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hksync,genhotkeys_add_ipc);
+ hkautofill.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_AUTOFILL_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hkautofill,genhotkeys_add_ipc);
+ hkeject.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_EJECT_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hkeject,genhotkeys_add_ipc);
+ return ML_INIT_SUCCESS;
+}
+
+bool quitting=false;
+static void quit()
+{
+ // trigger transfer kill
+ int i = devices.GetSize();
+ if (i > 0)
+ {
+ while(i-- > 0)
+ {
+ DeviceView * device = ((DeviceView*)devices.Get(i));
+ if (device) device->threadKillswitch = 1;
+ }
+ }
+
+ quitting = true;
+ KillTimer(mainMessageWindow, COMMITTIMERID);
+ DeviceMsgProc(NULL, WM_TIMER, COMMITTIMERID, 0);
+
+ i = devices.GetSize();
+ if (i > 0)
+ {
+ while(i-- > 0)
+ {
+ DeviceView * device = ((DeviceView*)devices.Get(i));
+ if (device) device->dev->Close();
+ }
+ }
+ unloadDevPlugins();
+
+ stopServer();
+ HWND f = mainMessageWindow;
+ mainMessageWindow = 0;
+ DestroyWindow(f);
+ delete global_config;
+
+ WASABI_API_SYSCB->syscb_deregisterCallback(&serviceWatcher);
+ serviceWatcher.Clear();
+
+ ServiceRelease( AGAVE_API_PLAYLISTS, api_playlistsGUID );
+ ServiceRelease( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceRelease( WASABI_API_SYSCB, syscbApiServiceGuid );
+ ServiceRelease( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceRelease( WASABI_API_LNG, languageApiGUID );
+ ServiceRelease( WASABI_API_MEMMGR, memMgrApiServiceGuid );
+ ServiceRelease( AGAVE_API_MLDB, mldbApiGuid );
+ ServiceRelease( AGAVE_API_PODCASTS, api_podcastsGUID );
+ ServiceRelease( AGAVE_API_STATS, AnonymousStatsGUID );
+ ServiceRelease( AGAVE_API_DEVICEMANAGER, DeviceManagerGUID );
+ ServiceRelease( AGAVE_API_ALBUMART, albumArtGUID );
+ ServiceRelease( AGAVE_API_METADATA, api_metadataGUID );
+
+ if (NULL != wasabiPngLoader)
+ {
+ ServiceRelease(wasabiPngLoader, pngLoaderGUID);
+ wasabiPngLoader = NULL;
+ }
+
+ DeleteCriticalSection(&csenumdrives);
+ TranscoderImp::quit();
+
+ delete gen_mlconfig;
+ Tataki::Quit();
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ if (0 != viewAtom)
+ {
+ GlobalDeleteAtom(viewAtom);
+ viewAtom = 0;
+ }
+}
+
+typedef struct { ULONG_PTR param; void * proc; int state; CRITICAL_SECTION lock;} spc ;
+
+static VOID CALLBACK spc_caller(ULONG_PTR dwParam) {
+ spc * s = (spc*)dwParam;
+ if(!s) return;
+ EnterCriticalSection(&s->lock);
+ if(s->state == -1) { LeaveCriticalSection(&s->lock); DeleteCriticalSection(&s->lock); free(s); return; }
+ s->state = 2;
+ void (CALLBACK *p)(ULONG_PTR dwParam);
+ p = (void (CALLBACK *)(ULONG_PTR dwParam))s->proc;
+ if(p) p(s->param);
+ s->state=1;
+ LeaveCriticalSection(&s->lock);
+}
+
+static int spc_GetState(spc *s)
+{
+ EnterCriticalSection(&s->lock);
+ int state = s->state;
+ LeaveCriticalSection(&s->lock);
+ return state;
+}
+
+// p must be of type void (CALLBACK *)(ULONG_PTR dwParam). Returns 0 for success.
+int SynchronousProcedureCall(void * p, ULONG_PTR dwParam) {
+ if(!p) return 1;
+ spc * s = (spc*)calloc(1, sizeof(spc));
+ InitializeCriticalSection(&s->lock);
+ s->param = dwParam;
+ s->proc = p;
+ s->state = 0;
+#if 1 // _WIN32_WINNT >= 0x0400
+ if(!QueueUserAPC(spc_caller,hMainThread,(ULONG_PTR)s)) { DeleteCriticalSection(&s->lock); free(s); return 1; } //failed
+#else
+ if(!mainMessageWindow) { DeleteCriticalSection(&s->lock); free(s); return 1; } else PostMessage(mainMessageWindow,WM_USER+3,(WPARAM)spc_caller,(LPARAM)s);
+#endif
+ int i=0;
+ while (spc_GetState(s) != 1)
+ {
+ if (SleepEx(10,TRUE) == 0)
+ i++;
+ if(i >= 100)
+ {
+ EnterCriticalSection(&s->lock);
+ s->state = -1;
+ LeaveCriticalSection(&s->lock);
+ return 1;
+ }
+ }
+ DeleteCriticalSection(&s->lock);
+ free(s);
+ return 0;
+}
+
+static VOID CALLBACK getTranscoder(ULONG_PTR dwParam)
+{
+ C_Config * conf = global_config;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == *(Device **)dwParam) { conf = d->config; break; }
+ }
+ Transcoder ** t = (Transcoder**)dwParam;
+ *t = new TranscoderImp(plugin.hwndWinampParent,plugin.hDllInstance,conf, *(Device **)dwParam);
+}
+
+static void CALLBACK ProgressTimerTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, unsigned long elapsed)
+{
+ wchar_t *buf = (wchar_t*)calloc(sizeof(wchar_t), 256);
+ NAVITEM itemInfo = {0};
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if (!AGAVE_API_DEVICEMANAGER && NULL == rootItem)
+ {
+ KillTimer(hwnd, eventId);
+ if (buf) free(buf);
+ return;
+ }
+
+ // TODO need to get this working for a merged instance...
+ int num = 0,total = 0, percent = 0;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(dev);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(dev);
+ int txProgress = getTransferProgress(dev);
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ int device_num = (100 * size) - txProgress;
+ num += device_num;
+ int device_total = 100 * size + 100 * (finishedTX ? finishedTX->GetSize() : 0);
+ total += device_total;
+ percent = (0 != device_total) ? (((device_total - device_num) * 100) / device_total) : -1;
+ // TODO need to do something to handle it sticking on 100%
+ if (dev->queueTreeItem)
+ {
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = dev->queueTreeItem;
+ itemInfo.mask = NIMF_PARAM;
+
+ if (MLNavItem_GetInfo(plugin.hwndLibraryParent, &itemInfo) && itemInfo.lParam != percent)
+ {
+ if (-1 == percent)
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERS,buf, 256);
+ else
+ StringCbPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_TRANSFERS_PERCENT), percent);
+
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = buf;
+ itemInfo.lParam = percent;
+ itemInfo.cchTextMax = -1;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+ }
+ }
+
+ dev->UpdateActivityState();
+ }
+
+ if (!rootItem)
+ {
+ if (buf) free(buf);
+ return;
+ }
+
+ percent = (0 != total) ? (((total-num)*100)/total) : -1;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = rootItem;
+ itemInfo.mask = NIMF_PARAM;
+
+ if (FALSE == MLNavItem_GetInfo(plugin.hwndLibraryParent, &itemInfo))
+ {
+ if (buf) free(buf);
+ return;
+ }
+
+ if (itemInfo.lParam != percent)
+ {
+ if (-1 == percent)
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLES,buf,256);
+ else
+ StringCbPrintfW(buf, sizeof(buf), WASABI_API_LNGSTRINGW(IDS_PORTABLES_PERCENT),percent);
+
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = buf;
+ itemInfo.lParam = percent;
+ itemInfo.cchTextMax = -1;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+ }
+ if (buf) free(buf);
+}
+
+extern LRESULT CALLBACK TranscodeMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void EnumDrives(ENUM_DRIVES_CALLBACK callback)
+{
+ DWORD drives=GetLogicalDrives();
+ wchar_t drivestr[4] = L"X:\\";
+ for(int i=2;i<25;i++) if(drives&(1<<i))
+ {
+ EnterCriticalSection(&csenumdrives);
+ UINT olderrmode=SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ drivestr[0] = L'A'+i;
+ callback(drivestr[0],GetDriveTypeW(drivestr));
+ SetErrorMode(olderrmode);
+ LeaveCriticalSection(&csenumdrives);
+ }
+}
+
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_WA_IPC && hwnd != mainMessageWindow) return TranscodeMsgProc(hwnd,uMsg,wParam,lParam);
+ switch(uMsg)
+ {
+ case WM_USER: // hotkey: sync
+ if(devices.GetSize() > 0)
+ {
+ if (!((DeviceView *)devices.Get(0))->isCloudDevice)
+ ((DeviceView *)devices.Get(0))->Sync();
+ else
+ ((DeviceView *)devices.Get(0))->CloudSync();
+ }
+ break;
+ case WM_USER+1: // hotkey: autofill
+ if(devices.GetSize() > 0)
+ ((DeviceView *)devices.Get(0))->Autofill();
+ break;
+ case WM_USER+2: // hotkey: eject
+ if(devices.GetSize() > 0)
+ ((DeviceView *)devices.Get(0))->Eject();
+ break;
+ case WM_USER+3:
+ {
+ void (CALLBACK *p)(ULONG_PTR dwParam);
+ p = (void (CALLBACK *)(ULONG_PTR dwParam))wParam;
+ p((ULONG_PTR)lParam);
+ }
+ break;
+ case WM_USER+4: // refreshes cloud views (re-checks if we've moved away)
+ if (IsWindow(hwndMediaView) && currentViewedDevice && currentViewedDevice->isCloudDevice)
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ break;
+ case WM_USER+5: // removes cloud devices from a cloud sources logout...
+ {
+ for (int i = devices.GetSize() - 1; i > -1; --i)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (dev->isCloudDevice)
+ dev->dev->Close();
+ }
+ break;
+ }
+ case WM_PMP_IPC:
+ {
+ switch(lParam)
+ {
+ case PMP_IPC_DEVICECONNECTED:
+ deviceConnected((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICEDISCONNECTED:
+ deviceDisconnected((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICELOADING:
+ deviceLoading((pmpDeviceLoading *)wParam);
+ break;
+ case PMP_IPC_DEVICENAMECHANGED:
+ deviceNameChanged((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICECLOUDTRANSFER:
+ {
+ int ret = 0;
+ cloudDeviceTransfer * transfer = (cloudDeviceTransfer *)wParam;
+ if (transfer)
+ {
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (dev->dev->extraActions(DEVICE_IS_CLOUD_TX_DEVICE, (intptr_t)transfer->device_token, 0, 0))
+ {
+ ret = dev->TransferFromML(ML_TYPE_FILENAMESW, (void *)transfer->filenames, 0, 1);
+ }
+ }
+ }
+ return ret;
+ }
+ case PMP_IPC_GETCLOUDTRANSFERS:
+ {
+ typedef std::vector<wchar_t*> CloudFiles;
+ CloudFiles *pending = (CloudFiles *)wParam;
+ if (pending)
+ {
+ cloudTransferQueue.lock();
+ // TODO de-dupe incase going to multiple devices...
+ for(int i = 0; i < cloudTransferQueue.GetSize(); i++)
+ {
+ pending->push_back(((CopyInst *)cloudTransferQueue.Get(i))->sourceFile);
+ }
+ cloudTransferQueue.unlock();
+
+ return pending->size();
+ }
+ return 0;
+ }
+ case PMP_IPC_GET_TRANSCODER:
+ {
+ void * t = (void*)wParam;
+ getTranscoder((ULONG_PTR)&t);
+ if (t == (void*)wParam) return 0;
+ return (LRESULT)t;
+ }
+ case PMP_IPC_RELEASE_TRANSCODER:
+ delete ((TranscoderImp *)wParam);
+ break;
+ case PMP_IPC_ENUM_ACTIVE_DRIVES:
+ {
+ if (wParam == 0)
+ return (LRESULT)EnumDrives;
+ else
+ EnumDrives((ENUM_DRIVES_CALLBACK)wParam);
+ }
+ break;
+ case PMP_IPC_GET_INI_FILE:
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if(dev->dev == (Device*)wParam) return (intptr_t)dev->config->GetIniFile();
+ }
+ break;
+ case PMP_IPC_GET_PREFS_VIEW:
+ {
+ pmpDevicePrefsView *view = (pmpDevicePrefsView *)wParam;
+ if (view)
+ {
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (!lstrcmpA(dev->GetName(), view->dev_name))
+ {
+ return (LRESULT)OnSelChanged(view->parent, view->parent, dev);
+ }
+ }
+ }
+ return 0;
+ }
+ }
+ }
+ break;
+ case WM_DEVICECHANGE:
+ {
+ int r = wmDeviceChange(wParam,lParam);
+ if (r) return r;
+ }
+ break;
+ case WM_TIMER:
+ switch(wParam)
+ {
+ case COMMITTIMERID:
+ {
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if (txQueue)
+ {
+ if(d->commitNeeded && !txQueue->GetSize())
+ {
+ d->commitNeeded = false;
+ d->dev->commitChanges();
+ }
+ }
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+void UpdateLoadingCaption(wchar_t * caption, void * context)
+{
+ NAVITEM itemInfo = {0};
+
+ itemInfo.hItem = (HNAVITEM)context;
+ if (NULL == itemInfo.hItem)
+ return;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.pszText = caption;
+ itemInfo.cchTextMax = -1;
+ itemInfo.mask = NIMF_TEXT;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+}
+
+void deviceLoading(pmpDeviceLoading * load)
+{
+ if (!AGAVE_API_DEVICEMANAGER)
+ {
+ wchar_t buffer[256] = {0};
+ NAVINSERTSTRUCT nis = {0};
+ MLTREEIMAGE devIcon;
+
+ devIcon.resourceId = IDR_DEVICE_ICON;
+ devIcon.hinst = plugin.hDllInstance;
+ if(load->dev)
+ load->dev->extraActions(DEVICE_SET_ICON,(intptr_t)&devIcon,0,0);
+
+ nis.hParent = GetNavigationRoot(TRUE);
+ nis.hInsertAfter = NCI_LAST;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_LOADING, buffer, ARRAYSIZE(buffer));
+ nis.item.pszInvariant = L"device_loading";
+ nis.item.style = NIS_ITALIC;
+ nis.item.iImage = icon_store.GetResourceIcon(devIcon.hinst, (LPCWSTR)MAKEINTRESOURCE(devIcon.resourceId));
+ nis.item.iSelectedImage = nis.item.iImage;
+ load->context = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ load->UpdateCaption = UpdateLoadingCaption;
+ loadingDevices.Add(load);
+ }
+}
+
+void finishDeviceLoading(Device * dev)
+{
+ for(int i=0; i < loadingDevices.GetSize(); i++)
+ {
+ pmpDeviceLoading * l = (pmpDeviceLoading *)loadingDevices.Get(i);
+ if(l->dev == dev) {
+ loadingDevices.Del(i);
+ HNAVITEM treeItem = (HNAVITEM)l->context;
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent,treeItem);
+ return;
+ }
+ }
+}
+
+void deviceConnected(Device * dev)
+{
+ if (!AGAVE_API_DEVICEMANAGER)
+ GetNavigationRoot(TRUE);
+
+ Device * oldDev = dev;
+
+ finishDeviceLoading(oldDev);
+ if(!devices.GetSize()) startServer();
+
+ DeviceView *pmp_device = new DeviceView(dev);
+ devices.Add(pmp_device);
+ UpdateDevicesListView(false);
+}
+
+void deviceDisconnected(Device * dev)
+{
+ finishDeviceLoading(dev);
+ int cloud_count = 0;
+ for(int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == dev)
+ {
+ d->threadKillswitch = 1;
+ d->transferContext.WaitForKill();
+ devices.Del(i);
+ d->Unregister();
+ d->Release();
+ }
+ else
+ {
+ // to keep the 'cloud library' node on the correct expanded state
+ // we need to adjust the cloud sources count due to 'all sources'
+ if (_strnicmp(d->GetName(), "all_sources", 11) && d->isCloudDevice)
+ {
+ cloud_count += 1;
+ }
+ }
+ }
+
+ // if we're only showing a single device, then we can just disable everything else
+ if (cloud_count == 0)
+ {
+ HNAVITEM cloud_transfers = NavigationItem_Find(0, L"cloud_transfers", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ cloudQueueTreeItem = NULL;
+ }
+
+ cloud_transfers = NavigationItem_Find(0, L"cloud_add_sources", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ }
+
+ cloud_transfers = NavigationItem_Find(0, L"cloud_byos", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ }
+
+ HNAVITEM cloud = NavigationItem_Find(0, L"cloud_sources", TRUE);
+ if (cloud != NULL)
+ {
+ NAVITEM item = {0};
+ item.cbSize = sizeof(NAVITEM);
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ item.hItem = cloud;
+ item.iImage = item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD);
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+ }
+
+ UpdateDevicesListView(false);
+ if(!devices.GetSize() && !loadingDevices.GetSize() && global_config->ReadInt(L"HideRoot",0))
+ {
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if (NULL != rootItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+ }
+ if(!devices.GetSize()) stopServer();
+}
+
+void deviceNameChanged(Device * dev)
+{
+ finishDeviceLoading(dev);
+ for(int i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == dev)
+ {
+ wchar_t *buffer = (wchar_t*)calloc(sizeof(wchar_t),128);
+ if (buffer)
+ {
+ dev->getPlaylistName(0, buffer, 128);
+ UpdateLoadingCaption(buffer, d->treeItem);
+ d->SetDisplayName(buffer, 1);
+ free(buffer);
+ }
+ break;
+ }
+ }
+}
+
+svc_imageLoader *GetPngLoaderService()
+{
+ if (NULL == wasabiPngLoader)
+ {
+ if (NULL == WASABI_API_MEMMGR)
+ return NULL;
+
+ ServiceBuild(wasabiPngLoader, pngLoaderGUID);
+ }
+
+ return wasabiPngLoader;
+}
+
+BOOL FormatResProtocol(const wchar_t *resourceName, const wchar_t *resourceType, wchar_t *buffer, size_t bufferMax)
+{
+ unsigned long filenameLength;
+
+ if (NULL == resourceName)
+ return FALSE;
+
+ if (FAILED(StringCchCopyExW(buffer, bufferMax, L"res://", &buffer, &bufferMax, 0)))
+ return FALSE;
+
+ filenameLength = GetModuleFileNameW(plugin.hDllInstance, buffer, bufferMax);
+ if (0 == filenameLength || bufferMax == filenameLength)
+ return FALSE;
+
+ buffer += filenameLength;
+ bufferMax -= filenameLength;
+
+ if (NULL != resourceType)
+ {
+ if (FALSE != IS_INTRESOURCE(resourceType))
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceType)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceType)))
+ return FALSE;
+ }
+ }
+
+ if (FALSE != IS_INTRESOURCE(resourceName))
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceName)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceName)))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL
+CenterWindow(HWND window, HWND centerWindow)
+{
+ RECT centerRect, windowRect;
+ long x, y;
+
+ if (NULL == window ||
+ FALSE == GetWindowRect(window, &windowRect))
+ {
+ return FALSE;
+ }
+
+ if (CENTER_OVER_ML_VIEW == centerWindow)
+ {
+ centerWindow = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == centerWindow || FALSE == IsWindowVisible(centerWindow))
+ centerWindow = CENTER_OVER_ML;
+ }
+
+ if (CENTER_OVER_ML == centerWindow)
+ {
+ centerWindow = plugin.hwndLibraryParent;
+ if (NULL == centerWindow || FALSE == IsWindowVisible(centerWindow))
+ centerWindow = CENTER_OVER_WINAMP;
+ }
+
+ if (CENTER_OVER_WINAMP == centerWindow)
+ {
+ centerWindow = (HWND)SENDWAIPC(plugin.hwndWinampParent, IPC_GETDIALOGBOXPARENT, 0);
+ if (FALSE == IsChild(centerWindow, plugin.hwndLibraryParent))
+ centerWindow = NULL;
+ }
+
+ if (NULL == centerWindow ||
+ FALSE == IsWindowVisible(centerWindow) ||
+ FALSE == GetWindowRect(centerWindow, &centerRect))
+ {
+ HMONITOR monitor;
+ MONITORINFO monitorInfo;
+
+ monitor = MonitorFromWindow(plugin.hwndWinampParent, MONITOR_DEFAULTTONEAREST);
+
+ monitorInfo.cbSize = sizeof(monitorInfo);
+
+ if (NULL == monitor ||
+ FALSE == GetMonitorInfo(monitor, &monitorInfo) ||
+ FALSE == CopyRect(&centerRect, &monitorInfo.rcWork))
+ {
+ CopyRect(&centerRect, &windowRect);
+ }
+ }
+
+ x = centerRect.left + ((centerRect.right - centerRect.left)- (windowRect.right - windowRect.left))/2;
+ y = centerRect.top + ((centerRect.bottom - centerRect.top)- (windowRect.bottom - windowRect.top))/2;
+
+ if (x == windowRect.left && y == windowRect.top)
+ return FALSE;
+
+ return SetWindowPos(window, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
+}
+ATOM
+GetViewAtom()
+{
+ return viewAtom;
+}
+
+void *
+GetViewData(HWND hwnd)
+{
+ return GetPropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom));
+}
+
+BOOL
+SetViewData(HWND hwnd, void *data)
+{
+ return SetPropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom), data);
+}
+
+void *
+RemoveViewData(HWND hwnd)
+{
+ return RemovePropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom));
+}
+
+static void URL_GetParameter(const wchar_t *param_start, char *dest, size_t dest_len)
+{
+ if (!param_start)
+ {
+ dest[0]=0;
+ return;
+ }
+
+ while (param_start && *param_start++ != '=');
+
+ while (param_start && *param_start && dest_len > 1 && *param_start != L'&')
+ {
+ if (*param_start == '+')
+ {
+ param_start++;
+ *dest++=' ';
+ }
+ else if (*param_start == L'%' && param_start[1] != L'%' && param_start[1])
+ {
+ int a=0;
+ int b=0;
+ for ( b = 0; b < 2; b ++)
+ {
+ int r=param_start[1+b];
+ if (r>='0'&&r<='9') r-='0';
+ else if (r>='a'&&r<='z') r-='a'-10;
+ else if (r>='A'&&r<='Z') r-='A'-10;
+ else break;
+ a*=16;
+ a+=r;
+ }
+ if (b < 2)
+ {
+ *dest++=(char)*param_start++;
+ }
+ else
+ {
+ *dest++=a;
+ param_start += 3;}
+ }
+ else
+ {
+ *dest++=(char)*param_start++;
+ }
+ dest_len--;
+ }
+ *dest = 0;
+}
+
+// this only works for one-character parameters
+static const wchar_t *GetParameterStart(const wchar_t *url, wchar_t param)
+{
+ wchar_t lookup[4] = { L'&', param, L'=', 0 };
+ lookup[1] = param;
+ const wchar_t *val = wcsstr(url, lookup);
+ if (!val)
+ {
+ lookup[0] = L'?';
+ val = wcsstr(url, lookup);
+ }
+ return val;
+}
+extern int serverPort;
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ int i;
+ if (message_type == ML_IPC_HOOKEXTINFOW)
+ {
+ extendedFileInfoStructW *hookMetadata = (extendedFileInfoStructW *)param1;
+ if (hookMetadata->filename && !wcsncmp(hookMetadata->filename, L"http://127.0.0.1:", 17) && _wtoi(hookMetadata->filename + 17) == serverPort)
+ {
+ if (!_wcsicmp(hookMetadata->metadata, L"artist"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 'a'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ else if (!_wcsicmp(hookMetadata->metadata, L"album"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 'l'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ else if (!_wcsicmp(hookMetadata->metadata, L"title"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 't'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ }
+ }
+
+ for(i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView *deviceView;
+ deviceView = (DeviceView *)devices.Get(i);
+ if (NULL != deviceView)
+ {
+ INT_PTR a= deviceView->MessageProc(message_type,param1,param2,param3);
+ if(0 != a)
+ return a;
+ }
+ }
+
+
+ if (message_type >= ML_MSG_TREE_BEGIN &&
+ message_type <= ML_MSG_TREE_END)
+ {
+ HNAVITEM item, rootItem;
+ item = GetNavigationItemFromMessage(message_type, param1);
+ rootItem = GetNavigationRoot(FALSE);
+
+ if(message_type == ML_MSG_TREE_ONCREATEVIEW)
+ {
+ for(i=0; i < loadingDevices.GetSize(); i++)
+ {
+ pmpDeviceLoading * l = (pmpDeviceLoading *)loadingDevices.Get(i);
+ if(NULL != l->context)
+ {
+ if (((HNAVITEM)l->context) == item)
+ {
+ HNAVITEM parentItem;
+ parentItem = MLNavItem_GetParent(plugin.hwndLibraryParent, item);
+ if (NULL == parentItem)
+ parentItem = rootItem;
+
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)parentItem, ML_IPC_NAVITEM_SELECT);
+ return 0;
+ }
+ }
+ }
+ }
+
+ if(NULL != item && item == rootItem)
+ {
+ switch (message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ return (INT_PTR)WASABI_API_CREATEDIALOGW(IDD_VIEW_PMP_DEVICES,(HWND)param2,pmp_devices_dlgproc);
+ case ML_MSG_TREE_ONCLICK:
+ switch(param2)
+ {
+ case ML_ACTION_RCLICK:
+ {
+ HMENU menu = GetSubMenu(m_context_menus,6);
+ int hideRoot = global_config->ReadInt(L"HideRoot",0);
+ CheckMenuItem(menu,ID_MAINTREEROOT_AUTOHIDEROOT,hideRoot?MF_CHECKED:MF_UNCHECKED);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,p.x,p.y,plugin.hwndLibraryParent,NULL);
+ switch(r)
+ {
+ case ID_MAINTREEROOT_AUTOHIDEROOT:
+ hideRoot = hideRoot?0:1;
+ global_config->WriteInt(L"HideRoot",hideRoot);
+ if(hideRoot && devices.GetSize() == 0)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+ }
+ break;
+ case ID_MAINTREEROOT_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &pluginsPrefsPage);
+ break;
+ case ID_MAINTREEROOT_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8106455294612-Winamp-Portables-Guide");
+ break;
+ }
+ }
+ break;
+ case ML_ACTION_DBLCLICK:
+ break;
+ case ML_ACTION_ENTER:
+ break;
+ }
+ break;
+ case ML_MSG_TREE_ONDROPTARGET:
+ break;
+ case ML_MSG_TREE_ONDRAG:
+ break;
+ case ML_MSG_TREE_ONDROP:
+ break;
+ case ML_MSG_NAVIGATION_ONDELETE:
+ navigationRoot = NULL;
+ KillTimer(mainMessageWindow, PROGRESSTIMERID);
+ return TRUE;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_NO_CONFIG)
+ {
+ if(prefsPage._id == 0)
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_CONFIG)
+ {
+ if(prefsPage._id == 0) return 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, prefsPage._id, IPC_OPENPREFSTOPAGE);
+ }
+ else if (message_type == ML_MSG_NOTOKTOQUIT)
+ {
+ // see if we have any transfers in progress and if so then prompt on what to do...
+ bool transfers = false;
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if(txQueue && txQueue->GetSize() > 0)
+ {
+ transfers = true;
+ break;
+ }
+ }
+
+ if (transfers)
+ {
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_CANCEL_TRANSFERS_AND_QUIT),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRM_QUIT,titleStr,32), MB_YESNO | MB_ICONQUESTION) == IDNO)
+ return TRUE;
+ }
+ return FALSE;
+ }
+ else if(message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_ITEMRECORDLISTW ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW ||
+ param1 == ML_TYPE_PLAYLISTS || param1 == ML_TYPE_PLAYLIST)
+ {
+ if (gen_mlconfig->ReadInt(L"pmp_send_to", DEFAULT_PMP_SEND_TO))
+ {
+ for(int m = 0, mode = 0; m < 2; m++, mode++)
+ {
+ int added = 0;
+ for(i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView *deviceView;
+ deviceView = (DeviceView *)devices.Get(i);
+ if (NULL != deviceView)
+ {
+ if (deviceView->dev->extraActions(DEVICE_SENDTO_UNSUPPORTED, 0, 0, 0) == 0)
+ {
+ wchar_t buffer[128] = {0};
+ deviceView->dev->getPlaylistName(0, buffer, 128);
+ if (buffer[0])
+ {
+ // TODO - this is to block true playlists from appearing on the sendto
+ // for cloud playlists handling - remove this when we can do more
+ // than just uploading the playlist blob without the actual files
+ if (deviceView->isCloudDevice && param3 == ML_TYPE_PLAYLIST) continue;
+
+ if (!deviceView->isCloudDevice == mode)
+ {
+ if (!added)
+ {
+ mediaLibrary.BranchSendTo(param2);
+ added = 1;
+ }
+ mediaLibrary.AddToBranchSendTo(buffer, param2, reinterpret_cast<INT_PTR>(deviceView));
+ }
+ }
+ }
+ }
+ }
+
+ if (added)
+ {
+ mediaLibrary.EndBranchSendTo(WASABI_API_LNGSTRINGW((!m ? IDS_SENDTO_CLOUD : IDS_SENDTO_DEVICES)), param2);
+ }
+ }
+ }
+ }
+ }
+
+ else if (message_type == ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE)
+ {
+ enqueuedef = param1;
+ groupBtn = param2;
+ if (IsWindow(hwndMediaView))
+ PostMessage(hwndMediaView, WM_APP + 104, param1, param2);
+ return 0;
+ }
+
+ return 0;
+}
+
+extern "C" {
+ __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // cleanup the ml tree so the portables root isn't left
+
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if(NULL != rootItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+
+ // prompt to remove our settings with default as no (just incase)
+ wchar_t title[256] = {0};
+ StringCbPrintfW(title, ARRAYSIZE(title), WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PMP_SUPPORT), PLUGIN_VERSION);
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ title,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ global_config->WriteString(0,0);
+ }
+
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&pluginsPrefsPage,IPC_REMOVE_PREFS_DLG);
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/main.h b/Src/Plugins/Library/ml_pmp/main.h
new file mode 100644
index 00000000..53d2767e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/main.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <wtypes.h>
+#include <windowsx.h>
+
+#include "./graphics.h"
+#include "DeviceView.h"
+#include "../ml_pmp/pmp.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include <map>
+#include "./syncDialog.h"
+#include "./syncCloudDialog.h"
+
+#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 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
+
+#define DEFAULT_PMP_SEND_TO 1
+extern DeviceView *configDevice;
+extern winampMediaLibraryPlugin plugin;
+extern C_ItemList devices;
+extern HWND mainMessageWindow;
+extern std::map<DeviceView *, bool> device_update_map;
+extern C_Config * global_config;
+extern void Devices_Init();
+extern HMENU m_context_menus, m_context_menus2;
+
+/* indirectplaybackserver.cpp - for localhost HTTP-based playback (for devices that don't support direct playback */
+void startServer();
+void stopServer();
+
+ATOM GetViewAtom();
+
+void *GetViewData(HWND hwnd);
+
+BOOL SetViewData(HWND hwnd, void *data);
+
+void *RemoveViewData(HWND hwnd);
+
+BOOL FormatResProtocol(const wchar_t *resourceName,
+ const wchar_t *resourceType,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+#define CENTER_OVER_WINAMP ((HWND)0)
+#define CENTER_OVER_ML ((HWND)1)
+#define CENTER_OVER_ML_VIEW ((HWND)2)
+
+BOOL CenterWindow(HWND window, HWND centerWindow);
+HWND OnSelChanged(HWND hwndDlg, HWND external, DeviceView *dev);
+
+LinkedQueue *getTransferQueue(DeviceView *deviceView = NULL);
+LinkedQueue *getFinishedTransferQueue(DeviceView *deviceView = NULL);
+int getTransferProgress(DeviceView *deviceView = NULL);
+
+extern int groupBtn, customAllowed, enqueuedef; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/metadata_utils.cpp b/Src/Plugins/Library/ml_pmp/metadata_utils.cpp
new file mode 100644
index 00000000..4a238612
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/metadata_utils.cpp
@@ -0,0 +1,516 @@
+#include "main.h"
+#include "DeviceView.h"
+#include "metadata_utils.h"
+#include "../nu/sort.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+#include "api__ml_pmp.h"
+
+#define METADATASTRATAGYSWITCH 500
+
+filenameMap **filenameMapping;
+int filenameMapLen;
+static INT_PTR CALLBACK findingMetadata2_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static int i;
+ static int added;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ SendDlgItemMessage(hwndDlg, IDC_METADATAPROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, filenameMapLen));
+ i=0;
+ added=0;
+ SetTimer(hwndDlg, 1, 10, NULL);
+
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+
+ SetForegroundWindow(hwndDlg);
+ return 0;
+
+ case WM_TIMER:
+ if(wParam == 1) {
+ KillTimer(hwndDlg, 1);
+ filenameMap **map = filenameMapping;
+ int len = filenameMapLen;
+ for(; i < len; i++) {
+ if (i % 25 == 0) SendDlgItemMessage(hwndDlg, IDC_METADATAPROGRESS, PBM_SETPOS, i, 0);
+
+ itemRecordW *result = AGAVE_API_MLDB->GetFile(map[i]->fn);
+ map[i]->ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1);
+ if (result) {
+ copyRecord(map[i]->ice, result);
+ AGAVE_API_MLDB->FreeRecord(result);
+
+ if(i % 200 == 0) {
+ i++;
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ return 0;
+ }
+ } else {
+ filenameToItemRecord(map[i]->fn, map[i]->ice); // ugh. Disk intensive.
+ SendMessage(plugin.hwndWinampParent, WM_ML_IPC, (WPARAM)map[i]->ice, ML_IPC_DB_ADDORUPDATEITEMW);
+ added++;
+ i++;
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ return 0;
+ }
+ }
+ if (added) SendMessage(plugin.hwndWinampParent,WM_ML_IPC,0,ML_IPC_DB_SYNCDB);
+ EndDialog(hwndDlg,0);
+ }
+ break;
+ }
+ return 0;
+}
+
+void mapFilesToItemRecords(filenameMap ** map0, int len, HWND centerWindow) {
+ filenameMapping = map0;
+ filenameMapLen = len;
+ if (filenameMapLen > 0)
+ WASABI_API_DIALOGBOXPARAMW(IDD_GETTINGMETADATA,plugin.hwndWinampParent, findingMetadata2_dlgproc, (LPARAM)centerWindow);
+}
+
+C_ItemList * fileListToItemRecords(wchar_t** files,int l, HWND centerWindow) {
+ filenameMap ** map = (filenameMap **)calloc(l, sizeof(void*));
+ filenameMap * m = (filenameMap *)calloc(l, sizeof(filenameMap));
+ for(int i=0; i<l; i++) {
+ map[i] = &m[i];
+ map[i]->fn = files[i];
+ }
+
+ mapFilesToItemRecords(map, l, centerWindow);
+
+ C_ItemList * out = new C_ItemList;
+ for(int i=0; i<l; i++)
+ out->Add(map[i]->ice);
+ free(m);
+ free(map);
+ return out;
+}
+
+C_ItemList * fileListToItemRecords(C_ItemList * fileList, HWND centerWindow) {
+ return fileListToItemRecords((wchar_t**)fileList->GetAll(),fileList->GetSize(), centerWindow);
+}
+
+typedef struct
+{
+ songid_t songid;
+ Device * dev;
+} SortSongItem;
+
+#define RETIFNZ(v) { int zz = (v); if(zz) return zz; }
+#define CMPFIELDS(x) { x(a,bufa,256); x(b,bufb,256); int v = lstrcmpiW(bufa,bufb); if(v) return v; }
+
+int __fastcall compareSongs(const void *elem1, const void *elem2, const void *context) {
+ songid_t a = *(songid_t*)elem1;
+ songid_t b = *(songid_t*)elem2;
+ if(a == b) return 0;
+ Device * dev = (Device *)context;
+ wchar_t bufa[256] = {0};
+ wchar_t bufb[256] = {0};
+ CMPFIELDS(dev->getTrackArtist)
+ CMPFIELDS(dev->getTrackAlbum)
+ CMPFIELDS(dev->getTrackTitle)
+ int t1 = dev->getTrackTrackNum(a);
+ int t2 = dev->getTrackTrackNum(b);
+ if(t1>0 && t2>0) RETIFNZ(t1 - t2)
+ return 0;
+}
+
+#undef CMPFIELDS
+
+static __forceinline int strcmp_nullok(wchar_t * x,wchar_t * y) {
+ if(!x) x=L"";
+ if(!y) y=L"";
+ return lstrcmpiW(x,y);
+}
+
+int compareItemRecordAndSongId(itemRecordW * item, songid_t song, Device *dev)
+{
+ wchar_t buf[2048] = {0};
+ dev->getTrackArtist(song,buf,sizeof(buf)/sizeof(wchar_t));
+ RETIFNZ(strcmp_nullok(buf,item->artist));
+ dev->getTrackAlbum(song,buf,sizeof(buf)/sizeof(wchar_t));
+ RETIFNZ(strcmp_nullok(buf,item->album));
+ dev->getTrackTitle(song,buf,sizeof(buf)/sizeof(wchar_t));
+ RETIFNZ(strcmp_nullok(buf,item->title));
+ int t = dev->getTrackTrackNum(song);
+ if(item->track>0 && t>0) RETIFNZ(t - item->track);
+ return 0;
+}
+
+int compareItemRecords(itemRecordW * a, itemRecordW * b) {
+ if(a == b) return 0;
+ RETIFNZ(lstrcmpiW(a->artist?a->artist:L"",b->artist?b->artist:L""));
+ RETIFNZ(lstrcmpiW(a->album?a->album:L"",b->album?b->album:L""));
+ RETIFNZ(lstrcmpiW(a->title?a->title:L"",b->title?b->title:L""));
+ if(a->track>0 && b->track>0) RETIFNZ(a->track - b->track);
+ return 0;
+}
+
+static int sortfunc_ItemRecords_map(const void *elem1, const void *elem2) {
+ PlaylistAddItem *a = *(PlaylistAddItem **)elem1;
+ PlaylistAddItem *b = *(PlaylistAddItem **)elem2;
+ return compareItemRecords(a->item,b->item);
+}
+
+static int sortfunc_ItemRecords(const void *elem1, const void *elem2) {
+ itemRecordW *a = *(itemRecordW **)elem1;
+ itemRecordW *b = *(itemRecordW **)elem2;
+ return compareItemRecords(a,b);
+}
+
+#undef RETIFNZ
+
+/* Gay Venn Diagram(tm) explaining ProcessDatabaseDifferences. Leave arguments NULL if not required
+ ml device
+ /------\ /------\
+ / X \
+ / / \<-------\---songsInML and itemRecordsOnDevice
+ \ \ / /
+ \ X /
+ \------/ \------/
+ ^ ^--------songsNotInML
+ |--itemRecordsNotOnDevice
+*/
+// Runs in O(nlogn) of largest list
+void ProcessDatabaseDifferences(Device * dev, C_ItemList * ml0,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML) {
+ C_ItemList device2;
+ C_ItemList *device0=&device2;
+
+ int l = dev->getPlaylistLength(0);
+ for(int i=0; i<l; i++) device0->Add((void*)dev->getPlaylistTrack(0,i));
+
+ qsort(ml0->GetAll(),ml0->GetSize(),sizeof(void*),sortfunc_ItemRecords);
+ nu::qsort(device0->GetAll(), device0->GetSize(), sizeof(void*), dev, compareSongs);
+
+ C_ItemList *ml = new C_ItemList;
+ C_ItemList *device = new C_ItemList;
+
+ int i,j;
+ {
+ itemRecordW * lastice = NULL;
+ songid_t lastsong = NULL;
+ for(i=0; i<ml0->GetSize(); i++) {
+ itemRecordW * it = (itemRecordW*)ml0->Get(i);
+ if(lastice) if(compareItemRecords(lastice,it)==0) continue;
+ ml->Add(it);
+ lastice = it;
+ }
+ for(i=0; i<device0->GetSize(); i++) {
+ songid_t song = (songid_t)device0->Get(i);
+ if(lastsong) if(compareSongs((void*)&song,(void*)&lastsong, dev)==0) continue;
+ device->Add((void*)song);
+ lastsong = song;
+ }
+ }
+
+ i=0,j=0;
+ int li = device->GetSize();
+ int lj = ml->GetSize();
+ while(i<li && j<lj) {
+ itemRecordW * it = (itemRecordW*)ml->Get(j);
+ songid_t song = (songid_t)device->Get(i);
+
+ int cmp = compareItemRecordAndSongId(it,song, dev);
+ if(cmp == 0) { // song on both
+ if(itemRecordsOnDevice) itemRecordsOnDevice->Add(it);
+ if(songsInML) songsInML->Add((void*)song);
+ i++;
+ j++;
+ }
+ else if(cmp > 0) { //song in ml and not on device
+ if(itemRecordsNotOnDevice) itemRecordsNotOnDevice->Add(it);
+ j++;
+ }
+ else { // song on device but not in ML
+ if(songsNotInML) songsNotInML->Add((void*)song);
+ i++;
+ }
+ }
+
+ // any leftovers?
+ if(songsNotInML) while(i<li) {
+ songid_t song = (songid_t)device->Get(i++);
+ songsNotInML->Add((void*)song);
+ }
+
+ if(itemRecordsNotOnDevice) while(j<lj) {
+ itemRecordW * it = (itemRecordW *)ml->Get(j++);
+ itemRecordsNotOnDevice->Add(it);
+ }
+
+ delete ml; delete device;
+}
+
+void MapItemRecordsToSongs(Device * dev, PlaylistAddItem ** map, int len, C_ItemList * itemRecordsNotOnDevice) {
+ C_ItemList device;
+ int l = dev->getPlaylistLength(0);
+ int i;
+ for(i=0; i<l; i++) device.Add((void*)dev->getPlaylistTrack(0,i));
+
+ qsort(map,len,sizeof(void*),sortfunc_ItemRecords_map);
+ nu::qsort(device.GetAll(),device.GetSize(),sizeof(void*),dev,compareSongs);
+
+ int j=0;
+ i=0;
+ int li = device.GetSize();
+ int lj = len;
+ while(i<li && j<lj) {
+ PlaylistAddItem* p = map[j];
+ songid_t s = (songid_t)device.Get(i);
+ int cmp = compareItemRecordAndSongId(p->item,s, dev);
+ if(cmp == 0) {
+ p->songid = s;
+ j++;
+ }
+ else if(cmp > 0) { j++; if(itemRecordsNotOnDevice) itemRecordsNotOnDevice->Add(p->item); }
+ else i++;
+ }
+}
+
+void ProcessDatabaseDifferences(Device * dev, itemRecordListW * ml,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML) {
+ if(!ml) return;
+ C_ItemList ml_list;
+ for(int i=0; i < ml->Size; i++) ml_list.Add(&ml->Items[i]);
+ ProcessDatabaseDifferences(dev,&ml_list,itemRecordsOnDevice,itemRecordsNotOnDevice,songsInML,songsNotInML);
+}
+
+typedef struct { songid_t song; Device * dev; const wchar_t * filename; } tagItem;
+
+static wchar_t * tagFunc(const wchar_t * tag, void * p) { //return 0 if not found, -1 for empty tag
+ tagItem * s = (tagItem *)p;
+ int len = 2048;
+ wchar_t * buf = (wchar_t *)calloc(len, sizeof(wchar_t));
+ if (buf)
+ {
+ if (!_wcsicmp(tag, L"artist")) s->dev->getTrackArtist(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"album")) s->dev->getTrackAlbum(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"title")) s->dev->getTrackTitle(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"genre")) s->dev->getTrackGenre(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"year")) wsprintf(buf,L"%d",s->dev->getTrackYear(s->song));
+ else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) wsprintf(buf,L"%d",s->dev->getTrackTrackNum(s->song));
+ else if (!_wcsicmp(tag, L"discnumber")) wsprintf(buf,L"%d",s->dev->getTrackDiscNum(s->song));
+ else if (!_wcsicmp(tag, L"bitrate")) wsprintf(buf,L"%d",s->dev->getTrackBitrate(s->song));
+ else if (!_wcsicmp(tag, L"filename")) lstrcpyn(buf,s->filename,len);
+ else if (!_wcsicmp(tag, L"albumartist")) s->dev->getTrackAlbumArtist(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"composer")) s->dev->getTrackComposer(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"publisher")) s->dev->getTrackPublisher(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"mime")) s->dev->getTrackMimeType(s->song,buf,len);
+ }
+ return buf;
+}
+
+static void tagFreeFunc(wchar_t *tag, void *p) { if(tag) free(tag); }
+
+static time_t FileTimeToUnixTime(FILETIME *ft)
+{
+ ULARGE_INTEGER end;
+ memcpy(&end,ft,sizeof(end));
+ end.QuadPart -= 116444736000000000;
+ end.QuadPart /= 10000000; // 100ns -> seconds
+ return (time_t)end.QuadPart;
+}
+
+static __int64 FileSize64(HANDLE file)
+{
+ LARGE_INTEGER position;
+ position.QuadPart=0;
+ position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
+
+ if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
+ return INVALID_FILE_SIZE;
+ else
+ return position.QuadPart;
+}
+
+void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time)
+{
+ WIN32_FILE_ATTRIBUTE_DATA file_data;
+ if (GetFileAttributesExW(filename, GetFileExInfoStandard, &file_data) == FALSE)
+ {
+ // GetFileAttributesEx failed. that sucks, let's try something else
+ HANDLE hFile=CreateFileW(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ FILETIME lt;
+ if (GetFileTime(hFile,NULL,NULL,&lt))
+ {
+ *file_time=FileTimeToUnixTime(&lt);
+ }
+ *file_size=FileSize64(hFile);
+ CloseHandle(hFile);
+ }
+ }
+ else
+ {
+ // success
+ *file_time = FileTimeToUnixTime(&file_data.ftLastWriteTime);
+ LARGE_INTEGER size64;
+ size64.LowPart = file_data.nFileSizeLow;
+ size64.HighPart = file_data.nFileSizeHigh;
+ *file_size = size64.QuadPart;
+ }
+}
+
+void getTitle(Device * dev, songid_t song, const wchar_t * filename,wchar_t * buf, int len) {
+ buf[0]=0; buf[len-1]=0;
+ tagItem item = {song,dev,filename};
+ waFormatTitleExtended fmt={filename,0,NULL,&item,buf,len,tagFunc,tagFreeFunc};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+#define atoi_NULLOK(s) ((s)?_wtoi(s):0)
+
+void filenameToItemRecord(wchar_t * file, itemRecordW * ice)
+{
+ int gtrack=0;
+ wchar_t *gartist=NULL,*galbum=NULL,*gtitle=NULL;
+ wchar_t *guessbuf = guessTitles(file,&gtrack,&gartist,&galbum,&gtitle);
+ if(!gartist) gartist=L"";
+ if(!galbum) galbum=L"";
+ if(!gtitle) gtitle=L"";
+
+ wchar_t buf[512]=L"";
+ extendedFileInfoStructW efs={file,NULL,buf,512};
+
+ efs.metadata=L"title"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) { ice->title=_wcsdup(buf); gartist=L""; galbum=L""; gtrack=-1;}
+ else ice->title=_wcsdup(gtitle);
+
+ efs.metadata=L"album";
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) ice->album=_wcsdup(buf);
+ else ice->album=_wcsdup(galbum);
+
+ efs.metadata=L"artist"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) ice->artist=_wcsdup(buf);
+ else ice->artist=_wcsdup(gartist);
+
+ efs.metadata=L"comment"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->comment=_wcsdup(buf);
+
+ efs.metadata=L"genre"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->genre=_wcsdup(buf);
+
+ efs.metadata=L"albumartist"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->albumartist=_wcsdup(buf);
+
+ efs.metadata=L"replaygain_album_gain"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->replaygain_album_gain=_wcsdup(buf);
+
+ efs.metadata=L"replaygain_track_gain"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->replaygain_track_gain=_wcsdup(buf);
+
+ efs.metadata=L"publisher"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->publisher=_wcsdup(buf);
+
+ efs.metadata=L"composer"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->composer=_wcsdup(buf);
+
+ efs.metadata=L"year"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->year=atoi_NULLOK(buf);
+
+ efs.metadata=L"track"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) ice->track=atoi_NULLOK(buf);
+ else ice->track=gtrack;
+
+ efs.metadata=L"tracks"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->tracks=atoi_NULLOK(buf);
+
+ efs.metadata=L"rating"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->rating=atoi_NULLOK(buf);
+
+ __int64 file_size=INVALID_FILE_SIZE;
+ time_t file_time=0;
+ GetFileSizeAndTime(file, &file_size, &file_time);
+ if (!(file_size == INVALID_FILE_SIZE || file_size == 0))
+ {
+ ice->filetime=file_time;
+ // scales to the kb value this uses
+ ice->filesize=(int)(file_size/1024);
+ // and since 5.64+ we can also return this as a true value
+ StringCchPrintf(buf, sizeof(buf), L"%d", file_size);
+ setRecordExtendedItem(ice,L"realsize",buf);
+ }
+
+ ice->lastupd=time(NULL);
+
+ efs.metadata=L"bitrate"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->bitrate=atoi_NULLOK(buf);
+
+ efs.metadata=L"type"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->type=atoi_NULLOK(buf);
+
+ efs.metadata=L"disc"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->disc=atoi_NULLOK(buf);
+
+ efs.metadata=L"discs"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->discs=atoi_NULLOK(buf);
+
+ efs.metadata=L"bpm"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->bpm=atoi_NULLOK(buf);
+
+ basicFileInfoStructW b={efs.filename,0};
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
+ ice->length=b.length;
+
+ // additional fields to match (if available) with a full library
+ // response this is mainly for improving the cloud compatibility
+ efs.metadata=L"lossless"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"lossless",buf);
+
+ efs.metadata=L"director"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"director",buf);
+
+ efs.metadata=L"producer"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"producer",buf);
+
+ efs.metadata=L"width"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"width",buf);
+
+ efs.metadata=L"height"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"height",buf);
+
+ efs.metadata=L"mime"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"mime",buf);
+
+ // Not filled in are: playcount, lastplay
+ ice->filename = _wcsdup(file);
+ free(guessbuf);
+}
+
+void copyTags(itemRecordW * in, wchar_t * out) {
+ // check if the old file still exists - if it does, we will let Winamp copy metadata for us
+ if (wcscmp(in->filename, out) && PathFileExists(in->filename))
+ {
+ copyFileInfoStructW copy;
+ copy.dest = out;
+ copy.source = in->filename;
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&copy, IPC_COPY_EXTENDED_FILE_INFOW) == 0) // 0 means success
+ return;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/metadata_utils.h b/Src/Plugins/Library/ml_pmp/metadata_utils.h
new file mode 100644
index 00000000..7b6ddfc7
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/metadata_utils.h
@@ -0,0 +1,48 @@
+#ifndef __METADATA_UTILS_H_
+#define __METADATA_UTILS_H_
+
+#include "pmp.h"
+
+typedef struct {
+ wchar_t * fn;
+ itemRecordW * ice;
+} filenameMap;
+
+typedef struct {
+ itemRecordW * item;
+ songid_t songid;
+} PlaylistAddItem;
+
+typedef union {
+ struct {
+ wchar_t * filename;
+ itemRecordW * ice;
+ songid_t song;
+ };
+ struct {
+ filenameMap map;
+ songid_t song;
+ };
+ struct {
+ wchar_t * filename;
+ PlaylistAddItem pladd;
+ };
+} songMapping;
+
+void MapItemRecordsToSongs(Device * dev, PlaylistAddItem ** map, int len, C_ItemList * itemRecordsNotOnDevice=NULL);
+void mapFilesToItemRecords(filenameMap ** map0, int len, HWND centerWindow);
+
+void ProcessDatabaseDifferences(Device * dev, itemRecordListW * ml,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML);
+void ProcessDatabaseDifferences(Device * dev, C_ItemList * ml,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML);
+void getTitle(Device * dev, songid_t song, const wchar_t * filename,wchar_t * buf, int len);
+C_ItemList * fileListToItemRecords(wchar_t** files,int l, HWND centerWindow);
+C_ItemList * fileListToItemRecords(C_ItemList * fileList, HWND centerWindow);
+void filenameToItemRecord(wchar_t * file, itemRecordW * ice);
+
+int __fastcall compareSongs(const void *elem1, const void *elem2, const void *context);
+int compareItemRecords(itemRecordW * a, itemRecordW * b);
+int compareItemRecordAndSongId(itemRecordW * item, songid_t song, Device *dev);
+
+void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time);
+
+#endif // __METADATA_UTILS_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ml_pmp.rc b/Src/Plugins/Library/ml_pmp/ml_pmp.rc
new file mode 100644
index 00000000..f249a586
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.rc
@@ -0,0 +1,1228 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource1.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
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_CONFIG DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_TAB1,"SysTabControl32",WS_TABSTOP,0,0,271,246
+END
+
+IDD_CONFIG_MEDIAVIEW DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Media Display Settings",IDC_STATIC,4,3,256,74
+ CONTROL "Remember search filters",IDC_REMEMBER_SEARCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,16,93,10
+ CONTROL "Show Video files in a separate view",IDC_CHECK_VIDEOVIEW,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,30,128,10
+ CONTROL "Simple",IDC_RADIO_FILTERS1,"Button",BS_AUTORADIOBUTTON,10,44,36,10
+ CONTROL "Two Filters",IDC_RADIO_FILTERS2,"Button",BS_AUTORADIOBUTTON,50,44,50,10
+ CONTROL "Three Filters",IDC_RADIO_FILTERS3,"Button",BS_AUTORADIOBUTTON,104,44,55,10
+ LTEXT "Filters",IDC_STATIC,11,59,20,8
+ COMBOBOX IDC_COMBO_FILTER1,35,57,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC,93,59,8,8
+ COMBOBOX IDC_COMBO_FILTER2,98,57,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER3,156,59,8,8
+ COMBOBOX IDC_COMBO_FILTER3,161,57,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+END
+
+IDD_CONFIG_GLOBAL DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Copy to Local Media Filenames",IDC_STATIC,0,0,272,132
+ LTEXT "This determines the path and filename when using the Copy To Local Media feature for copying songs from devices back to your computer's hard drive.",IDC_STATIC,6,12,259,16
+ CONTROL "Use CD Rip Settings",IDC_CHECK_USECDRIP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,34,192,10
+ LTEXT "Specify the destination folder for copied tracks:",IDC_STATIC_1,6,47,204,8
+ EDITTEXT IDC_DESTPATH,6,58,214,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BUTTON1,221,58,45,13
+ LTEXT "Specify the naming convention for copied tracks:",IDC_STATIC_2,6,77,203,8
+ EDITTEXT IDC_FILENAMEFMT,6,88,214,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Format Help",IDC_BUTTON2,221,88,45,13
+ LTEXT "",IDC_FMTOUT,8,106,258,20
+END
+
+IDD_VIEW_PMP_VIDEO DIALOGEX 0, 0, 291, 272
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Search:",IDC_SEARCH_TEXT,0,0,26,8
+ EDITTEXT IDC_QUICKSEARCH,29,0,208,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,239,0,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,12,289,247
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+ CONTROL "Sync",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,77,261,34,11
+ CONTROL "AutoFill",IDC_BUTTON_AUTOFILL,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,114,261,40,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,158,262,98,9
+ CONTROL "Eject",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,260,261,30,11
+END
+
+IDD_CUSTCOLUMNS DIALOGEX 0, 0, 342, 154
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Customize columns"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Hidden columns",IDC_STATIC,7,7,126,120
+ LISTBOX IDC_LIST2,13,17,114,104,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Add -->",IDC_BUTTON2,138,41,66,14,WS_DISABLED
+ PUSHBUTTON "<-- Remove",IDC_BUTTON3,138,59,66,14,WS_DISABLED
+ PUSHBUTTON "Restore Defaults",IDC_DEFS,138,107,66,14
+ GROUPBOX "Visible columns",IDC_STATIC,208,7,126,140
+ LISTBOX IDC_LIST1,214,17,114,110,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ DEFPUSHBUTTON "OK",IDOK,7,133,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,62,133,50,14
+ PUSHBUTTON "Move up",IDC_BUTTON4,239,130,43,12,WS_DISABLED
+ PUSHBUTTON "Move down",IDC_BUTTON5,285,130,43,12,WS_DISABLED
+END
+
+IDD_VIEW_PMP_PLAYLIST DIALOGEX 0, 0, 291, 178
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,0,289,155
+ LTEXT "",IDC_STATUS,115,168,142,9,SS_CENTERIMAGE | SS_ENDELLIPSIS
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,167,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,167,44,11
+ CONTROL "Sort",IDC_BUTTON_SORT,"Button",BS_OWNERDRAW | WS_TABSTOP,77,167,34,11
+ CONTROL "Eject",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW,261,167,30,11
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_VIEW_CLOUD_QUEUE$(DISABLED) DIALOGEX 0, 0, 294, 204
+#else
+IDD_VIEW_CLOUD_QUEUE DIALOGEX 0, 0, 294, 204
+#endif
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_TRANSFERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,0,292,191,WS_EX_CLIENTEDGE
+ PUSHBUTTON "Cancel Transfers",IDC_BUTTONCANCELSELECTED,0,193,70,11
+ PUSHBUTTON "Clear Finished",IDC_BUTTON_CLEARFINISHED,73,193,60,11
+ PUSHBUTTON "Remove Selected",IDC_BUTTON_REMOVESELECTED,136,193,72,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_ENDELLIPSIS,247,195,47,9
+ PUSHBUTTON "Retry Selected",IDC_BUTTON_RETRYSELECTED,210,193,34,11
+END
+#endif
+
+IDD_VIEW_PMP_ARTISTALBUM DIALOGEX 0, 0, 291, 272
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Search:",IDC_SEARCH_TEXT,64,1,26,8
+ EDITTEXT IDC_QUICKSEARCH,92,2,145,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,240,1,49,11
+ CONTROL "",IDC_LIST_ARTIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,13,109,83
+ CONTROL "",IDC_VDELIM,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,109,14,6,82
+ CONTROL "",IDC_LIST_ALBUM,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,115,13,95,83
+ CONTROL "",IDC_VDELIM2,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,210,14,6,82
+ CONTROL "",IDC_LIST_ALBUM2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,216,13,75,83
+ CONTROL "",IDC_HDELIM,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,96,291,6
+ LTEXT "Refine:",IDC_REFINE_TEXT,1,104,24,8
+ EDITTEXT IDC_REFINE,29,104,208,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Refine",IDC_BUTTON_CLEARREFINE,"Button",BS_OWNERDRAW | WS_TABSTOP,240,104,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,115,291,144
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,161,262,96,9
+ CONTROL "Sync",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | WS_TABSTOP,79,261,34,11
+ CONTROL "AutoFill",IDC_BUTTON_AUTOFILL,"Button",BS_OWNERDRAW | WS_TABSTOP,117,261,40,11
+ CONTROL "Eject",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,261,261,30,11
+ CONTROL "",IDC_BUTTON_ARTMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,0,1,14,11
+ CONTROL "",IDC_BUTTON_VIEWMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,16,1,20,11
+ CONTROL "",IDC_BUTTON_COLUMNS,"Button",BS_OWNERDRAW | WS_TABSTOP,37,1,20,11
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+END
+
+IDD_VIEW_PMP_DEVICES DIALOGEX 0, 0, 294, 204
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Portable Media Players Currently Connected:",IDC_STATIC,2,1,145,8
+ CONTROL "",IDC_LIST_DEVICES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,11,292,78
+ CONTROL "",IDC_HDELIM,"Static",SS_ETCHEDHORZ | SS_NOTIFY | NOT WS_VISIBLE,0,91,292,1
+ LTEXT "Transfer Queue:",IDC_TQ_STATIC,2,95,54,8
+ CONTROL "",IDC_LIST_TRANSFERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,106,292,85
+ CONTROL "Pause",IDC_BUTTON_PAUSETRANSFERS,"Button",BS_OWNERDRAW | WS_TABSTOP,0,193,35,11
+ CONTROL "Clear Finished",IDC_BUTTON_CLEARFINISHED,"Button",BS_OWNERDRAW | WS_TABSTOP,38,193,54,11
+ CONTROL "Remove Selected",IDC_BUTTON_REMOVESELECTED,"Button",BS_OWNERDRAW | WS_TABSTOP,95,193,64,11
+ LTEXT "",IDC_STATUS,162,195,130,9
+END
+
+IDD_EDIT_INFO DIALOGEX 0, 0, 295, 215
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Edit item info"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Artist",IDC_CHECK_ARTIST,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,8,32,10
+ EDITTEXT IDC_EDIT_ARTIST,52,7,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Title",IDC_CHECK_TITLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,26,29,10
+ EDITTEXT IDC_EDIT_TITLE,52,25,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Album",IDC_CHECK_ALBUM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,44,35,10
+ EDITTEXT IDC_EDIT_ALBUM,52,43,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Track #",IDC_CHECK_TRACK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,62,41,10
+ EDITTEXT IDC_EDIT_TRACK,52,61,54,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Disc #",IDC_CHECK_DISC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,62,36,10
+ EDITTEXT IDC_EDIT_DISC,163,61,54,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Genre",IDC_CHECK_GENRE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,80,35,10
+ EDITTEXT IDC_EDIT_GENRE,52,79,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Year",IDC_CHECK_YEAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,98,31,10
+ EDITTEXT IDC_EDIT_YEAR,52,97,54,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Album Artist",IDC_CHECK_ALBUMARTIST,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,117,54,10
+ EDITTEXT IDC_EDIT_ALBUMARTIST,65,116,142,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Publisher",IDC_CHECK_PUBLISHER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,135,54,10
+ EDITTEXT IDC_EDIT_PUBLISHER,65,133,142,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Composer",IDC_CHECK_COMPOSER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,152,54,10
+ EDITTEXT IDC_EDIT_COMPOSER,65,151,142,13,ES_AUTOHSCROLL | WS_DISABLED
+ DEFPUSHBUTTON "Update",IDOK,184,194,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,238,194,50,14
+ GROUPBOX "A",IDC_STATIC,213,99,75,92
+ CONTROL "",IDC_PICTUREHOLDER,"Static",SS_BITMAP | SS_REALSIZEIMAGE | WS_DISABLED,221,109,60,55
+ PUSHBUTTON "Change...",IDC_ART_CHANGE,217,174,40,12,WS_DISABLED
+ PUSHBUTTON "Clear",IDC_ART_CLEAR,260,174,26,12,WS_DISABLED
+ CTEXT "No image",IDC_ARTINFO,217,165,67,8,WS_DISABLED
+ CONTROL "Album Art",IDC_CHECK_ALBUMART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,219,98,45,10
+END
+
+IDD_PROGRESS DIALOGEX 0, 0, 186, 44
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Dialog"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,7,172,14
+ PUSHBUTTON "Abort",IDC_ABORT,68,27,50,13
+END
+
+IDD_GETTINGMETADATA DIALOGEX 0, 0, 186, 26
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION
+CAPTION "Finding Metadata..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_METADATAPROGRESS,"msctls_progress32",WS_BORDER,7,7,172,12
+END
+
+IDD_FIND DIALOGEX 0, 0, 185, 39
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Find in Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ EDITTEXT IDC_EDIT,5,5,175,12,ES_AUTOHSCROLL
+ DEFPUSHBUTTON "Find",IDOK,76,22,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,130,22,50,13
+END
+
+IDD_SYNC DIALOGEX 0, 0, 361, 217
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Sync"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Leave them",IDC_TRUESYNC_LEAVE,"Button",BS_AUTORADIOBUTTON,7,26,53,10
+ CONTROL "Delete them",IDC_TRUESYNC_DELETE,"Button",BS_AUTORADIOBUTTON,61,26,53,10
+ CONTROL "Copy them to the Library",IDC_TRUESYNC_COPY,"Button",BS_AUTORADIOBUTTON,116,26,136,10
+ CONTROL "Automatically Sync upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,38,131,10
+ PUSHBUTTON "More >>",IDC_MORE,7,51,50,14
+ DEFPUSHBUTTON "OK",IDOK,153,51,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,209,51,50,14
+ LISTBOX IDC_LIST_ADD,11,66,163,105,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Remove Selected",IDC_ADD_REMSEL,11,175,61,14
+ PUSHBUTTON "Crop to Selected",IDC_ADD_CROPSEL,77,175,61,14
+ LISTBOX IDC_LIST_REMOVE,186,66,163,105,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Remove Selected",IDC_REM_REMSEL,186,175,61,14
+ PUSHBUTTON "Crop to Selected",IDC_REM_CROPSEL,252,175,61,14
+ PUSHBUTTON "Less <<",IDC_LESS,7,196,50,14
+ DEFPUSHBUTTON "OK",IDOK2,248,196,50,14
+ PUSHBUTTON "Cancel",IDCANCEL2,304,196,50,14
+ LTEXT "%d songs will be transferred to the device. \nThere are %d songs on the device which are not in the Local Media Library. ",1020,7,7,250,18
+ GROUPBOX "Songs to be transferred",IDC_ADDLABEL,7,55,172,138,0,WS_EX_TRANSPARENT
+ GROUPBOX "Songs to be deleted",IDC_REMOVELABEL,182,55,172,138,0,WS_EX_TRANSPARENT
+END
+
+IDD_CONFIG_TRANSCODE DIALOGEX 0, 0, 260, 227
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Transcoding Formats",IDC_STATIC,4,3,255,49
+ CONTROL "Enable Transcoding of incompatible tracks",IDC_ENABLETRANSCODER,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,16,149,10
+ PUSHBUTTON "Advanced Settings",IDC_ADVANCED,178,14,75,13
+ LTEXT "Format:",IDC_STATIC,11,35,25,8
+ COMBOBOX IDC_ENCFORMAT,40,33,213,116,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_ENC_CONFIG,"Static",SS_BLACKRECT | NOT WS_VISIBLE,4,56,255,167
+END
+
+IDD_CONFIG_PLUGINS DIALOGEX 0, 0, 272, 246
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Portable Device plug-ins",IDC_STATIC,0,0,272,246
+ LTEXT "Installed Portable Device plug-ins (loaded at startup):",IDC_STATIC,6,12,253,8
+ LISTBOX IDC_PLUGINSLIST,5,26,259,198,LBS_SORT | LBS_USETABSTOPS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "&Configure selected plug-in",IDC_CONFIGPLUGIN,5,228,96,13,WS_DISABLED
+ PUSHBUTTON "Uninstall selected plug-in",IDC_UNINSTALLPLUGIN,105,228,90,13,WS_DISABLED
+ CONTROL "Get plug-ins",IDC_PLUGINVERS,"Button",BS_OWNERDRAW | WS_TABSTOP,224,230,41,9
+END
+
+IDD_CONFIG_TRANSCODING_ADVANCED DIALOGEX 0, 0, 263, 56
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Advanced Transcoding Settings"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Force transcoding of compatible tracks if bitrate is over",IDC_CHECK_FORCE,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,6,190,10
+ EDITTEXT IDC_FORCE_BITRATE,196,4,40,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "kbps",IDC_STATIC,240,6,16,10,SS_CENTERIMAGE
+ CONTROL "Force transcoding of compatible lossless tracks",IDC_CHECK_FORCE_LOSSLESS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,21,190,10
+ DEFPUSHBUTTON "OK",IDOK,155,39,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,209,39,50,13
+END
+
+IDD_AUTOFILL DIALOGEX 0, 0, 361, 199
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "AutoFill"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "This will add %d tracks and delete %d tracks.\nContinue?",1020,7,7,154,18
+ CONTROL "AutoFill my device immediately upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,24,168,10
+ PUSHBUTTON "More >>",IDC_MORE,7,38,50,14
+ DEFPUSHBUTTON "OK",IDOK,63,38,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,119,38,50,14
+ GROUPBOX "Songs to be deleted",IDC_REMOVELABEL,182,38,172,138,0,WS_EX_TRANSPARENT
+ GROUPBOX "Songs to be transferred",IDC_ADDLABEL,7,38,172,138,0,WS_EX_TRANSPARENT
+ LISTBOX IDC_LIST_ADD,11,49,163,122,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ LISTBOX IDC_LIST_REMOVE,186,49,163,122,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Less <<",IDC_LESS,7,179,50,14
+ DEFPUSHBUTTON "OK",IDOK2,248,179,50,14
+ PUSHBUTTON "Cancel",IDCANCEL2,304,179,50,14
+END
+
+IDD_CONFIG_SYNC DIALOGEX 0, 0, 260, 228
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Here you can configure what is transferred when you hit the ""Sync"" button. Podcasts will be synchronised based on the options on the 'Podcast Sync' tab.",IDC_STATIC,4,3,251,17
+ GROUPBOX "Playlist Sync",IDC_STATIC,4,24,254,100
+ CONTROL "Update selected playlists only",IDC_PL_WHITELIST,"Button",BS_AUTORADIOBUTTON,11,36,111,8
+ CONTROL "Update all playlists except those selected",IDC_PL_BLACKLIST,
+ "Button",BS_AUTORADIOBUTTON,11,47,148,8
+ CONTROL "",IDC_PL_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,11,60,240,58
+ GROUPBOX "Library Sync",IDC_STATIC,4,128,254,54
+ CONTROL "Update all songs from my Local Media Library",IDC_LIBRARYSYNC,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,139,157,10
+ LTEXT "Use the following query to specify media types which should be included:",IDC_STATIC,11,152,238,8
+ EDITTEXT IDC_SYNC_QUERY_STRING,11,164,187,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit Query...",IDC_SYNC_QUERY_EDIT,199,164,53,12
+ GROUPBOX "Auto-Sync",IDC_STATIC,4,186,254,40
+ CONTROL "Automatically Sync my device upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,198,165,10
+ CTEXT "if I haven't done a Sync in",IDC_STATIC,23,210,85,10,SS_CENTERIMAGE
+ EDITTEXT IDC_SYNCONCONNECT_TIME,111,209,27,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "hours",IDC_STATIC,143,210,19,10,SS_CENTERIMAGE
+END
+
+IDD_CONFIG_AUTOFILL DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Here you can configure what is transferred when you hit the ""AutoFill"" button.",IDC_STATIC,4,3,253,8
+ GROUPBOX "AutoFill Settings",IDC_STATIC,4,14,256,85
+ CONTROL "Make higher rated songs more likely to be included in the AutoFill",IDC_BOOSTRATINGS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,27,239,10
+ LTEXT "Aim to AutoFill the device 90% full",IDC_FILLCAPTION,11,41,112,8
+ CONTROL "",IDC_SPACESLIDER,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,125,38,100,12
+ LTEXT "Use this query to fine-tune what songs should be included in the AutoFill",IDC_STATIC,11,56,242,8
+ EDITTEXT IDC_AUTOFILL_QUERY_STRING,11,68,195,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit Query",IDC_AUTOFILL_QUERY_EDIT,207,68,44,12
+ CONTROL "AutoFill full albums only",IDC_AUTOFILLALBUMS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,84,100,10
+ GROUPBOX "Super AutoFill",IDC_STATIC,4,103,256,41
+ CONTROL "AutoFill my device immediately upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,115,167,10
+ CTEXT "if I haven't done an AutoFill in",IDC_STATIC,22,128,98,10,SS_CENTERIMAGE
+ EDITTEXT IDC_SYNCONCONNECT_TIME,123,126,27,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "hours",IDC_STATIC,155,126,19,12,SS_CENTERIMAGE
+ LTEXT "AutoFill will select enough songs to fit on your device based on rating and when they were last played. It is perfect for devices with low capactiy (eg, 2GB or less). Unlike Sync, it will select a different set of songs each time.",IDC_STATIC,4,148,250,25
+END
+
+IDD_CONFIG_PODCAST_SYNC DIALOGEX 0, 0, 264, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Here you can configure what podcasts will be transferred when using the ""Sync"" button. See the 'Sync' tab for other options when synchronising device media.",-1,4,3,258,17
+ GROUPBOX "Podcast Sync Settings",IDC_STATIC_PODCASTS,4,24,258,157
+ CONTROL "Sync",IDC_CHECK_PC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,37,28,10,WS_EX_TRANSPARENT
+ COMBOBOX IDC_COMBO_PC_NUM,43,36,32,107,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "most recent episodes of:",IDC_STATIC_PODTXT,79,38,80,8
+ CONTROL "All podcasts",IDC_PC_ALL,"Button",BS_AUTORADIOBUTTON | WS_GROUP,11,52,54,8
+ CONTROL "Selected podcasts:",IDC_PC_SEL,"Button",BS_AUTORADIOBUTTON,11,64,77,8
+ LTEXT "Tip: For best results, set your podcasts to update regularly and download automatically.",IDC_STATIC_PODTIP,11,76,84,42,0,WS_EX_TRANSPARENT
+ CONTROL "",IDC_PC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,99,62,156,112
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_VIEW_CLOUD_ARTISTALBUM$(DISABLED) DIALOGEX 0, 0, 291, 272
+#else
+IDD_VIEW_CLOUD_ARTISTALBUM DIALOGEX 0, 0, 291, 272
+#endif
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_HEADER_DEVICE_ICON,"Static",SS_BITMAP | SS_CENTERIMAGE | NOT WS_VISIBLE,0,0,20,20
+ LTEXT "",IDC_HEADER_DEVICE_NAME,0,0,70,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HEADER_DEVICE_BAR,"msctls_progress32",NOT WS_VISIBLE,75,0,110,8
+ LTEXT "",IDC_HEADER_DEVICE_SIZE,203,0,60,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HDELIM2,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,10,291,6
+ CONTROL "",IDC_HEADER_DEVICE_TRANSFER,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,269,0,22,11
+ CONTROL "",IDC_BUTTON_ARTMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,0,12,14,11
+ CONTROL "",IDC_BUTTON_VIEWMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,16,12,20,11
+ CONTROL "",IDC_BUTTON_COLUMNS,"Button",BS_OWNERDRAW | WS_TABSTOP,37,12,20,11
+ LTEXT "Search:",IDC_SEARCH_TEXT,64,12,26,8,SS_NOTIFY
+ EDITTEXT IDC_QUICKSEARCH,92,13,145,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,240,12,49,11
+ CONTROL "",IDC_LIST_ARTIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,24,109,71
+ CONTROL "",IDC_VDELIM,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,109,25,6,71
+ CONTROL "",IDC_LIST_ALBUM,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,115,24,95,71
+ CONTROL "",IDC_VDELIM2,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,210,25,6,71
+ CONTROL "",IDC_LIST_ALBUM2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,216,25,75,71
+ CONTROL "",IDC_HDELIM,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,96,291,6
+ LTEXT "Refine:",IDC_REFINE_TEXT,1,104,24,8
+ EDITTEXT IDC_REFINE,29,104,208,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Refine",IDC_BUTTON_CLEARREFINE,"Button",BS_OWNERDRAW | WS_TABSTOP,240,104,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,115,291,146
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+ CONTROL "Transfer",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | WS_TABSTOP,79,261,42,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,125,262,122,9
+ CONTROL "Remove",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,251,261,40,11
+END
+#endif
+
+IDD_CLOUD_SYNC DIALOGEX 0, 0, 379, 215
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Cloud Transfer"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Transfer songs to the device by:",IDC_STATIC,5,7,106,8
+ COMBOBOX IDC_TX_MODE,115,5,72,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "Songs to be transferred",IDC_ADDLABEL,5,19,182,191,0,WS_EX_TRANSPARENT
+ CONTROL "Transfer all playlists",IDC_CLOUDSYNC_ALL,"Button",BS_AUTORADIOBUTTON | WS_GROUP,11,32,79,10
+ CONTROL "Transfer selected playlists only",IDC_CLOUDSYNC_SEL,
+ "Button",BS_AUTORADIOBUTTON,11,44,115,10
+ CONTROL "Transfer all playlists except those selected",IDC_CLOUDSYNC_NOTSEL,
+ "Button",BS_AUTORADIOBUTTON,11,56,152,10
+ LISTBOX IDC_LIST_ADD_PL,11,70,170,134,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_HSCROLL | WS_GROUP | WS_TABSTOP
+ LISTBOX IDC_LIST_ADD,11,70,170,134,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_HSCROLL | WS_GROUP | WS_TABSTOP
+ GROUPBOX "Transfer Summary",IDC_DETAILSLABEL,193,5,182,172,0,WS_EX_TRANSPARENT
+ LTEXT "",IDC_DETAILS,199,16,170,154
+ DEFPUSHBUTTON "Begin Transfer",IDOK,253,196,68,14
+ PUSHBUTTON "Cancel",IDCANCEL,325,196,50,14
+END
+
+IDD_CONFIG_CLOUD_MEDIAVIEW DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Cloud Source Display Settings",IDC_STATIC,4,3,256,62
+ CONTROL "Remember search filters",IDC_REMEMBER_SEARCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,16,93,10
+ CONTROL "Simple",IDC_RADIO_FILTERS1,"Button",BS_AUTORADIOBUTTON,10,32,36,10
+ CONTROL "Two Filters",IDC_RADIO_FILTERS2,"Button",BS_AUTORADIOBUTTON,50,32,50,10
+ CONTROL "Three Filters",IDC_RADIO_FILTERS3,"Button",BS_AUTORADIOBUTTON,104,32,55,10
+ LTEXT "Filters",IDC_STATIC,11,47,20,8
+ COMBOBOX IDC_COMBO_FILTER1,35,45,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER2,93,47,8,8
+ COMBOBOX IDC_COMBO_FILTER2,98,45,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER3,156,47,8,8
+ COMBOBOX IDC_COMBO_FILTER3,161,45,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_VIEW_CLOUD_SIMPLE$(DISABLED) DIALOGEX 0, 0, 291, 272
+#else
+IDD_VIEW_CLOUD_SIMPLE DIALOGEX 0, 0, 291, 272
+#endif
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_HEADER_DEVICE_ICON,"Static",SS_BITMAP | SS_CENTERIMAGE | NOT WS_VISIBLE,0,0,20,20
+ LTEXT "",IDC_HEADER_DEVICE_NAME,34,0,70,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HEADER_DEVICE_BAR,"msctls_progress32",NOT WS_VISIBLE,112,0,110,8
+ LTEXT "",IDC_HEADER_DEVICE_SIZE,203,0,60,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HEADER_DEVICE_TRANSFER,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,269,0,22,11
+ CONTROL "",IDC_HDELIM2,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,9,291,6
+ LTEXT "Search:",IDC_SEARCH_TEXT,0,13,26,8,SS_NOTIFY
+ EDITTEXT IDC_QUICKSEARCH,29,13,208,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,239,13,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,28,289,231
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+ CONTROL "Transfer",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,77,261,42,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,123,262,124,9
+ CONTROL "Remove",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,251,261,40,11
+END
+#endif
+
+IDD_VIEW_PMP_QUEUE DIALOGEX 0, 0, 294, 204
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_TRANSFERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,0,292,191,WS_EX_CLIENTEDGE
+ PUSHBUTTON "Pause",IDC_BUTTON_PAUSETRANSFERS,0,193,38,11
+ PUSHBUTTON "Clear Finished",IDC_BUTTON_CLEARFINISHED,41,193,60,11
+ PUSHBUTTON "Remove Selected",IDC_BUTTON_REMOVESELECTED,104,193,72,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_ENDELLIPSIS,180,195,112,9
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_CONFIG, DIALOG
+ BEGIN
+ RIGHTMARGIN, 265
+ BOTTOMMARGIN, 240
+ END
+
+ IDD_CUSTCOLUMNS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 335
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 147
+ END
+
+ IDD_VIEW_PMP_PLAYLIST, DIALOG
+ BEGIN
+ RIGHTMARGIN, 289
+ END
+
+ IDD_VIEW_PMP_DEVICES, DIALOG
+ BEGIN
+ RIGHTMARGIN, 292
+ END
+
+ IDD_EDIT_INFO, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 288
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 208
+ END
+
+ IDD_PROGRESS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 40
+ END
+
+ IDD_GETTINGMETADATA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 19
+ END
+
+ IDD_FIND, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 180
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 35
+ END
+
+ IDD_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 354
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 210
+ END
+
+ IDD_CONFIG_TRANSCODE, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 226
+ END
+
+ IDD_CONFIG_TRANSCODING_ADVANCED, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 259
+ TOPMARGIN, 3
+ BOTTOMMARGIN, 52
+ END
+
+ IDD_AUTOFILL, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 354
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 192
+ END
+
+ IDD_CONFIG_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 258
+ END
+
+ IDD_CONFIG_AUTOFILL, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ END
+
+ IDD_CONFIG_PODCAST_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 262
+ BOTTOMMARGIN, 222
+ END
+
+ IDD_CLOUD_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 375
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 210
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource1.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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// PNG
+//
+
+IDB_XFER_QUEUE_16 PNG ".\\resources\\transfer_queue_16x16.png"
+IDB_USB PNG ".\\resources\\usb.png"
+IDR_IMAGE_NOTFOUND PNG ".\\resources\\notfound.png"
+IDB_XFER_QUEUE_16_3 PNG ".\\resources\\transfer_queue_16x16_3.png"
+IDB_XFER_QUEUE_16_2 PNG ".\\resources\\transfer_queue_16x16_2.png"
+IDB_XFER_QUEUE_16_1 PNG ".\\resources\\transfer_queue_16x16_1.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "TracksList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select &all\tCtrl+A", ID_TRACKSLIST_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &metadata...\tCtrl+E", ID_TRACKSLIST_EDITSELECTEDITEMS
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete...\tDelete", ID_TRACKSLIST_DELETE
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TRACKSLIST_COPYTOLIBRARY
+ END
+ POPUP "ArtistAlbumList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete...\tDelete", ID_TRACKSLIST_DELETE
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TRACKSLIST_COPYTOLIBRARY
+ END
+ POPUP "PlayList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select &all\tCtrl+A", ID_TRACKSLIST_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &metadata...\tCtrl+E", ID_TRACKSLIST_EDITSELECTEDITEMS
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete...\tDelete", ID_TRACKSLIST_DELETE
+ MENUITEM "Rem&ove from playlist\tShift+Delete", ID_TRACKSLIST_REMOVEFROMPLAYLIST
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TRACKSLIST_COPYTOLIBRARY
+ END
+ POPUP "TreeDevice"
+ BEGIN
+ MENUITEM "&New Playlist...", ID_TREEDEVICE_NEWPLAYLIST
+ MENUITEM SEPARATOR
+ MENUITEM "&Eject Device", ID_TREEDEVICE_EJECTDEVICE
+ END
+ POPUP "TreePlaylist"
+ BEGIN
+ MENUITEM "&Rename\tF2", ID_TREEPLAYLIST_RENAMEPLAYLIST
+ MENUITEM "&Delete\tDelete", ID_TREEPLAYLIST_REMOVEPLAYLIST
+ MENUITEM "Delete with &files", ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA
+ END
+ POPUP "SortPlaylist"
+ BEGIN
+ MENUITEM "Artist", ID_SORTPLAYLIST_ARTIST
+ MENUITEM "Album", ID_SORTPLAYLIST_ALBUM
+ MENUITEM "Title", ID_SORTPLAYLIST_TITLE
+ MENUITEM "Track", ID_SORTPLAYLIST_TRACK
+ MENUITEM "Disc", ID_SORTPLAYLIST_DISC
+ MENUITEM "Genre", ID_SORTPLAYLIST_GENRE
+ MENUITEM "Rating", ID_SORTPLAYLIST_RATING
+ MENUITEM "Play Count", ID_SORTPLAYLIST_PLAYCOUNT
+ MENUITEM "Last Played", ID_SORTPLAYLIST_LASTPLAYED
+ MENUITEM SEPARATOR
+ MENUITEM "Randomize", ID_SORTPLAYLIST_RANDOMIZE
+ MENUITEM SEPARATOR
+ MENUITEM "Reverse Playlist", ID_SORTPLAYLIST_REVERSEPLAYLIST
+ END
+ POPUP "MainTreeRoot"
+ BEGIN
+ MENUITEM "H&ide this icon when no portables are attached", ID_MAINTREEROOT_AUTOHIDEROOT
+ MENUITEM SEPARATOR
+ MENUITEM "&Preferences", ID_MAINTREEROOT_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "&Help", ID_MAINTREEROOT_HELP
+ END
+ POPUP "TransfersList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &metadata...\tCtrl+E", ID_TRACKSLIST_EDITSELECTEDITEMS
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete\tDelete...", ID_TRACKSLIST_DELETE
+ END
+ POPUP "HeaderWnd"
+ BEGIN
+ MENUITEM "&Customize columns...", 40045
+ END
+ POPUP "FilterHeaderWnd"
+ BEGIN
+ MENUITEM "&Customize columns...", 40045
+ MENUITEM "Show &Horizontal Scrollbar", ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR
+ END
+ POPUP "ArtHeaderMenu"
+ BEGIN
+ MENUITEM "&Small Icon", ID_ARTHEADERMENU_SMALLICON
+ MENUITEM "&Medium Icon", ID_ARTHEADERMENU_MEDIUMICON
+ MENUITEM "&Large Icon", ID_ARTHEADERMENU_LARGEICON
+ MENUITEM SEPARATOR
+ MENUITEM "Sm&all Details", ID_ARTHEADERMENU_SMALLDETAILS
+ MENUITEM "M&edium Details", ID_ARTHEADERMENU_MEDIUMDETAILS
+ MENUITEM "La&rge Details", ID_ARTHEADERMENU_LARGEDETAILS
+ END
+ POPUP "ArtEditMenu"
+ BEGIN
+ MENUITEM "Copy", ID_ARTEDITMENU_COPY
+ MENUITEM "Paste", ID_ARTEDITMENU_PASTE
+ MENUITEM "Delete", ID_ARTEDITMENU_DELETE
+ MENUITEM "Save As...", ID_ARTEDITMENU_SAVEAS
+ MENUITEM "Download...", ID_ARTEDITMENU_DOWNLOAD
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RCDATA
+//
+
+IDR_DEVICE_ICON RCDATA ".\\resources\\deviceIcon.png"
+IDR_PLAYLIST_ICON RCDATA ".\\resources\\playlistIcon.png"
+IDR_VIDEO_ICON RCDATA ".\\resources\\videoIcon.png"
+IDR_TOOL_VIEWMODE_ICON RCDATA ".\\resources\\viewMode.png"
+IDR_TOOL_ALBUMART_ICON RCDATA ".\\resources\\albumArt.png"
+IDR_TOOL_COLUMNS_ICON RCDATA ".\\resources\\columns.png"
+IDR_TRANSFER_SMALL_ICON RCDATA ".\\resources\\sync-command-small.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD$(DISABLED) BITMAP "..\\ml_cloud\\resources\\ti_cloud_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD BITMAP "..\\ml_cloud\\resources\\ti_cloud_16x16x16.bmp"
+#endif
+#endif
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD_ADD_SOURCE$(DISABLED) BITMAP "..\\ml_cloud\\resources\\ti_add_cloud_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD_ADD_SOURCE BITMAP "..\\ml_cloud\\resources\\ti_add_cloud_16x16x16.bmp"
+#endif
+#endif
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD_ADD_BYOS$(DISABLED) BITMAP "..\\ml_cloud\\resources\\ti_byos_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD_ADD_BYOS BITMAP "..\\ml_cloud\\resources\\ti_byos_16x16x16.bmp"
+#endif
+#endif
+
+//
+// Accelerator
+//
+
+IDR_ACCELERATORS ACCELERATORS
+BEGIN
+ "A", ID_TRACKSLIST_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "C", ID_TRACKSLIST_COPYTOLIBRARY, VIRTKEY, CONTROL, NOINVERT
+ "E", ID_TRACKSLIST_EDITSELECTEDITEMS, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_TRACKSLIST_DELETE, VIRTKEY, NOINVERT
+ VK_DELETE, ID_TRACKSLIST_REMOVEFROMPLAYLIST, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_TRACKSLIST_PLAYSELECTION, VIRTKEY, NOINVERT
+ VK_RETURN, IDC_BUTTON_PLAY, VIRTKEY, NOINVERT
+ VK_RETURN, ID_TRACKSLIST_ENQUEUESELECTION, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_PMP_SUPPORT "Nullsoft Portable Music Player Support v%s"
+ 65535 "{04C986EE-9CE3-4369-820D-A64394C63D60}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_VIDEO "Video"
+ IDS_RENAME_DEVICE "Rename Device"
+ IDS_LAST_CHANGED "Last Changed"
+ IDS_DEVICE_OUT_OF_SPACE "Not all of these tracks can be transferred, the device is out of space"
+ IDS_INCOMPATABLE_FORMAT_NO_TX
+ "Some of these tracks were of an incompatible format and will not be transferred"
+ IDS_ERROR "Error"
+ IDS_PHYSICALLY_REMOVE_X_TRACKS
+ "This will physically remove these %d tracks from this device.\nProceed?"
+ IDS_ARE_YOU_SURE "Are you sure?"
+ IDS_SYNC_IS_IN_PROGRESS "Synchronization is in progress!"
+ IDS_CANNOT_EJECT "Cannot Eject"
+ IDS_DELETING_TRACKS "Deleting Tracks..."
+ IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING
+ "You cannot remove tracks while transfers are in progress!"
+ IDS_SORRY "Sorry"
+ IDS_NATS_DEVICE_MAYBE_FULL
+ "Unfortunately not all of the tracks from the playlist could be transferred.\nThe device may be full."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT
+ "Unfortunately not all of the tracks from the playlist could be transferred.\nSome tracks are of an incompatible format."
+ IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT
+ "Unfortunately not all of the tracks from the playlist could be transferred.\nThe device may be full and some tracks are of an incompatible format."
+ IDS_WARNING "Warning"
+ IDS_SONGS_TO_BE_DELETED "Songs to be deleted"
+ IDS_SONGS_TO_BE_COPIED "Songs to be copied"
+ IDS_SONGS_NOT_IN_MEDIA_LIBRARY "Songs not in Library"
+ IDS_WAITING "Waiting"
+ IDS_PLAYLIST_SYNCRONIZATION "Playlist Synchronization"
+ IDS_OTHER "Other"
+ IDS_WORKING "Working..."
+ IDS_DONE "Done"
+ IDS_NOTHING_TO_SYNC_UP_TO_DATE
+ "Nothing to sync, the device is up to date."
+ IDS_SYNC "Sync"
+ IDS_X_SONGS_WILL_BE_TRANSFERRED_TO_THE_DEVICE
+ "%d songs will be transferred to the device.\nThere are %d songs on the device which are not in the Library."
+ IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE
+ "There is not enough space on this device to sync selected media.\r\n\r\nTry the AutoFill option to sync media with the available capacity on your device."
+ IDS_NOT_ENOUGH_SPACE "Limited Device Capacity"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_THIS_WILL_ADD_X_SONGS_AND_DELETE_X_SONGS
+ "This will add %d songs and delete %d songs.\nContinue?"
+ IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK
+ "This device does not support direct playback."
+ IDS_UNSUPPORTED "Unsupported"
+ IDS_PREFS_SYNC "Sync"
+ IDS_PREFS_AUTOFILL "AutoFill"
+ IDS_PREFS_TRANSCODING "Transcoding"
+ IDS_PREFS_VIEW "View"
+ IDS_EXAMPLE_FORMATTING_STRING "Example copied file filename: \n"
+ IDS_CHOOSE_A_FOLDER "Choose a folder"
+ IDS_COPIED_FILE_FORMAT_INFO
+ "You may enter a filename format string for your ripped files.\nIt can contain \\ or / to delimit a path, and the following keywords:\n\n <Artist> - inserts the album artist with the default capitalization\n <ARTIST> - inserts the album artist in all uppercase\n <artist> - inserts the album artist in all lowercase\n <Trackartist>/<TRACKARTIST>/<trackartist> - inserts the track artist\n <Album>/<ALBUM>/<album> - inserts the album\n <year> - inserts the album year\n <Genre>/<GENRE>/<genre> - inserts the album genre\n <Title>/<TITLE>/<title> - inserts the track title\n #, ##, or ### - inserts the track number, with leading 0s if ## or ###\n <filename> - uses the old filename of the file (not the full path)\n <disc> - inserts the disc number\n\n For Example: E:\\Music\\<Artist>\\<Album>\\## - <Title>\n"
+ IDS_COPIED_FILE_FORMAT_HELP "Copied Filename Format Help"
+ IDS_ALL "All"
+ IDS_AIM_TO_AUTOFILL_DEVICE "Aim to Autofill the device %d%% full"
+ IDS_ARTIST "Artist"
+ IDS_ALBUM "Album"
+ IDS_GENRE "Genre"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_YEAR "Year"
+ IDS_ALBUM_ARTIST "Album Artist"
+ IDS_PUBLISHER "Publisher"
+ IDS_COMPOSER "Composer"
+ IDS_ARTIST_INDEX "Artist Index"
+ IDS_ALBUM_ARTIST_INDEX "Album Artist Index"
+ IDS_PLUGIN_HAS_NO_CONFIG_IMPLEMENTED
+ "This plug-in has no configuration implemented"
+ IDS_DEVICE_PLUGINS "Device Plug-ins"
+ IDS_PERMANENTLY_UNINSTALL_THIS_PLUGIN
+ "Permanently uninstall this plug-in?\n(This may require a restart of Winamp)"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_CLICK_OK_TO_CONTINUE "Click Ok to continue"
+ IDS_RELOADING_PLUGIN "Reloading plug-in"
+ IDS_UNKNOWN_ARTIST "Unknown Artist"
+ IDS_UNKKNOWN_ALBUM "Unknown Album"
+ IDS_UNKNOWN "Unknown"
+ IDS_TRANSFER "Transfer"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COPY_TO_LIBRARY "Copy to Library"
+ IDS_INVALID_PATH "Invalid Path"
+ IDS_STARTING_TRANSFER "Starting transfer..."
+ IDS_DUPLICATE "Duplicate"
+ IDS_TITLE "Title"
+ IDS_LENGTH "Length"
+ IDS_TRACK_NUMBER "Track #"
+ IDS_DISC "Disc"
+ IDS_BITRATE "Bitrate"
+ IDS_SIZE "Size"
+ IDS_PLAY_COUNT "Play Count"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RATING "Rating"
+ IDS_LAST_PLAYED "Last Played"
+ IDS_TRACKS "Tracks"
+ IDS_ARTISTS "Artists"
+ IDS_NO_ARTIST "(no artist)"
+ IDS_ALBUMS "Albums"
+ IDS_GENRES "Genres"
+ IDS_ARTIST_INDEXES "Artist Indexes"
+ IDS_NO_ALBUM_ARTIST "(no album artist)"
+ IDS_ALBUM_ARTIST_INDEXES "Album Artist Indexes"
+ IDS_NO_YEAR "(no year)"
+ IDS_YEARS "Years"
+ IDS_ALBUM_ARTISTS "Album Artists"
+ IDS_NO_PUBLISHER "(no publisher)"
+ IDS_PUBLISHERS "Publishers"
+ IDS_NO_COMPOSER "(no composer)"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COMPOSERS "Composers"
+ IDS_GHK_SYNC_PORTABLE_DEVICE "Portables: Sync Portable Device"
+ IDS_GHK_AUTOFILL_PORTABLE_DEVICE "Portables: AutoFill Portable Device"
+ IDS_GHK_EJECT_PORTABLE_DEVICE "Portables: Eject Portable Device"
+ IDS_PORTABLES "Portables"
+ IDS_PORTABLES_PERCENT "Portables (%d%%)"
+ IDS_LOADING "Loading..."
+ IDS_NAME "Name"
+ IDS_CAPACITY_FREE "Capacity (Free)"
+ IDS_TYPE "Type"
+ IDS_STATUS "Status"
+ IDS_DEVICE "Device"
+ IDS_TRANFERS_PERCENT_REMAINING "%d Transfers (%d%%), %02d:%02d Remaining"
+ IDS_RESUME "Resume"
+ IDS_PAUSE "Pause"
+ IDS_SETTING_METADATA "Setting Metadata..."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_TRANSFER_PERCENT "Transferring %d%%"
+ IDS_ALL_X_WITHOUT_X "All (%d %s, %d without %s)"
+ IDS_ALL_X "All (%d %s)"
+ IDS_NO_ALBUM "(no album)"
+ IDS_NO_GENRE "(no genre)"
+ IDS_TRACK "Track"
+ IDS_X_ITEMS_X_AVAILABLE "%d items %s[%s] [%s available (%d%%) / %s]"
+ IDS_AUTOFILL_QUERY "Autofill Query"
+ IDS_SYNC_QUERY "Sync Query"
+ IDS_ALBUM_ART "Album Art"
+ IDS_NO_IMAGE "No image"
+ IDS_AVAILABLE "available"
+ IDS_OTHER2 "Other..."
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings for this plug-in?"
+ IDS_AUDIO_BUTTON_TT1 "Toggle Album Art View"
+ IDS_AUDIO_BUTTON_TT2 "Select Filters & Panes"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AUDIO_BUTTON_TT3 "Pane Options"
+ IDS_IMAGE_FILES "Image Files"
+ IDS_JPEG_FILE "JPEG File"
+ IDS_PNG_FILE "PNG File"
+ IDS_GIF_FILE "GIF File"
+ IDS_BMP_FILE "BMP File"
+ IDS_DELETE_PLAYLIST "Are you sure you want to delete playlist ""%s"" from the %s?"
+ IDS_NEW_PLAYLIST "Unknown playlist"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_TRANSFERRING "Transferring..."
+ IDS_DEVICE_CMD_PLAYLIST_CREATE "&New Playlist"
+ IDS_DEVICE_CMD_AUTOFILL "&Auto Fill"
+ IDS_DEVICE_CMD_EJECT "&Eject"
+ IDS_TRANSFERS "Transfers"
+ IDS_TRANSFERS_PERCENT "Transfers (%d%%)"
+ IDS_DEVICE_CMD_PREFERENCES "&Preferences"
+ IDS_DEVICE_CMD_VIEW_OPEN "&Open"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEVICE_CMD_VIEW_OPEN_DESC "Show contents of this device."
+ IDS_DEVICE_CMD_SYNC_DESC "Synchronize your Library with this device."
+ IDS_DEVICE_CMD_EJECT_DESC
+ "Save changes to device and prepare for removal."
+ IDS_DEVICE_CMD_RENAME_DESC "Change the name of this device."
+ IDS_DEVICE_CMD_AUTOFILL_DESC "AutoFill this device."
+ IDS_DEVICE_CMD_PLAYLIST_CREATE_DESC "Make a new playlist on this device."
+ IDS_DEVICE_CMD_PREFERENCES_DESC "Change settings for this device."
+ IDS_DEVICE_CMD_RENAME "&Rename\tF2"
+ IDS_DEVICE_CMD_SYNC "&Synchronize"
+ IDS_PORTABLE_DEVICE_TYPE "Portable Media Player"
+ IDS_DEVICE_CONNECTION_USB "USB"
+ IDS_PODCAST_SYNC "Podcast Sync"
+ IDS_TRANSFERRING_DESC "Performing synchronization. Select Transfer view for details."
+ IDS_UNKNOWN_TRACK "Unknown Track"
+ IDS_NUMBER "#"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DELETE_PLAYLIST_TITLE "Delete Playlist"
+ IDS_DEVICE_LOWERCASE "device"
+ IDS_KBPS "%d kbps"
+ IDS_SENDTO_DEVICES "Devices"
+ IDS_SENDTO_CLOUD "Cloud Sources"
+ IDS_MIME_TYPE "Mime"
+ IDS_ALREADY_UPLOADED "Already Uploaded"
+ IDS_SOURCE "Source"
+ IDS_DESTINATION "Destination"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLOUD_SYNC_PL_EMPTY "%s [No transferable files]"
+ IDS_CLOUD_SYNC_PL_SOME "%s [%d files already transferred]"
+ IDS_ALL_TRACKS_PLAYABLE "All tracks playable locally"
+ IDS_NO_TRACKS_PLAYABLE "No tracks playable locally"
+ IDS_SOME_TRACKS_PLAYABLE "Some tracks playable locally"
+ IDS_ALL_TRACKS_PLAYABLE_HERE "All tracks playable from here"
+ IDS_DATE_ADDED "Date Added"
+ IDS_CLIENT_TYPE "Client"
+ IDS_NOT_LOADED "NOT LOADED"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEVICE_CMD_REMOVE "Remove"
+ IDS_DEVICE_CMD_REMOVE_DESC "Remove"
+ IDS_LOCAL_MACHINE "Local Machine"
+ IDS_UPLOAD_CANCELLED "Upload Cancelled"
+ IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME "%d Transfers (%d%%)"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLOUD_SOURCES "Cloud Sources:"
+ IDS_CLOUD "Cloud"
+ IDS_X_ITEMS_X_AVAILABLE_SLIM "%d items %s[%s]"
+ IDS_CLOUD_REMOVE_X_TRACKS
+ "This will remove these %d tracks from this Cloud device only.\nProceed?"
+ IDS_DELETE "Delete...\tDel"
+ IDS_REMOVE "Remove...\tDel"
+ IDS_REMOVING_TRACKS "Removing Tracks..."
+ IDS_DEVICE_CMD_TRANSFER "Transfer"
+ IDS_DEVICE_CMD_TRANSFER_DESC "Transfer"
+ IDS_SOURCE_FILE "Source File"
+ IDS_UPLOADED "Uploaded"
+ IDS_PLAYLISTS "Playlists"
+ IDS_SONGS "Songs"
+ IDS_CLOUD_TX_X_TO_Y "Cloud Transfer: '%s' -> '%s'"
+ IDS_CLOUD_TX_HAVE_SELECTED_X
+ "Transferring from '%s' to '%s'\n\nYou have selected %lld songs (%s) to be transferred to '%s' (%s available space)"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLOUD_TX_OVER_LIMIT "\n\n\nThe current selection puts you %s over your limit on '%s'.\n\nChange the number of selected songs to reduce the number to transfer or consider upgrading or choosing a device with more available space."
+ IDS_CLOUD_TX_NO_SEL "Transferring from '%s' to '%s'\n\nThere are no selected songs to transfer"
+ IDS_CLOUD_LOCAL_LIBRARY "Local Library"
+ IDS_CLOUD_SYNC_ALL_SONGS "Transfer all songs"
+ IDS_CLOUD_SYNC_ALL_PL "Transfer all playlists"
+ IDS_CLOUD_SYNC_SEL_SONGS "Transfer selected songs only"
+ IDS_CLOUD_SYNC_SEL_PL "Transfer selected playlists only"
+ IDS_CLOUD_SYNC_NONSEL_SONGS "Transfer all songs except those selected"
+ IDS_CLOUD_SYNC_NONSEL_PL "Transfer all playlists except those selected"
+ IDS_SEND_TO_PL "&Send to playlist:"
+ IDS_DEVICE_CMD_HIDE "Hide Source"
+ IDS_ADD_SOURCE "Add Source"
+ IDS_TRACK_AVAILABLE "Track available in "
+ IDS_UPLOAD_TO_SOURCE "Upload track to source"
+ IDS_CONFIRM_QUIT "Confirm Quit"
+ IDS_CANCEL_TRANSFERS_AND_QUIT
+ "There are transfers currently in progress.\nAre you sure you want to cancel these transfers and quit?"
+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_pmp/ml_pmp.sln b/Src/Plugins/Library/ml_pmp/ml_pmp.sln
new file mode 100644
index 00000000..c05670fa
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.sln
@@ -0,0 +1,141 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29424.173
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_pmp", "ml_pmp.vcxproj", "{C524F141-87CA-491B-91D8-920248C9066E}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F} = {A929EC04-302E-4B4F-B2A3-65AF63BB088F}
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA} = {977153BF-8420-4C8D-AA25-592FAEDB6CBA}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tataki", "..\tataki\tataki.vcxproj", "{255B68B5-7EF8-45EF-A675-2D6B88147909}"
+ ProjectSection(ProjectDependencies) = postProject
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "elevator", "..\Elevator\elevator.vcxproj", "{977153BF-8420-4C8D-AA25-592FAEDB6CBA}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F} = {A929EC04-302E-4B4F-B2A3-65AF63BB088F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ElevatorPS", "..\Elevator\ElevatorPS.vcxproj", "{A929EC04-302E-4B4F-B2A3-65AF63BB088F}"
+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
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|Win32.Build.0 = Debug|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|x64.ActiveCfg = Debug|x64
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|x64.Build.0 = Debug|x64
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|Win32.ActiveCfg = Release|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|Win32.Build.0 = Release|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|x64.ActiveCfg = Release|x64
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|x64.Build.0 = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.ActiveCfg = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.Build.0 = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.ActiveCfg = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.Build.0 = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.ActiveCfg = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.Build.0 = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.ActiveCfg = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.Build.0 = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.Build.0 = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.Build.0 = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|Win32.ActiveCfg = Debug|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|Win32.Build.0 = Debug|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|x64.ActiveCfg = Debug|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|x64.Build.0 = Debug|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|Win32.ActiveCfg = Release|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|Win32.Build.0 = Release|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|x64.ActiveCfg = Release|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|x64.Build.0 = Release|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|Win32.Build.0 = Debug|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|x64.ActiveCfg = Debug|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|x64.Build.0 = Debug|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|Win32.ActiveCfg = Release|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|Win32.Build.0 = Release|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|x64.ActiveCfg = Release|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C138FA71-FE1F-4840-B678-526B21CE935D}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj
new file mode 100644
index 00000000..3ccbf638
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj
@@ -0,0 +1,444 @@
+<?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>{C524F141-87CA-491B-91D8-920248C9066E}</ProjectGuid>
+ <RootNamespace>ml_pmp</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;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4838;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN64;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4302;4311;4312;4838;4244;4267;4090;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4838;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN64;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;4312;4838;4244;4267;4090;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\replicant\nx\nx.vcxproj">
+ <Project>{57c90706-b25d-4aca-9b33-95cdb2427c27}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\tataki\tataki.vcxproj">
+ <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\itemlist.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\ml_local\guess.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="AlbumArtListView.cpp" />
+ <ClCompile Include="ArtistAlbumLists.cpp" />
+ <ClCompile Include="autofill.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="DeviceCommands.cpp" />
+ <ClCompile Include="DeviceView.cpp" />
+ <ClCompile Include="editinfo.cpp" />
+ <ClCompile Include="Filters.cpp" />
+ <ClCompile Include="graphics.cpp" />
+ <ClCompile Include="IconStore.cpp" />
+ <ClCompile Include="indirectplaybackserver.cpp" />
+ <ClCompile Include="LinkedQueue.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="metadata_utils.cpp" />
+ <ClCompile Include="mt19937ar.cpp" />
+ <ClCompile Include="pluginloader.cpp" />
+ <ClCompile Include="PmpDevice.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="replaceVars.cpp" />
+ <ClCompile Include="SkinnedListView.cpp" />
+ <ClCompile Include="syncCloudDialog.cpp" />
+ <ClCompile Include="syncDialog.cpp" />
+ <ClCompile Include="transcoder_imp.cpp" />
+ <ClCompile Include="transfer_thread.cpp" />
+ <ClCompile Include="view_pmp_devices.cpp" />
+ <ClCompile Include="view_pmp_media.cpp" />
+ <ClCompile Include="view_pmp_queue.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_hotkeys\wa_hotkeys.h" />
+ <ClInclude Include="..\..\General\gen_ml\itemlist.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\General\gen_ml\ml.h" />
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\nu\ServiceWatcher.h" />
+ <ClInclude Include="..\..\..\Winamp\ipc_pe.h" />
+ <ClInclude Include="..\..\..\Winamp\wa_dlg.h" />
+ <ClInclude Include="..\..\..\Winamp\wa_ipc.h" />
+ <ClInclude Include="AlbumArtListView.h" />
+ <ClInclude Include="api__ml_pmp.h" />
+ <ClInclude Include="ArtistAlbumLists.h" />
+ <ClInclude Include="config.h" />
+ <ClInclude Include="DeviceCommands.h" />
+ <ClInclude Include="DeviceView.h" />
+ <ClInclude Include="Filters.h" />
+ <ClInclude Include="graphics.h" />
+ <ClInclude Include="IconStore.h" />
+ <ClInclude Include="LinkedQueue.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="metadata_utils.h" />
+ <ClInclude Include="mt19937ar.h" />
+ <ClInclude Include="pluginloader.h" />
+ <ClInclude Include="pmp.h" />
+ <ClInclude Include="PmpDevice.h" />
+ <ClInclude Include="resource1.h" />
+ <ClInclude Include="SkinnedListView.h" />
+ <ClInclude Include="syncCloudDialog.h" />
+ <ClInclude Include="syncDialog.h" />
+ <ClInclude Include="transcoder.h" />
+ <ClInclude Include="transcoder_imp.h" />
+ <ClInclude Include="transfer_thread.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="..\ml_cloud\resources\ti_add_cloud_16x16x16.bmp" />
+ <Image Include="..\ml_cloud\resources\ti_byos_16x16x16.bmp" />
+ <Image Include="..\ml_cloud\resources\ti_cloud_16x16x16.bmp" />
+ <Image Include="resources\albumArt.png" />
+ <Image Include="resources\columns.png" />
+ <Image Include="resources\deviceIcon.png" />
+ <Image Include="resources\notfound.png" />
+ <Image Include="resources\playlistIcon.png" />
+ <Image Include="resources\sync-command-large.png" />
+ <Image Include="resources\sync-command-small.png" />
+ <Image Include="resources\transfer_queue_16x16.png" />
+ <Image Include="resources\transfer_queue_16x16_1.png" />
+ <Image Include="resources\transfer_queue_16x16_2.png" />
+ <Image Include="resources\transfer_queue_16x16_3.png" />
+ <Image Include="resources\usb.png" />
+ <Image Include="resources\videoIcon.png" />
+ <Image Include="resources\viewMode.png" />
+ <Image Include="transfer_queue_16x16.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_pmp.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+ <ProjectExtensions>
+ <VisualStudio>
+ <UserProperties RESOURCE_FILE="ml_pmp.rc" />
+ </VisualStudio>
+ </ProjectExtensions>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters
new file mode 100644
index 00000000..61358d7b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="AlbumArtListView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ArtistAlbumLists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="autofill.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="DeviceView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="editinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Filters.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="graphics.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ml_local\guess.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="IconStore.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="indirectplaybackserver.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LinkedQueue.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="metadata_utils.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="mt19937ar.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pluginloader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PmpDevice.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="replaceVars.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SkinnedListView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="syncCloudDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="syncDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="transcoder_imp.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="transfer_thread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pmp_devices.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pmp_media.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pmp_queue.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\itemlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="AlbumArtListView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_pmp.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ArtistAlbumLists.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="DeviceView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Filters.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="graphics.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="IconStore.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LinkedQueue.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="metadata_utils.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="mt19937ar.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="pluginloader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="pmp.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PmpDevice.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="transfer_thread.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="transcoder_imp.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="transcoder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="syncDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="syncCloudDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SkinnedListView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource1.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\ipc_pe.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\itemlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\ml.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ServiceWatcher.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\wa_dlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_hotkeys\wa_hotkeys.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\wa_ipc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\columns.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\albumArt.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\deviceIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\notfound.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\playlistIcon.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="..\ml_cloud\resources\ti_add_cloud_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="..\ml_cloud\resources\ti_byos_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="..\ml_cloud\resources\ti_cloud_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="transfer_queue_16x16.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16_1.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16_2.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16_3.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\usb.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\videoIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\viewMode.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{2260cdd9-16ed-4c23-8fb3-a13ecce513d3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{460ed29f-85d9-44b8-964a-8a55c6556b88}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b08c2218-ed5e-46a8-9564-75c07f6f2a85}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{0c486deb-c516-4f17-acd5-94651b7a19cc}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_pmp.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/mt19937ar.cpp b/Src/Plugins/Library/ml_pmp/mt19937ar.cpp
new file mode 100644
index 00000000..7d086488
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/mt19937ar.cpp
@@ -0,0 +1,3 @@
+#include "mt19937ar.h"
+
+int (*genrand_int31)()=0;
diff --git a/Src/Plugins/Library/ml_pmp/mt19937ar.h b/Src/Plugins/Library/ml_pmp/mt19937ar.h
new file mode 100644
index 00000000..175d95e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/mt19937ar.h
@@ -0,0 +1,6 @@
+#ifndef _MT19937AR_H
+#define _MT19937AR_H
+
+extern int (*genrand_int31)();
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/pluginloader.cpp b/Src/Plugins/Library/ml_pmp/pluginloader.cpp
new file mode 100644
index 00000000..6045e995
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/pluginloader.cpp
@@ -0,0 +1,187 @@
+#include "pluginloader.h"
+#include "../winamp/wa_ipc.h"
+#include "nu/AutoWide.h"
+#include <shlwapi.h>
+#include "../nu/MediaLibraryInterface.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+C_ItemList m_plugins;
+
+extern HWND mainMessageWindow;
+
+int wmDeviceChange(WPARAM wParam, LPARAM lParam) {
+ int ret=0;
+ for(int i=0; i < m_plugins.GetSize(); i++) {
+ PMPDevicePlugin * plugin = (PMPDevicePlugin *)m_plugins.Get(i);
+ /*
+ if(plugin->wmDeviceChange)
+ {
+ if(plugin->wmDeviceChange(wParam, lParam) == BROADCAST_QUERY_DENY)
+ ret = BROADCAST_QUERY_DENY;
+ }
+ */
+ if(plugin->MessageProc)
+ {
+ if(plugin->MessageProc(PMP_DEVICECHANGE,wParam,lParam,0) == BROADCAST_QUERY_DENY)
+ ret = BROADCAST_QUERY_DENY;
+ }
+ }
+ return ret;
+}
+
+PMPDevicePlugin * loadPlugin(wchar_t * file)
+{
+ HINSTANCE m=LoadLibrary(file);
+ if(m)
+ {
+ PMPDevicePlugin *(*gp)();
+ gp=(PMPDevicePlugin *(__cdecl *)(void))GetProcAddress(m,"winampGetPMPDevicePlugin");
+ if(!gp)
+ {
+ FreeLibrary(m);
+ return NULL;
+ }
+
+ PMPDevicePlugin *devplugin=gp();
+ if(!devplugin || devplugin->version != PMPHDR_VER)
+ {
+ FreeLibrary(m);
+ return NULL;
+ }
+
+ devplugin->hDllInstance=m;
+ devplugin->hwndLibraryParent=plugin.hwndLibraryParent;
+ devplugin->hwndWinampParent=plugin.hwndWinampParent;
+ devplugin->hwndPortablesParent=mainMessageWindow;
+ devplugin->service = plugin.service;
+
+ if(devplugin->init())
+ {
+ FreeLibrary(m);
+ }
+ else
+ {
+ m_plugins.Add((void *)devplugin);
+ return devplugin;
+ }
+ }
+ return NULL;
+}
+
+BOOL loadDevPlugins(int *count)
+{
+ BOOL loaded = FALSE;
+ wchar_t tofind[MAX_PATH] = {0};
+ LPCWSTR dir = (LPCWSTR)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORYW);
+ PathCombine(tofind, dir, L"pmp_*.dll");
+
+ WIN32_FIND_DATA d = {0};
+ HANDLE h = FindFirstFile(tofind,&d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ wchar_t file[MAX_PATH] = {0};
+ PathCombine(file, dir, d.cFileName);
+ loaded += (!!loadPlugin(file));
+ }
+ while(FindNextFile(h,&d));
+ FindClose(h);
+ }
+
+ if (count) *count = m_plugins.GetSize();
+ return loaded;
+}
+
+BOOL testForDevPlugins()
+{
+ BOOL found = FALSE;
+ wchar_t tofind[MAX_PATH] = {0};
+ LPCWSTR dir = (LPCWSTR)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORYW);
+ PathCombine(tofind, dir, L"pmp_*.dll");
+
+ WIN32_FIND_DATA d = {0};
+ HANDLE h = FindFirstFile(tofind,&d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ found = TRUE;
+ FindClose(h);
+ }
+ return found;
+}
+
+extern int profile;
+static HANDLE hProfile = INVALID_HANDLE_VALUE;
+HANDLE GetProfileFileHandle()
+{
+ if (profile)
+ {
+ if (hProfile == INVALID_HANDLE_VALUE)
+ {
+ wchar_t profileFile[MAX_PATH] = {0};
+ PathCombineW(profileFile, mediaLibrary.GetIniDirectoryW(), L"profile.txt");
+ hProfile = CreateFileW(profileFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ // just to make sure we don't over-write things
+ SetFilePointer(hProfile, NULL, NULL, FILE_END);
+ }
+ }
+ return hProfile;
+ }
+ return INVALID_HANDLE_VALUE;
+}
+
+void unloadPlugin(PMPDevicePlugin *devplugin, int n=-1)
+{
+ if(n == -1) for(int i=0; i<m_plugins.GetSize(); i++) if(m_plugins.Get(i) == (void*)devplugin) n=i;
+ devplugin->quit();
+ //if (devplugin->hDllInstance) FreeLibrary(devplugin->hDllInstance);
+ m_plugins.Del(n);
+}
+
+void unloadDevPlugins()
+{
+ int i=m_plugins.GetSize();
+ HANDLE hProfile = GetProfileFileHandle();
+ LARGE_INTEGER freq;
+ QueryPerformanceFrequency(&freq);
+
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ DWORD written = 0;
+ WriteFile(hProfile, L"\r\n", 2, &written, NULL);
+ }
+
+ while (i-->0) // reverse order to aid in not fucking up subclassing shit
+ {
+ PMPDevicePlugin *devplugin=(PMPDevicePlugin *)m_plugins.Get(i);
+ wchar_t profile[MAX_PATH*2] = {0}, filename[MAX_PATH] = {0};
+ LARGE_INTEGER starttime, endtime;
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ GetModuleFileNameW(devplugin->hDllInstance, filename, MAX_PATH);
+ QueryPerformanceCounter(&starttime);
+ }
+
+ unloadPlugin(devplugin,i);
+
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ QueryPerformanceCounter(&endtime);
+
+ DWORD written = 0;
+ unsigned int ms = (UINT)((endtime.QuadPart - starttime.QuadPart) * 1000 / freq.QuadPart);
+ int len = swprintf(profile, L"Portable\t%s\t[%s]\t%dms\r\n", filename, devplugin->description, ms);
+ WriteFile(hProfile, profile, len*sizeof(wchar_t), &written, NULL);
+ }
+ }
+
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ DWORD written = 0;
+ WriteFile(hProfile, L"\r\n", 2, &written, NULL);
+ CloseHandle(hProfile);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/pluginloader.h b/Src/Plugins/Library/ml_pmp/pluginloader.h
new file mode 100644
index 00000000..efb3cc1e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/pluginloader.h
@@ -0,0 +1,14 @@
+#ifndef __PLUGINLOADER_H_
+#define __PLUGINLOADER_H_
+
+#include <windows.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "pmp.h"
+#include "..\..\General\gen_ml/itemlist.h"
+
+BOOL testForDevPlugins();
+BOOL loadDevPlugins(int *count);
+void unloadDevPlugins();
+int wmDeviceChange(WPARAM wParam, LPARAM lParam);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/pmp.h b/Src/Plugins/Library/ml_pmp/pmp.h
new file mode 100644
index 00000000..1b24036b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/pmp.h
@@ -0,0 +1,321 @@
+#ifndef __PMP_H_
+#define __PMP_H_
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h> // needed for HDC and stuff
+#include <stddef.h>
+#include "..\..\General\gen_ml/ml.h" // for itemRecordW
+// make sure you include ml.h before you include this, wherever you include it.
+
+#ifdef __cplusplus
+class api_service;
+#endif
+
+typedef intptr_t songid_t;
+typedef intptr_t pmpart_t;
+
+// What metadata the device supports
+#define SUPPORTS_ARTIST 0x00000001
+#define SUPPORTS_ALBUM 0x00000002
+#define SUPPORTS_TITLE 0x00000004
+#define SUPPORTS_TRACKNUM 0x00000008
+#define SUPPORTS_DISCNUM 0x00000010
+#define SUPPORTS_GENRE 0x00000020
+#define SUPPORTS_YEAR 0x00000040
+#define SUPPORTS_SIZE 0x00000080
+#define SUPPORTS_LENGTH 0x00000100
+#define SUPPORTS_BITRATE 0x00000200
+#define SUPPORTS_PLAYCOUNT 0x00000400
+#define SUPPORTS_RATING 0x00000800
+#define SUPPORTS_LASTPLAYED 0x00001000
+#define SUPPORTS_LASTUPDATED 0x00002000
+#define SUPPORTS_ALBUMARTIST 0x00004000
+#define SUPPORTS_COMPOSER 0x00008000
+#define SUPPORTS_PUBLISHER 0x00010000
+#define SUPPORTS_ALBUMART 0x00020000
+#define SUPPORTS_MIMETYPE 0x00040000
+#define SUPPORTS_DATEADDED 0x00080000
+
+// constants for sorting playlists
+#define SORTBY_ARTIST 0
+#define SORTBY_ALBUM 1
+#define SORTBY_TITLE 2
+#define SORTBY_TRACKNUM 3
+#define SORTBY_DISCNUM 4
+#define SORTBY_GENRE 5
+#define SORTBY_RATING 6
+#define SORTBY_PLAYCOUNT 7
+#define SORTBY_LASTPLAYED 8
+#define SORTBY_DATEADDED 9
+
+// for get/setTrackExtraInfo, FIELD_* will be passed in as the "field" parameter. check using "if(!wcscmp(field,FIELD_*))" or similar
+#define FIELD_EXTENSION L"ext" // return the file extention, eg L"mp3". Only needed for indirect playback, will never be set.
+
+// firstly a little clarification between what a playlistnumber is and a songid is.
+// playlist numbers are always 0,1,2,...,getPlaylistCount() (thus, if playlist 1 is deleted,
+// the number of all playlists except 0 change)
+// songids are unique identifiers which persist even when other songs are removed.
+// Feel free to use a pointer OR an integer as a songid.
+
+// NOTE: wherever stated, len means number of characters NOT bytes available in the buffer
+
+/* benski> All calls will be made on the 'main thread', with the following exceptions
+transferTrackToDevice() on the transfer thread
+trackRemovedFromTransferQueue() on the transfer thread
+copyToHardDrive() on the transfer thread
+*/
+class Device {
+ protected:
+ Device() {}
+ ~Device() {}
+ public:
+ virtual __int64 getDeviceCapacityAvailable()=0; // in bytes
+ virtual __int64 getDeviceCapacityTotal()=0; // in bytes
+
+ virtual void Eject()=0; // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this;
+ virtual void Close()=0; // save any changes, and call PMP_IPC_DEVICEDISCONNECTED AND delete this;
+
+ // return 0 for success, -1 for failed or cancelled
+ virtual int transferTrackToDevice(const itemRecordW * track, // the track to transfer
+ void * callbackContext, //pass this to the callback
+ void (*callback)(void *callbackContext, wchar_t *status), // call this every so often so the GUI can be updated. Including when finished!
+ songid_t * songid, // fill in the songid when you are finished
+ int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
+ )=0;
+ virtual int trackAddedToTransferQueue(const itemRecordW *track)=0; // return 0 to accept, -1 for "not enough space", -2 for "incorrect format"
+ virtual void trackRemovedFromTransferQueue(const itemRecordW *track)=0;
+
+ // return the amount of space that will be taken up on the device by the track (once it has been tranferred)
+ // or 0 for incompatable. This is usually the filesize, unless you are transcoding. An estimate is acceptable.
+ virtual __int64 getTrackSizeOnDevice(const itemRecordW *track)=0;
+
+ virtual void deleteTrack(songid_t songid)=0; // physically remove from device. Be sure to remove it from all the playlists!
+
+ virtual void commitChanges(){} // optional. Will be called at a good time to save changes
+
+ virtual int getPlaylistCount()=0; // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks.
+ // PlaylistName(0) should return the name of the device.
+ virtual void getPlaylistName(int playlistnumber, wchar_t *buf, int len)=0;
+ virtual int getPlaylistLength(int playlistnumber)=0;
+ virtual songid_t getPlaylistTrack(int playlistnumber,int songnum)=0; // returns a songid
+
+ virtual void setPlaylistName(int playlistnumber, const wchar_t *buf)=0; // with playlistnumber==0, set the name of the device.
+ virtual void playlistSwapItems(int playlistnumber, int posA, int posB)=0; // swap the songs at position posA and posB
+ virtual void sortPlaylist(int playlistnumber, int sortBy)=0;
+ virtual void addTrackToPlaylist(int playlistnumber, songid_t songid)=0; // adds songid to the end of the playlist
+ virtual void removeTrackFromPlaylist(int playlistnumber, int songnum)=0; //where songnum is the position of the track in the playlist
+
+ virtual void deletePlaylist(int playlistnumber)=0;
+ virtual int newPlaylist(const wchar_t *name)=0; // create empty playlist, returns playlistnumber. -1 for failed.
+
+ virtual void getTrackArtist(songid_t songid, wchar_t *buf, int len)=0;
+ virtual void getTrackAlbum(songid_t songid, wchar_t *buf, int len)=0;
+ virtual void getTrackTitle(songid_t songid, wchar_t *buf, int len)=0;
+ virtual int getTrackTrackNum(songid_t songid)=0;
+ virtual int getTrackDiscNum(songid_t songid)=0;
+ virtual void getTrackGenre(songid_t songid, wchar_t * buf, int len)=0;
+ virtual int getTrackYear(songid_t songid)=0;
+ virtual __int64 getTrackSize(songid_t songid)=0; // in bytes
+ virtual int getTrackLength(songid_t songid)=0; // in millisecs
+ virtual int getTrackBitrate(songid_t songid)=0; // in kbps
+ virtual int getTrackPlayCount(songid_t songid)=0;
+ virtual int getTrackRating(songid_t songid)=0; //0-5
+ virtual __time64_t getTrackLastPlayed(songid_t songid)=0; // in unix time format
+ virtual __time64_t getTrackLastUpdated(songid_t songid)=0; // in unix time format
+ virtual void getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len){};
+ virtual void getTrackPublisher(songid_t songid, wchar_t *buf, int len){};
+ virtual void getTrackComposer(songid_t songid, wchar_t *buf, int len){};
+ virtual void getTrackMimeType(songid_t songid, wchar_t *buf, int len){};
+ virtual __time64_t getTrackDateAdded(songid_t songid){ return -1; }; // in unix time format
+ virtual int getTrackType(songid_t songid) { return 0; }
+ virtual void getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len) {}; //optional
+
+ // feel free to ignore any you don't support
+ virtual void setTrackArtist(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackAlbum(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackTitle(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackTrackNum(songid_t songid, int value)=0;
+ virtual void setTrackDiscNum(songid_t songid, int value)=0;
+ virtual void setTrackGenre(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackYear(songid_t songid, int year)=0;
+ virtual void setTrackPlayCount(songid_t songid, int value)=0;
+ virtual void setTrackRating(songid_t songid, int value)=0;
+ virtual void setTrackLastPlayed(songid_t songid, __time64_t value)=0; // in unix time format
+ virtual void setTrackLastUpdated(songid_t songid, __time64_t value)=0; // in unix time format
+ virtual void setTrackAlbumArtist(songid_t songid, const wchar_t *value){};
+ virtual void setTrackPublisher(songid_t songid, const wchar_t *value){};
+ virtual void setTrackComposer(songid_t songid, const wchar_t *value){};
+ virtual void setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value) {}; //optional
+
+ virtual bool playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue)=0; // return false if unsupported
+
+ virtual intptr_t extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4){return 0;}
+
+ virtual bool copyToHardDriveSupported() {return false;}
+
+ virtual __int64 songSizeOnHardDrive(songid_t song) {return -1;} // how big a song will be when copied back. Return -1 for not supported.
+
+ virtual int copyToHardDrive(songid_t song, // the song to copy
+ wchar_t * path, // path to copy to, in the form "c:\directory\song". The directory will already be created, you must append ".mp3" or whatever to this string! (there is space for at least 10 new characters).
+ void * callbackContext, //pass this to the callback
+ void (*callback)(void * callbackContext, wchar_t * status), // call this every so often so the GUI can be updated. Including when finished!
+ int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
+ ) {return -1;} // -1 for failed/not supported. 0 for success.
+
+ // art functions
+ virtual void setArt(songid_t songid, void *buf, int w, int h){} //buf is in format ARGB32*
+ virtual pmpart_t getArt(songid_t songid){return NULL;}
+ virtual void releaseArt(pmpart_t art){}
+ virtual int drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h) {return 0;}
+ virtual void getArtNaturalSize(pmpart_t art, int *w, int *h){*w=*h=0;}
+ virtual void setArtNaturalSize(pmpart_t art, int w, int h){}
+ virtual void getArtData(pmpart_t art, void* data){} // data ARGB32* is at natural size
+ virtual bool artIsEqual(pmpart_t a, pmpart_t b){return false;}
+};
+
+#define PMPHDR_VER 0x10
+/*
+0x10 is for Winamp 5.66+
+- it adds passing a api_service *service
+
+0x9 is for Winamp 5.63+
+- it now requires that plugins handle addTrackToPlaylist(0, ...)
+ so plugins that don't want to directly add to their database via transferTrackToDevice() [which happens off-thread]
+ can do it during addTrackToPlaylist(0, ...) [which happens on the main thread]
+- changes description from char* to wchar_t* (as there's no 3rd party pmp_* we can make such a breaking change)
+*/
+
+// The MessageProc could recieve any of the following..
+#define PMP_DEVICECHANGE 0x100 // param1=WPARAM, param2=LPARAM. See http://msdn.microsoft.com/library/en-us/devio/base/wm_devicechange.asp
+#define PMP_CONFIG 0x101 // param1 will be the parent HWND. return 1 if you implement this
+#define PMP_NO_CONFIG 0x102 // return TRUE to allow the plug-in config button to be disabled as applicable
+
+// use SendMessage(hwndPortablesParent,WM_PMP_IPC,param,PMP_IPC_*); on any of the following
+#define WM_PMP_IPC WM_USER+10
+#define PMP_IPC_DEVICECONNECTED 0x100 // pass a Device *
+#define PMP_IPC_DEVICEDISCONNECTED 0x101 // pass a Device *
+
+#define PMP_IPC_DEVICELOADING 0x102 // pass a pmpDeviceLoading *.
+ // This is optional, call PMP_IPC_DEVICECONNECTED when loading is finished
+ // or PMP_IPC_DEVICEDISCONNECTED to cancel.
+ // while a device is being loaded, DEVICE_SET_ICON will be called, nothing else.
+
+#define PMP_IPC_DEVICENAMECHANGED 0x103 // pass a Device *
+ // added 5.64+
+
+#define PMP_IPC_DEVICECLOUDTRANSFER 0x104 // pass a cloudDeviceTransfer *
+ // added 5.64+
+
+#define PMP_IPC_GETCLOUDTRANSFERS 0x105 // pass a Cloudfiles * (defined as typedef nu::PtrList<wchar_t>)
+ // added 5.64+
+
+typedef struct {
+ wchar_t filenames[MAX_PATH + 1]; // list of filename(s) to transfer to the cloud device
+ void * device_token; // token identifier of the cloud device to transfer to
+} cloudDeviceTransfer; // added 5.64+
+
+typedef struct {
+ Device * dev;
+ // filled in by ml_pmp
+ void (*UpdateCaption)(wchar_t * caption, void * context); // call this with the context to update the caption
+ void * context;
+} pmpDeviceLoading;
+
+typedef struct {
+ HWND parent;
+ const char * dev_name;
+} pmpDevicePrefsView;
+
+#define PMP_IPC_GET_TRANSCODER 0x200 // returns a Transcoder*, pass your Device* (you must have previously called PMP_IPC_DEVICECONNECTED)
+#define PMP_IPC_RELEASE_TRANSCODER 0x201 // pass your Transcoder*, no return value
+#define PMP_IPC_ENUM_ACTIVE_DRIVES 0x300 // pass your function in wParam, or if you pass wParam = 0, it will return a function pointer (type ENUMDRIVES) you can call directly
+typedef void (*ENUM_DRIVES_CALLBACK)(wchar_t, UINT);
+typedef void (*ENUMDRIVES)(ENUM_DRIVES_CALLBACK callback);
+#define PMP_IPC_GET_INI_FILE 0x301 // pass a Device*, returns a wchar_t*
+#define PMP_IPC_GET_PREFS_VIEW 0x302 // pass a pmpDevicePrefsView*, returns a HWND
+
+// The following may be recieved by Device::extraActions (as param1)
+#define DEVICE_SET_ICON 0x0 // param2 is of type MLTREEIMAGE *, modify it to use your own icon for the device in the ML tree
+#define DEVICE_SUPPORTED_METADATA 0x1 // return a load of SUPPORTS_* ORed together. If you return 0, all is assumed.
+#define DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA 0x3 // return 1 if metadata cannot be edited, 0 otherwise.
+#define DEVICE_CAN_RENAME_DEVICE 0x4 // return 1 if the device can be renamed. setPlaylistName(0,name) will be used to rename the device
+#define DEVICE_GET_INI_FILE 0x5 // param2 is wchar_t * of length MAX_PATH. Fill it with the location of the inifile that should be used.
+#define DEVICE_GET_PREFS_DIALOG 0x6 // param2 is a pref_tab*, fill it in if you want to put your own config in. On WM_INITDIALOG lParam will be a prefsParam *
+#define DEVICE_REFRESH 0x7 // F5 was pressed. return 1 to do an in-place update
+#define DEVICE_ADDPODCASTGROUP 0x8 // for ipod. param2=int playlistid, param3=int position, param4=wchar_t* channelname
+#define DEVICE_ADDPODCASTGROUP_FINISH 0x9 // for ipod. param2=int playlistid
+#define DEVICE_SUPPORTS_VIDEO 0xA // return 1 if you support video
+#define DEVICE_DONE_SETTING 0xB // param2=songid_t, tells your plugin that a series of setTrack*() functions are done being called for a particular track
+#define DEVICE_VETO_ENCODER 0xC // param2==fourcc. return 1 to remove the encoder from the transcoding preferences
+#define DEVICE_GET_ICON 0xD // param2=width, param3=height, param4 = wchar_t[260] to put your path into (possibly res:// protocol)
+#define DEVICE_SUPPORTS_PODCASTS 0xE // return 0 if you support podcasts. return 1 if you don't want them transferred
+#define DEVICE_GET_CONNECTION_TYPE 0xF // return 1 if you support. param2 is a const char ** that you should set to a static connect type string.
+#define DEVICE_GET_UNIQUE_ID 0x10 // return 1 if you support. copy a unique name into param2 (char *), param3 will be the allocated size (# of characters)
+#define DEVICE_GET_MODEL 0x11 // return 1 if you support, param2 = (wchar_t*)buffer - model name; param3 = (unsigned int)bufferSize - buffer size; param4 - not used.
+#define DEVICE_SENDTO_UNSUPPORTED 0x12 // return 1 if you don't support send-to
+#define DEVICE_GET_DISPLAY_TYPE 0x13 // return 1 if you support, param2 = (wchar_t*)buffer - model name; param3 = (unsigned int)bufferSize - buffer size; param4 - not used.
+#define DEVICE_VETO_TRANSCODING 0x14 // return 1 if you don't support transcoding and don't want to show the 'Transcoding' preference tab (also see DEVICE_VETO_ENCODER)
+#define DEVICE_GET_PREFS_PARENT 0x15 // return prefsDlgRecW * of the parent preference page you want the device preference page to be a child off. return 0 for default placing
+#define DEVICE_GET_CLOUD_SOURCES_MENU 0x16 // return HMENU if you support cloud source menus and have one to provide (only called as needed). param2=(int*)num_cloud_devices. param4=(int)songid i.e. clicked item or -1 for selection
+#define DEVICE_DO_CLOUD_SOURCES_MENU 0x17 // a cloud sources menu item was clicked. param2=(int)menu_id from the menu provided via DEVICE_GET_CLOUD_SOURCES_MENU.
+ // param3=(int)mode where 0 is via submenu and 1 is the menu only i.e. single-selection.
+ // param4=(CItemList*) of songid_t so it is possible to do multiple selection handling
+ // return 1 if needing the item to be removed from the view
+#define DEVICE_IS_CLOUD_TX_DEVICE 0x18 // param1=(nx_string_t) return 1 if device token matches ours
+#define DEVICE_SYNC_UNSUPPORTED 0x19 // return 1 if you don't support sync
+#define DEVICE_GET_CLOUD_DEVICE_ID 0x20 // return the device id
+#define DEVICE_NOT_READY_TO_VIEW 0x21 // return 1 if view is not ready to be shown
+#define DEVICE_GET_NODE_ICON_ID 0x22 // return the resource id of the node icon
+#define DEVICE_PLAYLISTS_UNSUPPORTED 0x23 // return 1 if you don't support playlists
+#define DEVICE_DOES_NOT_SUPPORT_REMOVE 0x24 // return 1 if you don't support remove / eject
+
+#define CLOUD_SOURCE_MENUS 60000
+#define CLOUD_SOURCE_MENUS_UPPER 60000 + 20
+#define CLOUD_SOURCE_MENUS_PL_UPPER 60000 + 200
+
+typedef struct {
+ wchar_t title[98];
+ int res_id;
+ DLGPROC dlg_proc;
+ HINSTANCE hinst;
+} pref_tab;
+
+typedef struct {
+ HWND parent;
+ Device * dev;
+ void (*config_tab_init)(HWND tab,HWND m_hwndDlg); // call this on WM_INITDIALOG
+} prefsParam;
+
+typedef struct {
+ int version; // should be PMPHDR_VER
+ wchar_t *description; // a textual desciption (including version info)
+ int ( __cdecl *init)(); // called when winamp is loaded, for any one-time init
+ void ( __cdecl *quit)(); // called when winamp is unloaded, for any one-time deinit
+
+ INT_PTR ( __cdecl *MessageProc)(int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+ // All the following data is filled in by ml_pmp
+ HWND hwndWinampParent; // send this any of the WM_WA_IPC messages
+ HWND hwndLibraryParent; // send this any of the WM_ML_IPC messages
+ HWND hwndPortablesParent; // send this any of the WM_PMP_IPC messages
+ HINSTANCE hDllInstance; // this plugins instance
+
+ // filled in by Winamp (added 5.66+ to replace need to call IPC_GET_API_SERVICE on loading)
+ #ifdef __cplusplus
+ api_service *service;
+ #else
+ void * service;
+ #endif
+} PMPDevicePlugin;
+
+// return values from the init(..) which determines if Winamp will continue loading
+// and handling the plugin or if it will disregard the load attempt. If PMP_INIT_FAILURE
+// is returned then the plugin will be listed as [NOT LOADED] on the plug-in prefs page.
+#define PMP_INIT_SUCCESS 0
+#define PMP_INIT_FAILURE 1
+
+// return values from the winampUninstallPlugin(HINSTANCE hdll, HWND parent, int param)
+// which determine if we can uninstall the plugin immediately or on winamp restart
+#define PMP_PLUGIN_UNINSTALL_NOW 0x0
+#define PMP_PLUGIN_UNINSTALL_REBOOT 0x1
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/prefs.cpp b/Src/Plugins/Library/ml_pmp/prefs.cpp
new file mode 100644
index 00000000..65c649e9
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/prefs.cpp
@@ -0,0 +1,1541 @@
+#include "../Winamp/buildType.h"
+#include "api__ml_pmp.h"
+#include "main.h"
+#include "../ml_wire/ifc_podcast.h"
+#include "DeviceView.h"
+#include "nu/AutoWide.h"
+#include "transcoder_imp.h"
+#include "nu/listview.h"
+#include "nu/AutoLock.h"
+#include <Commctrl.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <api/syscb/callbacks/browsercb.h>
+#if defined (_WIN64)
+ #include "../Elevator/IFileTypeRegistrar_64.h"
+#else
+ #include "../Elevator/IFileTypeRegistrar_32.h"
+#endif
+
+extern winampMediaLibraryPlugin plugin;
+extern DeviceView * currentViewedDevice;
+extern C_ItemList m_plugins;
+extern C_Config * global_config;
+extern C_ItemList devices;
+extern HWND hwndMediaView;
+
+static void link_handledraw(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+void link_startsubclass(HWND hwndDlg, UINT id);
+
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_cloud_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_podcast_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_autofill(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_transcode(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_cloud_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+void myOpenURLWithFallback(HWND hwnd, wchar_t *loc, wchar_t *fallbackLoc);
+
+HWND m_hwndTab = NULL, m_hwndTabDisplay = NULL;
+int g_prefs_openpage=0;
+static C_Config * config;
+DeviceView * configDevice;
+
+enum {
+ SYNC_PREF_IDX = 0,
+ PODSYNC_PREF_IDX,
+ AUTOFILL_PREF_IDX,
+ TRANS_PREF_IDX,
+ MEDIAVIEW_PREF_IDX
+};
+static pref_tab tabs[] = {
+ {L"",IDD_CONFIG_SYNC,config_dlgproc_sync,0},
+ {L"",IDD_CONFIG_PODCAST_SYNC,config_dlgproc_podcast_sync,0},
+ {L"",IDD_CONFIG_AUTOFILL,config_dlgproc_autofill,0},
+ {L"",IDD_CONFIG_TRANSCODE,config_dlgproc_transcode,0},
+ {L"",IDD_CONFIG_MEDIAVIEW,config_dlgproc_mediaview,0},
+ {L"",0,0,0}, // extra config for plugins
+};
+
+static void config_tab_init(HWND tab,HWND m_hwndDlg)
+{
+ RECT r;
+ GetWindowRect(m_hwndTab,&r);
+ TabCtrl_AdjustRect(m_hwndTab,FALSE,&r);
+ MapWindowPoints(NULL,m_hwndDlg,(LPPOINT)&r,2);
+ SetWindowPos(tab,HWND_TOP,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOACTIVATE);
+ if(!SendMessage(plugin.hwndWinampParent,WM_WA_IPC,IPC_ISWINTHEMEPRESENT,IPC_USE_UXTHEME_FUNC))
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)tab,IPC_USE_UXTHEME_FUNC);
+}
+
+HWND OnSelChanged(HWND hwndDlg, HWND external = NULL, DeviceView *dev = NULL)
+{
+ static prefsParam p;
+ int sel=(!external ? TabCtrl_GetCurSel(m_hwndTab) : MEDIAVIEW_PREF_IDX);
+ if (!external)
+ {
+ g_prefs_openpage=sel;
+ if(m_hwndTabDisplay!=NULL)
+ DestroyWindow(m_hwndTabDisplay);
+ p.config_tab_init = config_tab_init;
+ p.dev = configDevice->dev;
+ p.parent = hwndDlg;
+
+ // copes with the Transcoding tab being hidden
+ if (sel >= TRANS_PREF_IDX && tabs[TRANS_PREF_IDX].title[0] == 0) sel++;
+ }
+ else
+ {
+ configDevice = dev;
+ config = dev->config;
+ p.config_tab_init = config_tab_init;
+ p.dev = dev->dev;
+ p.parent = hwndDlg;
+ }
+
+ // copes with a cloud device which doesn't need podcast sync and autofill
+ // as well as factoring in the transcoding page needing to be hidden, etc
+ if (configDevice->isCloudDevice)
+ {
+ if (!external && (sel >= SYNC_PREF_IDX)) sel += (3 + (tabs[TRANS_PREF_IDX].title[0] == 0));
+ if (sel == MEDIAVIEW_PREF_IDX)
+ {
+ tabs[sel].res_id = IDD_CONFIG_CLOUD_MEDIAVIEW;
+ tabs[sel].dlg_proc = config_dlgproc_cloud_mediaview;
+ }
+ }
+ else
+ {
+ if (sel == SYNC_PREF_IDX)
+ {
+ tabs[sel].res_id = IDD_CONFIG_SYNC;
+ tabs[sel].dlg_proc = config_dlgproc_sync;
+ }
+ if (sel == MEDIAVIEW_PREF_IDX)
+ {
+ tabs[sel].res_id = IDD_CONFIG_MEDIAVIEW;
+ tabs[sel].dlg_proc = config_dlgproc_mediaview;
+ }
+ }
+
+ if(!tabs[sel].hinst)
+ m_hwndTabDisplay=WASABI_API_CREATEDIALOGPARAMW(tabs[sel].res_id, hwndDlg, tabs[sel].dlg_proc, (LPARAM)&p);
+ else
+ m_hwndTabDisplay=WASABI_API_LNG->CreateLDialogParamW(tabs[sel].hinst, tabs[sel].hinst, tabs[sel].res_id, hwndDlg, tabs[sel].dlg_proc, (LPARAM)&p);
+
+ if (!external)
+ {
+ RECT r;
+ GetClientRect(m_hwndTab, &r);
+ TabCtrl_AdjustRect(m_hwndTab, FALSE, &r);
+ SetWindowPos(m_hwndTabDisplay, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE | SWP_SHOWWINDOW);
+ }
+ return m_hwndTabDisplay;
+}
+
+void Shell_Free(void *p) {
+ IMalloc *m = NULL;
+ if (SUCCEEDED(SHGetMalloc(&m)) && m)
+ {
+ m->Free(p);
+ }
+}
+
+wchar_t* GetDefaultSaveToFolder(wchar_t* path_to_store)
+{
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, path_to_store)))
+ {
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path_to_store)))
+ {
+ // and if that all fails then do a reasonable default
+ lstrcpyn(path_to_store, L"C:\\My Music", MAX_PATH);
+ }
+ // if there's no valid My Music folder (typically win2k) then default to %my_documents%\my music
+ else
+ {
+ PathCombine(path_to_store, path_to_store, L"My Music");
+ }
+ }
+ return path_to_store;
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music";
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)global_config->ReadString(L"extractpath", GetDefaultSaveToFolder(m_def_extract_path)));
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+extern void doFormatFileName(wchar_t out[MAX_PATH], wchar_t *fmt, int trackno, wchar_t *artist, wchar_t *album, wchar_t *title, wchar_t *genre, wchar_t *year, wchar_t *trackartist);
+
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music";
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t m_def_filename_fmt[] = L"<Artist> - <Album>\\## - <Trackartist> - <Title>";
+ GetDefaultSaveToFolder(m_def_extract_path);
+ if (global_config->ReadInt(L"extractusecdrip", 1)) CheckDlgButton(hwndDlg, IDC_CHECK_USECDRIP, BST_CHECKED);
+ SendDlgItemMessage(hwndDlg, IDC_DESTPATH, EM_SETLIMITTEXT, MAX_PATH, 0);
+ SetDlgItemText(hwndDlg, IDC_DESTPATH, global_config->ReadString(L"extractpath", m_def_extract_path));
+ SetDlgItemText(hwndDlg, IDC_FILENAMEFMT, global_config->ReadString(L"extractfmt2", m_def_filename_fmt));
+ SendMessage(hwndDlg,WM_USER,0,0);
+ }
+ break;
+ case WM_USER:
+ {
+ BOOL enabled = !IsDlgButtonChecked(hwndDlg, IDC_CHECK_USECDRIP);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_1),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_DESTPATH),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON1),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_2),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON2),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_FILENAMEFMT),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_FMTOUT),enabled);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_DESTPATH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg, IDC_DESTPATH, buf, 1024);
+ global_config->WriteString(L"extractpath", buf);
+ }
+ return 0;
+ case IDC_CHECK_USECDRIP:
+ {
+ global_config->WriteInt(L"extractusecdrip", !!IsDlgButtonChecked(hwndDlg, IDC_CHECK_USECDRIP));
+ SendMessage(hwndDlg,WM_USER,0,0);
+ }
+ break;
+ // run through...
+ case IDC_FILENAMEFMT:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg, IDC_FILENAMEFMT, buf, 1024);
+ if(LOWORD(wParam) == IDC_FILENAMEFMT) global_config->WriteString(L"extractfmt2", buf);
+ wchar_t str[MAX_PATH] = {0};
+ doFormatFileName(WASABI_API_LNGSTRINGW_BUF(IDS_EXAMPLE_FORMATTING_STRING,str,MAX_PATH),
+ buf, 10, L"U2", L"The Joshua Tree", L"Exit", L"Rock", L"1987", L"U2");
+
+ wchar_t fmt[5]=L"mp3";
+
+ StringCchCat(str, MAX_PATH, L".");
+ StringCchCat(str, MAX_PATH, fmt);
+
+ SetDlgItemText(hwndDlg, IDC_FMTOUT, str);
+ }
+ return 0;
+ case IDC_BUTTON1:
+ {
+ //browse for folder
+ BROWSEINFO bi = {0};
+ wchar_t name[MAX_PATH] = {0};
+ bi.hwndOwner = hwndDlg;
+ bi.pidlRoot = NULL;
+ bi.pszDisplayName = name;
+ bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_A_FOLDER);
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ ITEMIDLIST *idlist = SHBrowseForFolder(&bi);
+ if (idlist)
+ {
+ wchar_t path[MAX_PATH] = {0};
+ SHGetPathFromIDList( idlist, path );
+ Shell_Free(idlist);
+ global_config->WriteString(L"extractpath", path);
+ SetDlgItemText(hwndDlg, IDC_DESTPATH, global_config->ReadString(L"extractpath", m_def_extract_path));
+ }
+ }
+ break;
+ case IDC_BUTTON2:
+ wchar_t titleStr[64] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_COPIED_FILE_FORMAT_INFO),
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPIED_FILE_FORMAT_HELP,titleStr,64),
+ MB_OK);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ config = global_config;
+ DeviceView * dev = NULL;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ dev = (DeviceView *)devices.Get(i);
+ if(&dev->devPrefsPage == (prefsDlgRecW *)lParam) { configDevice = dev; config = dev->config; break; }
+ }
+ if(config == global_config)
+ {
+ EndDialog(hwndDlg,0);
+ return 0;
+ }
+
+ // set the prefs page titles at this stage so that we can have localised versions
+ int title_ids[] = {IDS_PREFS_SYNC,IDS_PODCAST_SYNC,IDS_PREFS_AUTOFILL,IDS_PREFS_TRANSCODING,IDS_PREFS_VIEW};
+ for(int i=0; i<sizeof(title_ids)/sizeof(title_ids[0]); i++)
+ WASABI_API_LNGSTRINGW_BUF(title_ids[i],tabs[i].title,(sizeof(tabs[i].title)/sizeof(wchar_t)));
+
+ // set the last item's title to zero otherwise it can cause a dialog to appear incorrectly
+ tabs[sizeof(tabs)/sizeof(tabs[0])-1].title[0] = 0;
+
+ m_hwndTab = GetDlgItem(hwndDlg,IDC_TAB1);
+ TCITEM tie;
+ tie.mask = TCIF_TEXT;
+ int num = 0;
+ for(int i=0; i<sizeof(tabs)/sizeof(pref_tab); i++)
+ {
+ if (dev)
+ {
+ if(tabs[i].title[0] == 0)
+ {
+ dev->dev->extraActions(DEVICE_GET_PREFS_DIALOG,(intptr_t)&tabs[i],num++,0);
+ }
+
+ // check if we need to show the Transcoding page or not
+ if (i == TRANS_PREF_IDX && dev->dev->extraActions(DEVICE_VETO_TRANSCODING,0,0,0))
+ {
+ tabs[i].title[0] = 0;
+ }
+ else
+ {
+ // check if a cloud device and drop sync, podcast sync and autofill as appropriate
+ if (configDevice->isCloudDevice && (i == SYNC_PREF_IDX || i == PODSYNC_PREF_IDX || i == AUTOFILL_PREF_IDX))
+ {
+ tabs[i].title[0] = 0;
+ }
+ }
+ }
+
+ if(tabs[i].title[0] != 0)
+ {
+ tie.pszText=tabs[i].title;
+ TabCtrl_InsertItem(m_hwndTab,i,&tie);
+ }
+ }
+ TabCtrl_SetCurSel(m_hwndTab,g_prefs_openpage);
+ OnSelChanged(hwndDlg);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR lpn = (LPNMHDR) lParam;
+ if(lpn) if(lpn->code==TCN_SELCHANGE) OnSelChanged(hwndDlg);
+ }
+ break;
+
+ case WM_DESTROY:
+ tabs[4].title[0]=0;
+ configDevice = NULL;
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+static W_ListView m_list;
+static int nonotif;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ nonotif=1;
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ if(config->ReadInt(L"syncOnConnect",configDevice->SyncConnectionDefault)==1) CheckDlgButton(hwndDlg,IDC_SYNCONCONNECT,BST_CHECKED);
+ else EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),FALSE);
+ SetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,config->ReadString(L"syncOnConnect_hours",L"12"));
+ SetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,config->ReadString(L"SyncQuery",L"type=0"));
+ if(config->ReadInt(L"plsyncwhitelist",1)) CheckDlgButton(hwndDlg,IDC_PL_WHITELIST,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PL_BLACKLIST,BST_CHECKED);
+ if(config->ReadInt(L"syncAllLibrary",1)) CheckDlgButton(hwndDlg,IDC_LIBRARYSYNC,BST_CHECKED);
+
+ m_list.setwnd(GetDlgItem(hwndDlg,IDC_PL_LIST));
+ ListView_SetExtendedListViewStyle(m_list.getwnd(), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_list.AddCol(L"",353);
+ m_list.SetColumnWidth(0, 200);
+ mlPlaylistInfo playlist = {0};
+ playlist.size = sizeof(mlPlaylistInfo);
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,0,ML_IPC_PLAYLIST_COUNT);
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ m_list.InsertItem(i,playlist.playlistName,0);
+ }
+ ListView_SetColumnWidth(m_list.getwnd(),0,LVSCW_AUTOSIZE);
+
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ wchar_t buf[150] = {0};
+ StringCchPrintf(buf,150,L"sync-%s",playlist.playlistName);
+ BOOL state = config->ReadInt(buf,0)?TRUE:FALSE;
+ ListView_SetCheckState(m_list.getwnd(),i,state);
+ }
+ nonotif=0;
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_list.getwnd(), TRUE);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ HWND listWindow;
+ listWindow = GetDlgItem(hwndDlg,IDC_PL_LIST);
+ if (NULL != listWindow)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_PL_LIST && l->code == LVN_ITEMCHANGED && !nonotif) {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if(lv->iItem == -1) break;
+ wchar_t buf[150] = {0}, buf1[125] = {0};
+ m_list.GetText(lv->iItem,0,buf1,125);
+ StringCchPrintf(buf,150,L"sync-%s",buf1);
+ BOOL state = ListView_GetCheckState(m_list.getwnd(),lv->iItem);
+ config->WriteInt(buf,state?1:0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_SYNCONCONNECT:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT)?1:0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT));
+ break;
+
+ case IDC_SYNCONCONNECT_TIME:
+ if(HIWORD(wParam) == EN_CHANGE) {
+ wchar_t buf[100]=L"";
+ GetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,buf,100);
+ config->WriteInt(L"syncOnConnect_hours",_wtoi(buf));
+ }
+ break;
+
+ case IDC_PL_WHITELIST:
+ case IDC_PL_BLACKLIST:
+ config->WriteInt(L"plsyncwhitelist",IsDlgButtonChecked(hwndDlg,IDC_PL_WHITELIST)?1:0);
+ break;
+
+ case IDC_LIBRARYSYNC:
+ config->WriteInt(L"syncAllLibrary",IsDlgButtonChecked(hwndDlg,IDC_LIBRARYSYNC)?1:0);
+ break;
+
+ case IDC_SYNC_QUERY_STRING:
+ if (HIWORD(wParam) == EN_KILLFOCUS) {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,buf,1024);
+ config->WriteString(L"SyncQuery",buf);
+ }
+ break;
+
+ case IDC_SYNC_QUERY_EDIT:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, temp, sizeof(temp) - 1);
+ ml_editview meq = {hwndDlg,temp,"Sync Query",-1};
+ meq.name = WASABI_API_LNGSTRING(IDS_SYNC_QUERY);
+ if(!(int)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(LPARAM)&meq,ML_IPC_EDITVIEW)) return 0;
+ SetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, meq.query);
+ config->WriteString(L"SyncQuery",AutoWide(meq.query));
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_cloud_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+static W_ListView m_list;
+static int nonotif;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ nonotif=1;
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ if(config->ReadInt(L"syncOnConnect",configDevice->SyncConnectionDefault)==1) CheckDlgButton(hwndDlg,IDC_SYNCONCONNECT,BST_CHECKED);
+ else EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),FALSE);
+ SetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,config->ReadString(L"syncOnConnect_hours",L"12"));
+ SetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,config->ReadString(L"SyncQuery",L"type=0"));
+ if(config->ReadInt(L"plsyncwhitelist",1)) CheckDlgButton(hwndDlg,IDC_PL_WHITELIST,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PL_BLACKLIST,BST_CHECKED);
+ if(config->ReadInt(L"syncAllLibrary",1)) CheckDlgButton(hwndDlg,IDC_LIBRARYSYNC,BST_CHECKED);
+
+ m_list.setwnd(GetDlgItem(hwndDlg,IDC_PL_LIST));
+ ListView_SetExtendedListViewStyle(m_list.getwnd(), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_list.AddCol(L"",353);
+ mlPlaylistInfo playlist = {0};
+ playlist.size = sizeof(mlPlaylistInfo);
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,0,ML_IPC_PLAYLIST_COUNT);
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ m_list.InsertItem(i,playlist.playlistName,0);
+ }
+ ListView_SetColumnWidth(m_list.getwnd(),0,LVSCW_AUTOSIZE);
+
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ wchar_t buf[150] = {0};
+ StringCchPrintf(buf,150,L"sync-%s",playlist.playlistName);
+ BOOL state = config->ReadInt(buf,0)?TRUE:FALSE;
+ ListView_SetCheckState(m_list.getwnd(),i,state);
+ }
+ nonotif=0;
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_list.getwnd(), TRUE);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ HWND listWindow;
+ listWindow = GetDlgItem(hwndDlg,IDC_PL_LIST);
+ if (NULL != listWindow)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_PL_LIST && l->code == LVN_ITEMCHANGED && !nonotif) {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if(lv->iItem == -1) break;
+ wchar_t buf[150] = {0}, buf1[125] = {0};
+ m_list.GetText(lv->iItem,0,buf1,125);
+ StringCchPrintf(buf,150,L"sync-%s",buf1);
+ BOOL state = ListView_GetCheckState(m_list.getwnd(),lv->iItem);
+ config->WriteInt(buf,state?1:0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_SYNCONCONNECT:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT)?1:0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT));
+ break;
+
+ case IDC_SYNCONCONNECT_TIME:
+ if(HIWORD(wParam) == EN_CHANGE) {
+ wchar_t buf[100]=L"";
+ GetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,buf,100);
+ config->WriteInt(L"syncOnConnect_hours",_wtoi(buf));
+ }
+ break;
+
+ case IDC_PL_WHITELIST:
+ case IDC_PL_BLACKLIST:
+ config->WriteInt(L"plsyncwhitelist",IsDlgButtonChecked(hwndDlg,IDC_PL_WHITELIST)?1:0);
+ break;
+
+ case IDC_LIBRARYSYNC:
+ config->WriteInt(L"syncAllLibrary",IsDlgButtonChecked(hwndDlg,IDC_LIBRARYSYNC)?1:0);
+ break;
+
+ case IDC_SYNC_QUERY_STRING:
+ if (HIWORD(wParam) == EN_KILLFOCUS) {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,buf,1024);
+ config->WriteString(L"SyncQuery",buf);
+ }
+ break;
+
+ case IDC_SYNC_QUERY_EDIT:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, temp, sizeof(temp) - 1);
+ ml_editview meq = {hwndDlg,temp,"Sync Query",-1};
+ meq.name = WASABI_API_LNGSTRING(IDS_SYNC_QUERY);
+ if(!(int)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(LPARAM)&meq,ML_IPC_EDITVIEW)) return 0;
+ SetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, meq.query);
+ config->WriteString(L"SyncQuery",AutoWide(meq.query));
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_podcast_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+static W_ListView m_list2;
+static int nonotif;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ nonotif=1;
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+
+ m_list2.setwnd(GetDlgItem(hwndDlg,IDC_PC_LIST));
+ ListView_SetExtendedListViewStyle(m_list2.getwnd(), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_list2.AddCol(L"",215);
+
+ if (configDevice->dev->extraActions(DEVICE_SUPPORTS_PODCASTS, 0,0, 0) == 0) {
+ int podcastsnum = AGAVE_API_PODCASTS?AGAVE_API_PODCASTS->GetNumPodcasts():0;
+ for(int i=0; i<podcastsnum; i++) {
+ ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i);
+ if (podcast) {
+ wchar_t podcast_name[256] = {0};
+ if (podcast->GetTitle(podcast_name, 256) == 0 && podcast_name[0])
+ {
+ m_list2.InsertItem(i,podcast_name,0);
+ }
+ }
+ }
+ ListView_SetColumnWidth(m_list2.getwnd(),0,LVSCW_AUTOSIZE);
+
+ for(int i=0; i<podcastsnum; i++) {
+ ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i);
+ if (podcast) {
+ wchar_t podcast_name[256] = {0};
+ if (podcast->GetTitle(podcast_name, 256) == 0 && podcast_name[0]) {
+ wchar_t buf[300] = {0};
+ StringCchPrintf(buf,300,L"podcast-sync-%s",podcast_name);
+ BOOL state = config->ReadInt(buf,0)?TRUE:FALSE;
+ ListView_SetCheckState(m_list2.getwnd(),i,state);
+ }
+ }
+ }
+
+ int podcast_eps = config->ReadInt(L"podcast-sync_episodes",0);
+ if(podcast_eps == 0) {
+ podcast_eps=3;
+ } else
+ CheckDlgButton(hwndDlg,IDC_CHECK_PC,BST_CHECKED);
+
+ for(int i=0; i<6; i++) {
+ int a,d;
+ if(i==0) {
+ a = SendDlgItemMessage(hwndDlg,IDC_COMBO_PC_NUM,CB_ADDSTRING,0,
+ (LPARAM)WASABI_API_LNGSTRINGW(IDS_ALL));
+ d = -1;
+ } else {
+ wchar_t buf[10] = {0};
+ StringCchPrintf(buf,100,L"%d",i);
+ a = SendDlgItemMessage(hwndDlg,IDC_COMBO_PC_NUM,CB_ADDSTRING,0,(LPARAM)buf);
+ d = i;
+ }
+ SendDlgItemMessage(hwndDlg,IDC_COMBO_PC_NUM,CB_SETITEMDATA,a,(LPARAM)d);
+ if(d == podcast_eps) SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_SETCURSEL, (WPARAM)a, 0);
+ }
+
+ if(config->ReadInt(L"podcast-sync_all",1)) CheckDlgButton(hwndDlg,IDC_PC_ALL,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PC_SEL,BST_CHECKED);
+ }
+ else {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK_PC), FALSE);
+ }
+ SendMessage(hwndDlg,WM_COMMAND,IDC_CHECK_PC,0xdeadbeef);
+ nonotif=0;
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_list2.getwnd(), TRUE);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ HWND listWindow;
+ listWindow = GetDlgItem(hwndDlg,IDC_PL_LIST);
+ if (NULL != listWindow)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_PC_LIST && l->code == LVN_ITEMCHANGED && !nonotif) {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if(lv->iItem == -1) break;
+ wchar_t buf[150] = {0}, buf1[125] = {0};
+ m_list2.GetText(lv->iItem,0,buf1,125);
+ StringCchPrintf(buf,150,L"podcast-sync-%s",buf1);
+ BOOL state = ListView_GetCheckState(m_list2.getwnd(),lv->iItem);
+ config->WriteInt(buf,state?1:0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_CHECK_PC:
+ {
+ BOOL e = IsDlgButtonChecked(hwndDlg,IDC_CHECK_PC);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_PC_NUM),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_PODTXT),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_ALL),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_SEL),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_PODTIP),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_LIST),e && IsDlgButtonChecked(hwndDlg,IDC_PC_SEL));
+ if(lParam != 0xdeadbeef) {
+ if(e) {
+ int b = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETCURSEL, 0, 0);
+ if(b>=0) {
+ int x = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETITEMDATA, b, 0);
+ config->WriteInt(L"podcast-sync_episodes", x);
+ }
+ }
+ else config->WriteInt(L"podcast-sync_episodes",0);
+ }
+ }
+ break;
+
+ case IDC_PC_SEL:
+ case IDC_PC_ALL:
+ {
+ BOOL e = IsDlgButtonChecked(hwndDlg,IDC_PC_SEL);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_LIST),e);
+ config->WriteInt(L"podcast-sync_all",!e);
+ }
+ break;
+
+ case IDC_COMBO_PC_NUM:
+ if(HIWORD(wParam) == CBN_SELCHANGE) {
+ int b = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETCURSEL, 0, 0);
+ if (b >= 0) {
+ int x = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETITEMDATA, b, 0);
+ config->WriteInt(L"podcast-sync_episodes", x);
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_autofill(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ if(config->ReadInt(L"syncOnConnect",configDevice->SyncConnectionDefault)==2) CheckDlgButton(hwndDlg,IDC_SYNCONCONNECT,BST_CHECKED);
+ else EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),FALSE);
+ SetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,config->ReadString(L"syncOnConnect_hours",L"12"));
+ if(lstrlen(config->ReadString(L"AutoFillRatings",L""))!=0) CheckDlgButton(hwndDlg,IDC_BOOSTRATINGS,BST_CHECKED);
+ if(config->ReadInt(L"AlbumAutoFill",0)) CheckDlgButton(hwndDlg,IDC_AUTOFILLALBUMS,BST_CHECKED);
+ SetDlgItemText(hwndDlg,IDC_AUTOFILL_QUERY_STRING,config->ReadString(L"AutoFillQuery",L"length > 30"));
+ SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_SETRANGEMAX, TRUE, 100);
+ SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_SETRANGEMIN, TRUE, 0);
+ SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_SETPOS, TRUE, config->ReadInt(L"FillPercent",90));
+ break;
+ case WM_NOTIFY:
+ switch (LOWORD(wParam))
+ {
+ case IDC_SPACESLIDER:
+ {
+ int spaceToAutofill = SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_GETPOS,0,0);
+ wchar_t tmp[100]=L"";
+ StringCchPrintf(tmp,100,WASABI_API_LNGSTRINGW(IDS_AIM_TO_AUTOFILL_DEVICE),spaceToAutofill);
+ SetDlgItemText(hwndDlg, IDC_FILLCAPTION, tmp);
+ config->WriteInt(L"FillPercent",spaceToAutofill);
+ }
+ break;
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_SYNCONCONNECT:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT)?2:0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT));
+ break;
+ case IDC_SYNCONCONNECT_TIME:
+ if(HIWORD(wParam) == EN_CHANGE)
+ {
+ wchar_t buf[100]=L"";
+ GetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,buf,100);
+ config->WriteInt(L"syncOnConnect_hours",_wtoi(buf));
+ }
+ break;
+ case IDC_AUTOFILL_QUERY_STRING:
+ if (HIWORD(wParam) == EN_KILLFOCUS)
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg,IDC_AUTOFILL_QUERY_STRING,buf,1024);
+ config->WriteString(L"AutoFillQuery",buf);
+ }
+ break;
+ case IDC_AUTOFILL_QUERY_EDIT:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_AUTOFILL_QUERY_STRING, temp, sizeof(temp) - 1);
+ ml_editview meq = {hwndDlg,temp,0,-1};
+ meq.name = WASABI_API_LNGSTRING(IDS_AUTOFILL_QUERY);
+ if(!(int)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(LPARAM)&meq,ML_IPC_EDITVIEW)) return 0;
+ SetDlgItemTextA(hwndDlg, IDC_AUTOFILL_QUERY_STRING, meq.query);
+ config->WriteString(L"AutoFillQuery",AutoWide(meq.query));
+ }
+ break;
+ case IDC_BOOSTRATINGS:
+ if(IsDlgButtonChecked(hwndDlg,IDC_BOOSTRATINGS)) config->WriteString(L"AutoFillRatings",L"20:18:15:13:11:10");
+ else config->WriteString(L"AutoFillRatings",L"");
+ break;
+ case IDC_AUTOFILLALBUMS:
+ config->WriteInt(L"AlbumAutoFill",IsDlgButtonChecked(hwndDlg,IDC_AUTOFILLALBUMS)?1:0);
+ break;
+ }
+ break;
+ }
+
+ const int controls[] =
+ {
+ IDC_SPACESLIDER,
+ };
+ if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls)))
+ return TRUE;
+
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_transcode(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ Device *dev = 0;
+ if(lParam)
+ {
+ config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ dev = ((prefsParam*)lParam)->dev;
+
+ }
+ lParam = (LPARAM)TranscoderImp::ConfigureTranscoder(L"ml_pmp",plugin.hwndWinampParent,config, dev);
+ }
+ break;
+ }
+ return TranscoderImp::transcodeconfig_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+static INT_PTR CALLBACK config_dlgproc_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ int fieldsBits = (int)configDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits=-1;
+ // used to ensure that the filters will work irrespective of language in use
+ wchar_t * filters[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist":0,
+ (fieldsBits&SUPPORTS_ALBUM)?L"Album":0,
+ (fieldsBits&SUPPORTS_GENRE)?L"Genre":0,
+ (fieldsBits&SUPPORTS_YEAR)?L"Year":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist":0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?L"Publisher":0,
+ (fieldsBits&SUPPORTS_COMPOSER)?L"Composer":0,
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMART)?L"Album Art":0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?L"Mime":0,
+ (fieldsBits&SUPPORTS_DATEADDED)?L"Date Added":0,
+ };
+ // used for displayed items - localised crazyness, heh
+ int filters_idx[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST:0,
+ (fieldsBits&SUPPORTS_ALBUM)?IDS_ALBUM:0,
+ (fieldsBits&SUPPORTS_GENRE)?IDS_GENRE:0,
+ (fieldsBits&SUPPORTS_YEAR)?IDS_YEAR:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST:0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?IDS_PUBLISHER:0,
+ (fieldsBits&SUPPORTS_COMPOSER)?IDS_COMPOSER:0,
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMART)?IDS_ALBUM_ART:0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?IDS_MIME_TYPE:0,
+ (fieldsBits&SUPPORTS_DATEADDED)?IDS_DATE_ADDED:0,
+ };
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+ CheckRadioButton(hwndDlg, IDC_RADIO_FILTERS1, IDC_RADIO_FILTERS3, (IDC_RADIO_FILTERS1 + numFilters - 1));
+
+ CheckDlgButton(hwndDlg, IDC_REMEMBER_SEARCH, config->ReadInt(L"savefilter", 1));
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ for(int j=0; j<(sizeof(filters)/sizeof(wchar_t*)); j++) {
+ if(filters[j]) {
+ int a = SendDlgItemMessage(hwndDlg,id,CB_ADDSTRING,0,
+ (LPARAM)WASABI_API_LNGSTRINGW(filters_idx[j]));
+ SendDlgItemMessage(hwndDlg,id,CB_SETITEMDATA,a,(LPARAM)filters[j]);
+ }
+ }
+
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,numFilters));
+ for(int l = 0; l < (sizeof(filters)/sizeof(wchar_t*)); l++) {
+ wchar_t* x = (wchar_t*)SendDlgItemMessage(hwndDlg,id,CB_GETITEMDATA,l,0);
+ if(x && x != (wchar_t*)-1 && !_wcsicmp(filterStr,x)) {
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,(WPARAM)l,0);
+ break;
+ }
+ }
+ }
+
+ if(configDevice->videoView) CheckDlgButton(hwndDlg,IDC_CHECK_VIDEOVIEW,TRUE);
+ }
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case WM_USER:
+ {
+ BOOL full_enable = (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1) != BST_CHECKED);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER1), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER2), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER2), full_enable);
+
+ BOOL enable = (IsDlgButtonChecked(hwndDlg, IDC_RADIO_FILTERS3) == BST_CHECKED) && full_enable;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER3), enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER3), enable);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_RADIO_FILTERS3:
+ case IDC_RADIO_FILTERS2:
+ case IDC_RADIO_FILTERS1:
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case IDC_CHECK_VIDEOVIEW:
+ configDevice->SetVideoView(IsDlgButtonChecked(hwndDlg,IDC_CHECK_VIDEOVIEW));
+ break;
+ case IDC_REMEMBER_SEARCH:
+ config->WriteInt(L"savefilter", IsDlgButtonChecked(hwndDlg,IDC_REMEMBER_SEARCH));
+ config->WriteString(L"savedfilter", L"");
+ config->WriteString(L"savedrefinefilter", L"");
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ int update_needed = false;
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+
+ int new_numFilters = 1;
+ if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1)) new_numFilters = 1;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS2)) new_numFilters = 2;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS3)) new_numFilters = 3;
+
+ if (new_numFilters != numFilters) update_needed++;
+ config->WriteInt(L"media_numfilters",new_numFilters);
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ int sel = SendDlgItemMessage(hwndDlg, id, CB_GETCURSEL, 0, 0);
+ wchar_t * x = (wchar_t*)SendDlgItemMessage(hwndDlg, id, CB_GETITEMDATA, sel, 0);
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,new_numFilters));
+
+ if(x && x != (wchar_t*)-1)
+ {
+ config->WriteString(name,x);
+ update_needed += (wcscmp(filterStr, x));
+ }
+ }
+
+ // only refresh the view if it is one of ours (is a bit silly
+ // otherwise) and also not refresh unless there was a change
+ // with the configDevice check to cope with going elsewhere
+ if (update_needed && ((configDevice && configDevice == currentViewedDevice) || IsWindow(hwndMediaView))) {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_cloud_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ int fieldsBits = (int)configDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits=-1;
+ // used to ensure that the filters will work irrespective of language in use
+ wchar_t * filters[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist":0,
+ (fieldsBits&SUPPORTS_ALBUM)?L"Album":0,
+ (fieldsBits&SUPPORTS_GENRE)?L"Genre":0,
+ (fieldsBits&SUPPORTS_YEAR)?L"Year":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist":0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?L"Publisher":0,
+ (fieldsBits&SUPPORTS_COMPOSER)?L"Composer":0,
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMART)?L"Album Art":0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?L"Mime":0,
+ (fieldsBits&SUPPORTS_DATEADDED)?L"Date Added":0,
+ };
+ // used for displayed items - localised crazyness, heh
+ int filters_idx[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST:0,
+ (fieldsBits&SUPPORTS_ALBUM)?IDS_ALBUM:0,
+ (fieldsBits&SUPPORTS_GENRE)?IDS_GENRE:0,
+ (fieldsBits&SUPPORTS_YEAR)?IDS_YEAR:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST:0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?IDS_PUBLISHER:0,
+ (fieldsBits&SUPPORTS_COMPOSER)?IDS_COMPOSER:0,
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMART)?IDS_ALBUM_ART:0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?IDS_MIME_TYPE:0,
+ (fieldsBits&SUPPORTS_DATEADDED)?IDS_DATE_ADDED:0,
+ };
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+ CheckRadioButton(hwndDlg, IDC_RADIO_FILTERS1, IDC_RADIO_FILTERS3, (IDC_RADIO_FILTERS1 + numFilters - 1));
+
+ CheckDlgButton(hwndDlg, IDC_REMEMBER_SEARCH, config->ReadInt(L"savefilter", 1));
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ for(int j=0; j<(sizeof(filters)/sizeof(wchar_t*)); j++) {
+ if(filters[j]) {
+ int a = SendDlgItemMessage(hwndDlg,id,CB_ADDSTRING,0,
+ (LPARAM)WASABI_API_LNGSTRINGW(filters_idx[j]));
+ SendDlgItemMessage(hwndDlg,id,CB_SETITEMDATA,a,(LPARAM)filters[j]);
+ }
+ }
+
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,numFilters));
+ for(int l = 0; l < (sizeof(filters)/sizeof(wchar_t*)); l++) {
+ wchar_t* x = (wchar_t*)SendDlgItemMessage(hwndDlg,id,CB_GETITEMDATA,l,0);
+ if(x && x != (wchar_t*)-1 && !_wcsicmp(filterStr,x)) {
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,(WPARAM)l,0);
+ break;
+ }
+ }
+ }
+ }
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case WM_USER:
+ {
+ BOOL full_enable = (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1) != BST_CHECKED);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER1), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER2), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER2), full_enable);
+
+ BOOL enable = (IsDlgButtonChecked(hwndDlg, IDC_RADIO_FILTERS3) == BST_CHECKED) && full_enable;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER3), enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER3), enable);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_RADIO_FILTERS3:
+ case IDC_RADIO_FILTERS2:
+ case IDC_RADIO_FILTERS1:
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case IDC_REMEMBER_SEARCH:
+ config->WriteInt(L"savefilter", IsDlgButtonChecked(hwndDlg,IDC_REMEMBER_SEARCH));
+ config->WriteString(L"savedfilter", L"");
+ config->WriteString(L"savedrefinefilter", L"");
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ int update_needed = false;
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+
+ int new_numFilters = 1;
+ if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1)) new_numFilters = 1;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS2)) new_numFilters = 2;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS3)) new_numFilters = 3;
+
+ if (new_numFilters != numFilters) update_needed++;
+ config->WriteInt(L"media_numfilters",new_numFilters);
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ int sel = SendDlgItemMessage(hwndDlg, id, CB_GETCURSEL, 0, 0);
+ wchar_t * x = (wchar_t*)SendDlgItemMessage(hwndDlg, id, CB_GETITEMDATA, sel, 0);
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,new_numFilters));
+
+ if(x && x != (wchar_t*)-1)
+ {
+ config->WriteString(name,x);
+ update_needed += (wcscmp(filterStr, x));
+ }
+ }
+
+ // only refresh the view if it is one of ours (is a bit silly
+ // otherwise) and also not refresh unless there was a change
+ // with the configDevice check to cope with going elsewhere
+ if (update_needed && ((configDevice && configDevice == currentViewedDevice) || IsWindow(hwndMediaView))) {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+extern void unloadPlugin(PMPDevicePlugin *devplugin, int n=-1);
+extern PMPDevicePlugin * loadPlugin(wchar_t * file);
+
+HRESULT RemovePMPPlugin(LPCWSTR file, HINSTANCE hDllInstance) {
+ if(!hDllInstance) {
+ SHFILEOPSTRUCT op = {0};
+ wchar_t srcFile[MAX_PATH+1], *end;
+ op.wFunc = FO_DELETE;
+ StringCchCopyExW(srcFile, MAX_PATH, file, &end, 0, 0);
+ if (end) end[1]=0; // double null terminate
+ op.pFrom = srcFile;
+ op.fFlags=FOF_NOCONFIRMATION|FOF_NOCONFIRMMKDIR|FOF_SIMPLEPROGRESS|FOF_NORECURSION|FOF_NOERRORUI|FOF_SILENT;
+ return (!SHFileOperation(&op)? S_OK : E_FAIL);
+ }
+ else {
+ wchar_t buf[1024],
+ *ini = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETINIFILEW);
+ GetModuleFileName(hDllInstance, buf, ARRAYSIZE(buf));
+ WritePrivateProfileString(L"winamp", L"remove_genplug", buf, ini);
+ WritePrivateProfileString(L"winamp", L"show_prefs", L"-1", ini);
+ PostMessage(plugin.hwndWinampParent, WM_USER, 0, IPC_RESTARTWINAMP);
+ return S_OK;
+ }
+}
+
+static bool pluginsLoaded;
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ pluginsLoaded = false;
+ link_startsubclass(hwndDlg, IDC_PLUGINVERS);
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ if (NULL != listWindow)
+ {
+ RECT r = {0}, rc = {0};
+ GetWindowRect(listWindow, &r);
+ GetClientRect(listWindow, &r);
+ MapWindowPoints(listWindow, hwndDlg, (LPPOINT)&r, 2);
+ InflateRect(&r, 2, 2);
+ DestroyWindow(listWindow);
+ listWindow = CreateWindowEx(WS_EX_NOPARENTNOTIFY | WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL |
+ LVS_SHOWSELALWAYS | LVS_SORTASCENDING | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER,
+ r.left, r.top, r.right - r.left, r.bottom - r.top,
+ hwndDlg, (HMENU)IDC_PLUGINSLIST, NULL, NULL);
+ SetWindowPos(listWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
+ ListView_SetExtendedListViewStyleEx(listWindow, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ SendMessage(listWindow, WM_SETFONT, SendMessage(hwndDlg, WM_GETFONT, 0, 0), FALSE);
+
+ LVCOLUMNW lvc = {0};
+ ListView_InsertColumn(listWindow, 0, &lvc);
+ ListView_InsertColumn(listWindow, 1, &lvc);
+
+ wchar_t buf[1024] = {0}, fn[MAX_PATH] = {0};
+ for (int x = 0; x < m_plugins.GetSize(); x ++)
+ {
+ PMPDevicePlugin * devplugin=(PMPDevicePlugin *)m_plugins.Get(x);
+ if (devplugin)
+ {
+ GetModuleFileNameW(devplugin->hDllInstance, fn, MAX_PATH);
+ PathStripPath(fn);
+
+ LVITEMW lvi = {LVIF_TEXT | LVIF_PARAM, x, 0};
+ lvi.pszText = devplugin->description;
+ lvi.lParam = x;
+ lvi.iItem = ListView_InsertItem(listWindow, &lvi);
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ lvi.pszText = fn;
+ ListView_SetItem(listWindow, &lvi);
+ }
+ }
+
+ WIN32_FIND_DATA d = {0};
+ wchar_t *pluginPath = (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
+ wchar_t dirstr[MAX_PATH] = {0};
+ PathCombine(dirstr, pluginPath, L"PMP_*.DLL");
+ HANDLE h = FindFirstFile(dirstr, &d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ PathCombine(dirstr, pluginPath, d.cFileName);
+ HMODULE b = LoadLibraryEx(dirstr, NULL, LOAD_LIBRARY_AS_DATAFILE);
+ int x = 0;
+ for (; b && (x != m_plugins.GetSize()); x ++)
+ {
+ PMPDevicePlugin *devplugin = (PMPDevicePlugin *)m_plugins.Get(x);
+ if (devplugin->hDllInstance == b)
+ {
+ break;
+ }
+ }
+
+ if (x == m_plugins.GetSize() || !b)
+ {
+ LVITEMW lvi = {LVIF_TEXT | LVIF_PARAM, x, 0};
+ lvi.pszText = d.cFileName;
+ lvi.lParam = -2;
+ lvi.iItem = ListView_InsertItem(listWindow, &lvi);
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ lvi.pszText = WASABI_API_LNGSTRINGW(IDS_NOT_LOADED);
+ ListView_SetItem(listWindow, &lvi);
+ }
+ FreeLibrary(b);
+ }
+ while (FindNextFile(h, &d));
+ FindClose(h);
+ }
+
+ GetClientRect(listWindow, &r);
+ ListView_SetColumnWidth(listWindow, 1, LVSCW_AUTOSIZE);
+ ListView_SetColumnWidth(listWindow, 0, (r.right - r.left) - ListView_GetColumnWidth(listWindow, 1));
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, TRUE);
+
+ pluginsLoaded = true;
+ }
+ }
+ break;
+ }
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR p = (LPNMHDR)lParam;
+ if (p->idFrom == IDC_PLUGINSLIST)
+ {
+ if (p->code == LVN_ITEMCHANGED)
+ {
+ LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
+ LVITEM lvi = {LVIF_PARAM, pnmv->iItem};
+ if (ListView_GetItem(p->hwndFrom, &lvi) && (pnmv->uNewState & LVIS_SELECTED))
+ {
+ int loaded = (lvi.lParam != -2);
+ if (loaded)
+ {
+ PMPDevicePlugin *devplugin;
+ if (lvi.lParam >= 0 && lvi.lParam < m_plugins.GetSize() &&
+ (devplugin = (PMPDevicePlugin *)m_plugins.Get(lvi.lParam)))
+ {
+ // enables / disables the config button as applicable instead of the
+ // "This plug-in has no configuration implemented" message (opt-in)
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN), (!devplugin->MessageProc(PMP_NO_CONFIG, 0, 0, 0)));
+ }
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN), 0);
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_UNINSTALLPLUGIN), 1);
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_UNINSTALLPLUGIN), 0);
+ }
+ }
+ else if (p->code == NM_DBLCLK)
+ {
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CONFIGPLUGIN, 0), (LPARAM)GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN));
+ }
+ }
+ else if (p->code == HDN_ITEMCHANGINGW)
+ {
+ if (pluginsLoaded)
+ {
+#if defined(_WIN64)
+ SetWindowLong(hwndDlg, DWLP_MSGRESULT, TRUE);
+#else
+ SetWindowLong(hwndDlg, DWL_MSGRESULT, TRUE);
+#endif
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ case WM_DESTROY:
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ if (IsWindow(listWindow) && (NULL != WASABI_API_APP))
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_CONFIGPLUGIN:
+ {
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN)))
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ LVITEM lvi = {LVIF_PARAM, ListView_GetSelectionMark(listWindow)};
+ if (ListView_GetItem(listWindow, &lvi))
+ {
+ PMPDevicePlugin *devplugin;
+ if(lvi.lParam >= 0 && lvi.lParam < m_plugins.GetSize() && (devplugin=(PMPDevicePlugin *)m_plugins.Get(lvi.lParam)))
+ {
+ if(devplugin->MessageProc(PMP_CONFIG,(intptr_t)hwndDlg,0,0) == 0)
+ {
+ wchar_t titleStr[64] = {0};
+ MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_PLUGIN_HAS_NO_CONFIG_IMPLEMENTED),
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_PLUGINS,titleStr,64),0);
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case IDC_UNINSTALLPLUGIN:
+ {
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_UNINSTALLPLUGIN)))
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ int which_sel = ListView_GetSelectionMark(listWindow);
+ LVITEM lvi = {LVIF_PARAM, which_sel};
+ if (ListView_GetItem(listWindow, &lvi))
+ {
+ PMPDevicePlugin *devplugin = 0;
+ wchar_t titleStr[32] = {0};
+ int msgBox = MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_PERMANENTLY_UNINSTALL_THIS_PLUGIN),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION, titleStr, 32), MB_YESNO | MB_ICONEXCLAMATION);
+
+ if (lvi.lParam >= 0 && lvi.lParam <= m_plugins.GetSize() && (devplugin=(PMPDevicePlugin *)m_plugins.Get(lvi.lParam)) && msgBox == IDYES)
+ {
+ wchar_t buf[1024] = {0};
+ GetModuleFileName(devplugin->hDllInstance,buf,sizeof(buf)/sizeof(wchar_t));
+ int ret = PMP_PLUGIN_UNINSTALL_NOW;
+ int (*pr)(HINSTANCE hDllInst, HWND hwndDlg, int param);
+ *(void**)&pr = (void*)GetProcAddress(devplugin->hDllInstance,"winampUninstallPlugin");
+ if(pr) ret = pr(devplugin->hDllInstance,hwndDlg,0);
+ if(pr && ret == PMP_PLUGIN_UNINSTALL_NOW) { // dynamic unload
+ ListView_DeleteItem(listWindow, lvi.lParam);
+ unloadPlugin(devplugin,lvi.lParam);
+
+ // removing the plugin (bit convoluted to hopefully not cause crashes with dynamic removal)
+ // try to use the elevator to do this
+ IFileTypeRegistrar *registrar = (IFileTypeRegistrar*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_FILEREGISTRAR_OBJECT);
+ if(registrar && (registrar != (IFileTypeRegistrar*)1)) {
+ if(registrar->DeleteItem(buf) != S_OK) {
+ // we don't always free by default as it can cause some crashes
+ FreeLibrary(devplugin->hDllInstance);
+ if(registrar->DeleteItem(buf) != S_OK) {
+ // all gone wrong so non-dynamic unload (restart winamp)
+ RemovePMPPlugin(buf, devplugin->hDllInstance);
+ }
+ }
+ registrar->Release();
+ }
+ // otherwise revert to a standard method
+ else {
+ if(RemovePMPPlugin(buf, 0) != S_OK){
+ // we don't always free by default as it can cause some crashes
+ FreeLibrary(devplugin->hDllInstance);
+ if(RemovePMPPlugin(buf, 0) != S_OK) {
+ // all gone wrong so non-dynamic unload (restart winamp)
+ RemovePMPPlugin(buf, devplugin->hDllInstance);
+ }
+ }
+ }
+ }
+ else if(!pr)
+ { // non-dynamic unload (restart winamp)
+ RemovePMPPlugin(buf,devplugin->hDllInstance);
+ }
+ }
+ // will cope with not loaded plug-ins so we can still remove them, etc
+ else if (lvi.lParam == -2 && msgBox == IDYES)
+ {
+ wchar_t buf[1024] = {0}, base[1024] = {0};
+ GetModuleFileName(plugin.hDllInstance,base,sizeof(base)/sizeof(wchar_t));
+
+ LVITEM lvi = {LVIF_TEXT, which_sel};
+ lvi.pszText = buf;
+ lvi.cchTextMax = ARRAYSIZE(buf);
+ ListView_GetItem(listWindow, &lvi);
+
+ wchar_t *p = wcschr(buf, L'.');
+ if (p && *p == L'.')
+ {
+ p += 4;
+ *p = 0;
+ PathRemoveFileSpec(base);
+ PathAppend(base, buf);
+ }
+
+ // try to use the elevator to do this
+ IFileTypeRegistrar *registrar = (IFileTypeRegistrar*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_FILEREGISTRAR_OBJECT);
+ if(registrar && (registrar != (IFileTypeRegistrar*)1)) {
+ if(registrar->DeleteItem(base) != S_OK){
+ RemovePMPPlugin(base, 0);
+ }
+ else
+ ListView_DeleteItem(listWindow, which_sel);
+ registrar->Release();
+ }
+ // otherwise revert to a standard method
+ else {
+ RemovePMPPlugin(base, 0);
+ }
+ }
+
+ // resets the focus to the listbox so it'll keep ui response working
+ SetFocus(GetDlgItem(hwndDlg, IDC_PLUGINSLIST));
+ }
+ }
+ }
+ break;
+
+ case IDC_PLUGINVERS:
+ myOpenURLWithFallback(hwndDlg, L"http://www.google.com/search?q=Winamp+Portable+Plugins",L"http://www.google.com/search?q=Winamp+Portable+Plugins");
+ break;
+ }
+ break;
+ }
+ link_handledraw(hwndDlg,uMsg,wParam,lParam);
+ return 0;
+}
+
+void myOpenURLWithFallback(HWND hwnd, wchar_t *loc, wchar_t *fallbackLoc)
+{
+ bool override=false;
+ if (loc)
+ {
+ WASABI_API_SYSCB->syscb_issueCallback(SysCallback::BROWSER, BrowserCallback::ONOPENURL, reinterpret_cast<intptr_t>(loc), reinterpret_cast<intptr_t>(&override));
+ }
+ if (!override && fallbackLoc)
+ ShellExecuteW(hwnd, L"open", fallbackLoc, NULL, NULL, SW_SHOWNORMAL);
+}
+
+static HCURSOR link_hand_cursor;
+LRESULT link_handlecursor(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT ret = CallWindowProcW((WNDPROC)GetPropW(hwndDlg, L"link_proc"), hwndDlg, uMsg, wParam, lParam);
+ // override the normal cursor behaviour so we have a hand to show it is a link
+ if(uMsg == WM_SETCURSOR)
+ {
+ if((HWND)wParam == hwndDlg)
+ {
+ if(!link_hand_cursor)
+ {
+ link_hand_cursor = LoadCursor(NULL, IDC_HAND);
+ }
+ SetCursor(link_hand_cursor);
+ return TRUE;
+ }
+ }
+
+ return ret;
+}
+
+void link_startsubclass(HWND hwndDlg, UINT id){
+HWND ctrl = GetDlgItem(hwndDlg, id);
+ SetPropW(ctrl, L"link_proc",
+ (HANDLE)SetWindowLongPtrW(ctrl, GWLP_WNDPROC, (LONG_PTR)link_handlecursor));
+}
+
+static void link_handledraw(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_DRAWITEM)
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ wchar_t wt[123] = {0};
+ int y;
+ RECT r;
+ HPEN hPen, hOldPen;
+ GetDlgItemText(hwndDlg, wParam, wt, sizeof(wt)/sizeof(wchar_t));
+
+ // draw text
+ SetTextColor(di->hDC, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ r = di->rcItem;
+ r.left += 2;
+ DrawText(di->hDC, wt, -1, &r, DT_VCENTER | DT_SINGLELINE);
+
+ memset(&r, 0, sizeof(r));
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_CALCRECT);
+
+ // draw underline
+ y = di->rcItem.bottom - ((di->rcItem.bottom - di->rcItem.top) - (r.bottom - r.top)) / 2 - 1;
+ hPen = CreatePen(PS_SOLID, 0, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ hOldPen = (HPEN) SelectObject(di->hDC, hPen);
+ MoveToEx(di->hDC, di->rcItem.left + 2, y, NULL);
+ LineTo(di->hDC, di->rcItem.right + 2 - ((di->rcItem.right - di->rcItem.left) - (r.right - r.left)), y);
+ SelectObject(di->hDC, hOldPen);
+ DeleteObject(hPen);
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/replaceVars.cpp b/Src/Plugins/Library/ml_pmp/replaceVars.cpp
new file mode 100644
index 00000000..325442fb
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/replaceVars.cpp
@@ -0,0 +1,213 @@
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include "pmp.h"
+#include <strsafe.h>
+#include "api__ml_pmp.h"
+#include "resource1.h"
+extern winampMediaLibraryPlugin plugin;
+
+wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song);
+BOOL RecursiveCreateDirectory(wchar_t* buf1);
+void doFormatFileName(wchar_t out[MAX_PATH], wchar_t *fmt, int trackno, wchar_t *artist, wchar_t *album, wchar_t *title, wchar_t *genre, wchar_t *year, wchar_t *trackartist);
+
+static void removebadchars(wchar_t *s) {
+ while (s && *s)
+ {
+ if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|')
+ *s = L'_';
+ s = CharNextW(s);
+ }
+}
+
+// Skip_Root: removes drive/host/share name in a path
+wchar_t * Skip_Root(wchar_t *path) {
+ wchar_t *p = CharNext(path);
+ wchar_t *p2 = CharNext(p);
+ if (*path && *p == L':' && *p2 == L'\\') return CharNext(p2);
+ else if (*path == L'\\' && *p == L'\\') {
+ // skip host and share name
+ int x = 2;
+ while (x--) {
+ while (p2 && *p2 != L'\\') {
+ if (!p2 || !*p2) return NULL;
+ p2 = CharNext(p2);
+ }
+ p2 = CharNext(p2);
+ }
+ return p2;
+ }
+ return NULL;
+}
+
+// RecursiveCreateDirectory: creates all non-existent folders in a path
+BOOL RecursiveCreateDirectory(wchar_t* buf1) {
+ wchar_t *p=buf1;
+ int errors = 0;
+ if (*p) {
+ p = Skip_Root(buf1);
+ if (!p) return true ;
+
+ wchar_t ch='c';
+ while (ch) {
+ while (p && *p != '\\' && *p) p=CharNext(p);
+ ch=*p;
+ if (p) *p=0;
+ int pp = wcslen(buf1)-1;
+
+ while(buf1[pp] == '.' ||
+ buf1[pp] == ' ' ||
+ (buf1[pp] == '\\' && (buf1[pp-1] == '.' || buf1[pp-1] == ' ' || buf1[pp-1] == '/'))
+ || buf1[pp] == '/' && buf1)
+ {
+ if(buf1[pp] == '\\')
+ {
+ buf1[pp-1] = '_';
+ pp -= 2;
+ }else{
+ buf1[pp] = '_';
+ pp--;
+ }
+ }
+
+ HANDLE h;
+ WIN32_FIND_DATA fd;
+ // Avoid a "There is no disk in the drive" error box on empty removable drives
+ UINT prevErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ h = FindFirstFile(buf1,&fd);
+ SetErrorMode(prevErrorMode);
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ if (!CreateDirectory(buf1,NULL)) errors++;
+ } else {
+ FindClose(h);
+ if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) errors++;
+ }
+ *p++ = ch;
+ }
+ }
+ return errors != 0;
+}
+
+// FixReplacementVars: replaces <Artist>, <Title>, <Album>, and #, ##, ##, with appropriate data
+// DOES NOT add a file extention!!
+wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song)
+{
+ wchar_t artist[256]=L"",album[256]=L"",albumartist[256]=L"",title[256]=L"",genre[256]=L"",year[10]=L"",dest[MAX_PATH]=L"";
+ dev->getTrackArtist(song,artist,256);
+ dev->getTrackAlbum(song,album,256);
+ dev->getTrackAlbumArtist(song,albumartist,256);
+ if(!albumartist[0]) lstrcpyn(albumartist,artist,256);
+ dev->getTrackTitle(song,title,256);
+ dev->getTrackGenre(song,genre,256);
+ int y = dev->getTrackYear(song);
+ if(y>0) StringCchPrintfW(year,10,L"%d",y);
+ wchar_t unknown[32] = {0}, unknownartist[32] = {0}, unknownalbum[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,unknown,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_ARTIST,unknownartist,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKKNOWN_ALBUM,unknownalbum,32);
+ doFormatFileName(dest,str,
+ dev->getTrackTrackNum(song),
+ *albumartist ? albumartist : unknownartist,
+ *album ? album : unknownalbum,
+ *title ? title : L"0",
+ *genre ? genre : unknown,
+ *year ? year : unknown,
+ *artist ? artist : unknown
+ );
+ bool c = (str[1]==':');
+ lstrcpyn(str,dest,str_size);
+ if(c) str[1]=L':';
+ return str;
+}
+
+static void CleanDirectory(wchar_t *str)
+{
+ if (!str)
+ return ;
+ int l = wcslen(str);
+
+ while (l--)
+ {
+ if (str[l] == L' '
+ || str[l] == L'.')
+ str[l] = 0;
+ else
+ break;
+ }
+}
+
+void doFormatFileName(wchar_t out[MAX_PATH], wchar_t *fmt, int trackno, wchar_t *artist, wchar_t *album, wchar_t *title, wchar_t *genre, wchar_t *year, wchar_t *trackartist)
+{
+ CleanDirectory(artist);
+ CleanDirectory(album);
+ CleanDirectory(title);
+ CleanDirectory(genre);
+ CleanDirectory(year);
+ while (fmt && *fmt)
+ {
+ int whichstr = 0;
+ if (*fmt == L'#' && trackno != 0xdeadbeef)
+ {
+ int cnt = 0;
+ while (fmt && *fmt == L'#')
+ {
+ fmt++;
+ cnt++;
+ }
+ if (cnt > 8) cnt = 8;
+ wchar_t specstr[32] = {0};
+ StringCchPrintf(specstr, 32, L"%%%02dd", cnt);
+ wchar_t tracknostr[32] = {0};
+ StringCchPrintf(tracknostr, 32, specstr, trackno);
+ StringCchCat(out, MAX_PATH, tracknostr);
+ }
+ else if (artist && !_wcsnicmp(fmt, L"<artist>", 8)) whichstr = 1;
+ else if (album && !_wcsnicmp(fmt, L"<album>", 7)) whichstr = 2;
+ else if (title && !_wcsnicmp(fmt, L"<title>", 7)) whichstr = 3;
+ else if (genre && !_wcsnicmp(fmt, L"<genre>", 7)) whichstr = 4;
+ else if (year && !_wcsnicmp(fmt, L"<year>", 6)) whichstr = 5;
+ else if (year && !_wcsnicmp(fmt, L"<trackartist>", 13)) whichstr=6;
+ else
+ {
+ wchar_t p[2] = {0};
+ p[0] = *fmt++;
+ if (p[0] == L'?' || p[0] == L'*' || p[0] == L'|') p[0] = L'_';
+ else if (p[0] == L':') p[0] = L'-';
+ else if (p[0] == L'\"') p[0] = L'\'';
+ else if (p[0] == L'<') p[0] = L'(';
+ else if (p[0] == L'>') p[0] = L')';
+ p[1] = 0;
+ StringCchCat(out, MAX_PATH, p);
+ }
+ if (whichstr > 0)
+ {
+ int islow = IsCharLowerW(fmt[1]) && IsCharLowerW(fmt[2]);
+ int ishi = IsCharUpperW(fmt[1]) && IsCharUpperW(fmt[2]);
+ wchar_t *src;
+ if (whichstr == 1) { src = artist; fmt += 8; }
+ else if (whichstr == 2) { src = album; fmt += 7; }
+ else if (whichstr == 3) { src = title; fmt += 7; }
+ else if (whichstr == 4) { src = genre; fmt += 7; }
+ else if (whichstr == 5) { src = year; fmt += 6; }
+ else if (whichstr == 6) { src= trackartist; fmt+=13; }
+ else break;
+
+ while (src && *src)
+ {
+ wchar_t p[2] = {src[0], 0};
+ if (ishi) CharUpperBuffW(p, 1);
+ else if (islow) CharLowerBuffW(p, 1);
+
+ if (p[0] == L'?' || p[0] == L'*' || p[0] == L'|') p[0] = L'_';
+ else if (p[0] == L'/' || p[0] == L'\\' || p[0] == L':') p[0] = L'-';
+ else if (p[0] == L'\"') p[0] = L'\'';
+ else if (p[0] == L'<') p[0] = L'(';
+ else if (p[0] == L'>') p[0] = L')';
+
+ src++;
+ p[1] = 0;
+ StringCchCat(out, MAX_PATH, p);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/resource1.h b/Src/Plugins/Library/ml_pmp/resource1.h
new file mode 100644
index 00000000..54dbd4ac
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resource1.h
@@ -0,0 +1,475 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_pmp.rc
+//
+#define IDS_VIDEO 1
+#define IDS_RENAME_DEVICE 2
+#define IDS_RENAME_PLAYLIST 3
+#define IDS_STRING3 3
+#define IDS_LAST_CHANGED 3
+#define IDS_CREATE_PLAYLIST 4
+#define IDCANCEL2 5
+#define IDS_DEVICE_OUT_OF_SPACE 5
+#define IDOK2 6
+#define IDS_INCOMPATABLE_FORMAT_NO_TX 6
+#define IDS_ERROR 7
+#define IDS_PHYSICALLY_REMOVE_X_TRACKS 8
+#define IDS_ARE_YOU_SURE 9
+#define IDD_VIEW_PMP_DEVICES 9
+#define IDS_SYNC_IS_IN_PROGRESS 10
+#define IDS_CANNOT_EJECT 11
+#define IDS_DELETING_TRACKS 12
+#define IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING 13
+#define IDS_SORRY 14
+#define IDS_NATS_DEVICE_MAYBE_FULL 15
+#define IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT 16
+#define IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT 17
+#define IDS_WARNING 18
+#define IDS_SONGS_TO_BE_DELETED 19
+#define IDS_SONGS_TO_BE_COPIED 20
+#define IDS_SONGS_NOT_IN_MEDIA_LIBRARY 21
+#define IDS_WAITING 22
+#define IDS_PLAYLIST_SYNCRONIZATION 23
+#define IDS_OTHER 24
+#define IDS_WORKING 25
+#define IDS_DONE 26
+#define IDS_NOTHING_TO_SYNC_UP_TO_DATE 27
+#define IDS_SYNC 28
+#define IDS_X_SONGS_WILL_BE_TRANSFERRED_TO_THE_DEVICE 29
+#define IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE 30
+#define IDS_NOT_ENOUGH_SPACE 31
+#define IDS_THIS_WILL_ADD_X_SONGS_AND_DELETE_X_SONGS 32
+#define IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK 33
+#define IDS_UNSUPPORTED 34
+#define IDS_PREFS_SYNC 35
+#define IDS_PREFS_AUTOFILL 36
+#define IDS_PREFS_TRANSCODING 37
+#define IDS_PREFS_VIEW 38
+#define IDS_EXAMPLE_FORMATTING_STRING 39
+#define IDS_CHOOSE_A_FOLDER 40
+#define IDS_COPIED_FILE_FORMAT_INFO 41
+#define IDS_COPIED_FILE_FORMAT_HELP 42
+#define IDS_ALL 43
+#define IDS_AIM_TO_AUTOFILL_DEVICE 44
+#define IDS_ARTIST 45
+#define IDS_ALBUM 46
+#define IDS_GENRE 47
+#define IDS_YEAR 48
+#define IDS_ALBUM_ARTIST 49
+#define IDS_PUBLISHER 50
+#define IDS_COMPOSER 51
+#define IDS_ARTIST_INDEX 52
+#define IDS_ALBUM_ARTIST_INDEX 53
+#define IDS_PLUGIN_HAS_NO_CONFIG_IMPLEMENTED 54
+#define IDS_DEVICE_PLUGINS 55
+#define IDS_PERMANENTLY_UNINSTALL_THIS_PLUGIN 56
+#define IDS_CONFIRMATION 57
+#define IDS_CLICK_OK_TO_CONTINUE 58
+#define IDS_RELOADING_PLUGIN 59
+#define IDS_UNKNOWN_ARTIST 60
+#define IDS_UNKKNOWN_ALBUM 61
+#define IDS_UNKNOWN 62
+#define IDS_TRANSFER 63
+#define IDS_COPY_TO_LIBRARY 64
+#define IDS_INVALID_PATH 65
+#define IDS_STARTING_TRANSFER 66
+#define IDS_DUPLICATE 67
+#define IDS_TITLE 73
+#define IDS_LENGTH 74
+#define IDS_TRACK_NUMBER 75
+#define IDS_DISC 76
+#define IDS_BITRATE 77
+#define IDS_SIZE 78
+#define IDS_PLAY_COUNT 79
+#define IDS_RATING 80
+#define IDS_LAST_PLAYED 81
+#define IDS_TRACKS 82
+#define IDS_ARTISTS 83
+#define IDS_NO_ARTIST 84
+#define IDS_ALBUMS 85
+#define IDS_GENRES 86
+#define IDS_ARTIST_INDEXES 87
+#define IDS_NO_ALBUM_ARTIST 88
+#define IDS_ALBUM_ARTIST_INDEXES 89
+#define IDS_NO_YEAR 90
+#define IDS_YEARS 91
+#define IDS_ALBUM_ARTISTS 92
+#define IDS_NO_PUBLISHER 93
+#define IDS_PUBLISHERS 94
+#define IDS_NO_COMPOSER 95
+#define IDS_COMPOSERS 96
+#define IDS_GHK_SYNC_PORTABLE_DEVICE 97
+#define IDS_GHK_AUTOFILL_PORTABLE_DEVICE 98
+#define IDS_GHK_EJECT_PORTABLE_DEVICE 99
+#define IDS_PORTABLES 100
+#define IDS_PORTABLES_PERCENT 101
+#define IDD_VIEW_PMP_PLAYLIST 102
+#define IDS_LOADING 102
+#define IDS_NAME 103
+#define IDD_VIEW_PMP_ARTISTALBUM 104
+#define IDS_CAPACITY_FREE 104
+#define IDR_CONTEXTMENUS 105
+#define IDS_TYPE 105
+#define IDS_STATUS 106
+#define IDD_EDIT_INFO 107
+#define IDS_DEVICE 107
+#define IDD_PROGRESS 108
+#define IDS_TRANFERS_PERCENT_REMAINING 108
+#define IDD_CONFIG 109
+#define IDS_RESUME 109
+#define IDS_PAUSE 110
+#define IDS_SETTING_METADATA 111
+#define IDS_TRANSFER_PERCENT 112
+#define IDS_ALL_X_WITHOUT_X 113
+#define IDS_ALL_X 114
+#define IDS_NO_ALBUM 115
+#define IDS_NO_GENRE 116
+#define IDS_TRACK 117
+#define IDS_X_ITEMS_X_AVAILABLE 118
+#define IDS_AUTOFILL_QUERY 119
+#define IDD_GETTINGMETADATA 120
+#define IDS_SYNC_QUERY 120
+#define IDD_FIND 121
+#define IDS_ALBUM_ART 121
+#define IDS_NO_IMAGE 122
+#define IDS_AVAILABLE 123
+#define IDS_OTHER2 124
+#define IDD_SYNC 125
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 125
+#define IDS_AUDIO_BUTTON_TT1 126
+#define IDS_AUDIO_BUTTON_TT2 127
+#define IDD_CONFIG_TRANSCODE 128
+#define IDS_AUDIO_BUTTON_TT3 128
+#define IDD_CONFIG_PLUGINS 129
+#define IDS_IMAGE_FILES 129
+#define IDD_CONFIG_TRANSCODING_ADVANCED 130
+#define IDS_JPEG_FILE 130
+#define IDD_AUTOFILL 131
+#define IDS_PNG_FILE 131
+#define IDS_GIF_FILE 132
+#define IDS_BMP_FILE 133
+#define IDD_CONFIG_AUTOFILL 134
+#define IDS_DELETE_PLAYLIST 134
+#define IDD_CONFIG_GLOBAL 135
+#define IDS_NEW_PLAYLIST 135
+#define IDD_VIEW_PMP_VIDEO 141
+#define IDD_CONFIG_MEDIAVIEW 142
+#define IDR_TOOL_VIEWMODE_ICON 144
+#define IDR_TOOL_ALBUMART_ICON 145
+#define IDR_TOOL_COLUMNS_ICON 146
+#define IDR_IMAGE_NOTFOUND 147
+#define IDR_DEVICE_ICON 148
+#define IDR_PLAYLIST_ICON 149
+#define IDR_VIDEO_ICON 150
+#define IDB_USB 151
+#define IDS_TRANSFERRING 151
+#define IDB_XFER_QUEUE_16 152
+#define IDS_DEVICE_CMD_PLAYLIST_NEW 152
+#define IDS_DEVICE_CMD_PLAYLIST_CREATE 152
+#define IDB_XFER_QUEUE_16_1 153
+#define IDS_DEVICE_CMD_AUTOFILL 153
+#define IDB_XFER_QUEUE_16_2 154
+#define IDS_DEVICE_CMD_EJECT 154
+#define IDB_XFER_QUEUE_16_3 155
+#define IDS_TRANSFERS 155
+#define IDS_TRANSFERS_PERCENT 156
+#define IDS_DEVICE_CMD_PREFERENCES 158
+#define IDS_DEVICE_CMD_VIEW_OPEN 159
+#define IDS_DEVICE_CMD_VIEW_OPEN_DESC 160
+#define IDS_DEVICE_CMD_SYNC_DESC 161
+#define IDS_DEVICE_CMD_EJECT_DESC 162
+#define IDS_DEVICE_CMD_RENAME_DESC 163
+#define IDS_DEVICE_CMD_AUTOFILL_DESC 164
+#define IDS_DEVICE_CMD_PLAYLIST_NEW_DESC 165
+#define IDS_DEVICE_CMD_PLAYLIST_CREATE_DESC 165
+#define IDS_DEVICE_CMD_PREFERENCES_DESC 166
+#define IDS_DEVICE_CMD_RENAME 167
+#define IDS_DEVICE_CMD_SYNC 168
+#define IDS_PORTABLE_DEVICE_TYPE 170
+#define IDS_DEVICE_CONNECTION_USB 171
+#define IDS_PODCAST_SYNC 172
+#define IDS_TRANSFERRING_DESC 173
+#define IDS_UNKNOWN_TRACK 174
+#define IDS_NUMBER 175
+#define IDS_DELETE_PLAYLIST_TITLE 176
+#define IDS_DEVICE_LOWERCASE 177
+#define IDS_KBPS 178
+#define IDS_SENDTO_DEVICES 180
+#define IDS_STRING181 181
+#define IDS_SENDTO_CLOUD 182
+#define IDS_MIME_TYPE 183
+#define IDS_ALREADY_UPLOADED 184
+#define IDS_SOURCE 185
+#define IDS_DESTINATION 186
+#define IDD_VIEW_CLOUD_ARTISTALBUM 187
+#define IDD_CLOUD_SYNC 188
+#define IDD_CONFIG_CLOUD_MEDIAVIEW 190
+#define IDD_VIEW_CLOUD_SIMPLE 191
+#define IDD_CUSTCOLUMNS 247
+#define IDD_VIEW_PMP_QUEUE 248
+#define IDD_CONFIG_PODCAST_SYNC 249
+#define IDD_CONFIG_SYNC 250
+#define IDS_DEVICE_CMD_REMOVE 251
+#define IDS_DEVICE_CMD_REMOVE_DESC 252
+#define IDD_VIEW_CLOUD_QUEUE 253
+#define IDS_LOCAL_MACHINE 253
+#define IDS_UPLOAD_CANCELLED 254
+#define IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME 255
+#define IDS_CLOUD_SOURCES 256
+#define IDS_CLOUD 257
+#define IDS_X_ITEMS_X_AVAILABLE_SLIM 258
+#define IDS_CLOUD_REMOVE_X_TRACKS 259
+#define IDS_DELETE 260
+#define IDS_REMOVE 261
+#define IDS_REMOVING_TRACKS 262
+#define IDS_DEVICE_CMD_TRANSFER 263
+#define IDS_DEVICE_CMD_TRANSFER_DESC 264
+#define IDR_TRANSFER_SMALL_ICON 265
+#define IDS_SOURCE_FILE 266
+#define IDB_TREEITEM_CLOUD 267
+#define IDS_UPLOADED 267
+#define IDB_TREEITEM_CLOUD_ADD_SOURCE 268
+#define IDS_PLAYLISTS 268
+#define IDS_SONGS 269
+#define IDS_CLOUD_TX_X_TO_Y 270
+#define IDS_CLOUD_TX_HAVE_SELECTED_X 271
+#define IDS_CLOUD_TX_OVER_LIMIT 272
+#define IDS_CLOUD_TX_NO_SEL 273
+#define IDS_CLOUD_LOCAL_LIBRARY 274
+#define IDS_CLOUD_SYNC_ALL_SONGS 275
+#define IDS_CLOUD_SYNC_ALL_PL 276
+#define IDS_CLOUD_SYNC_SEL_SONGS 277
+#define IDS_CLOUD_SYNC_SEL_PL 278
+#define IDS_CLOUD_SYNC_NONSEL_SONGS 279
+#define IDS_CLOUD_SYNC_NONSEL_PL 280
+#define IDS_SEND_TO_PL 281
+#define IDS_DEVICE_CMD_HIDE 282
+#define IDS_ADD_SOURCE 283
+#define IDS_TRACK_AVAILABLE 284
+#define IDS_UPLOAD_TO_SOURCE 285
+#define IDS_CONFIRM_QUIT 286
+#define IDS_CANCEL_TRANSFERS_AND_QUIT 287
+#define IDS_CLOUD_SYNC_PL_EMPTY 288
+#define IDS_CLOUD_SYNC_PL_SOME 289
+#define IDS_ALL_TRACKS_PLAYABLE 290
+#define IDS_NO_TRACKS_PLAYABLE 291
+#define IDS_SOME_TRACKS_PLAYABLE 292
+#define IDS_ALL_TRACKS_PLAYABLE_HERE 293
+#define IDB_TREEITEM_CLOUD_ADD_BYOS 294
+#define IDS_DATE_ADDED 294
+#define IDS_CLIENT_TYPE 295
+#define IDR_ACCELERATORS 295
+#define IDS_NOT_LOADED 296
+#define IDC_BUTTON_CUSTOM 1000
+#define IDC_PL_LIST 1000
+#define IDC_LIST1 1000
+#define IDC_LIST2 1001
+#define IDC_LIST_TRANSFERS 1002
+#define IDC_EDIT_ARTIST 1002
+#define IDC_PC_LIST 1002
+#define IDC_LIST_DEVICES 1003
+#define IDC_REFINE_TEXT 1004
+#define IDC_CHECK_ARTIST 1004
+#define IDC_CHECK_TITLE 1005
+#define IDC_CHECK_ALBUM 1006
+#define IDC_TQ_STATIC 1006
+#define IDC_QUICKSEARCH 1007
+#define IDC_BUTTON_CLEARFINISHED 1007
+#define IDC_REFINE 1008
+#define IDC_CHECK_TRACK 1008
+#define IDC_BUTTON_REMOVESELECTED 1008
+#define IDC_CHECK_GENRE 1009
+#define IDC_PROGRESS 1009
+#define IDC_DESTPATH 1009
+#define IDC_BUTTON_RETRYSELECTED 1009
+#define IDC_CHECK_DISC 1010
+#define IDC_BUTTON_PAUSETRANSFERS 1010
+#define IDC_ABORT 1010
+#define IDC_BUTTON1 1010
+#define IDC_CHECK_YEAR 1011
+#define IDC_BOOSTRATINGS 1011
+#define IDC_FILENAMEFMT 1011
+#define IDC_BUTTON_VIEWMODE 1011
+#define IDC_BUTTON_COLUMNS 1012
+#define IDC_EDIT_TITLE 1013
+#define IDC_PLUGINSLIST 1013
+#define IDC_EDIT_ALBUM 1014
+#define IDC_CONFIGPLUGIN 1014
+#define IDC_UNINSTALLPLUGIN 1015
+#define IDC_EDIT_TRACK 1016
+#define IDC_PLUGINVERS 1016
+#define IDC_EDIT_DISC 1017
+#define IDC_METADATAPROGRESS 1018
+#define IDC_CHECK_ALBUMARTIST 1018
+#define IDC_EDIT_GENRE 1019
+#define IDC_EDIT 1019
+#define IDC_FMTOUT 1019
+#define IDC_CAPTION 1020
+#define IDC_ENCFORMAT 1020
+#define IDC_EDIT_ALBUMARTIST 1020
+#define IDC_EDIT_YEAR 1021
+#define IDC_TRUESYNC_LEAVE 1021
+#define IDC_ENC_CONFIG 1021
+#define IDC_TRUESYNC_DELETE 1022
+#define IDC_CHECK_PUBLISHER 1022
+#define IDC_BUTTON_PLAY 1023
+#define IDC_TRUESYNC_COPY 1023
+#define IDC_EDIT_PUBLISHER 1023
+#define IDC_BUTTON_ENQUEUE 1024
+#define IDC_TAB1 1024
+#define IDC_CHECK_COMPOSER 1024
+#define IDC_EDIT_COMPOSER 1025
+#define IDC_LIST_ARTIST 1027
+#define IDC_ENABLETRANSCODER 1027
+#define IDC_BUTTON_EJECT 1028
+#define IDC_ADVANCED 1028
+#define IDC_CHECK_FORCE 1029
+#define IDC_LIST_ALBUM 1030
+#define IDC_FORCE_BITRATE 1030
+#define IDC_LIST_ALBUM2 1031
+#define IDC_CHECK_FORCE2 1031
+#define IDC_CHECK_FORCE_LOSSLESS 1031
+#define IDC_PL_WHITELIST 1032
+#define IDC_HDELIM 1033
+#define IDC_PL_BLACKLIST 1033
+#define IDC_LIBRARYSYNC 1034
+#define IDC_VDELIM 1035
+#define IDC_SYNCONCONNECT 1035
+#define IDC_VDELIM2 1036
+#define IDC_STATUS 1039
+#define IDC_SYNC_QUERY_STRING 1040
+#define IDC_LIST_TRACKS 1041
+#define IDC_AUTOFILL_QUERY_STRING 1041
+#define IDC_SYNCONCONNECT_TIME 1042
+#define IDC_BUTTON_SYNC 1044
+#define IDC_SEARCH_TEXT 1045
+#define IDC_COMBO_FILTER1 1045
+#define IDC_COMBO_FILTER2 1046
+#define IDC_COMBO_FILTER3 1047
+#define IDC_RADIO_FILTERS1 1048
+#define IDC_RADIO_FILTERS2 1049
+#define IDC_RADIO_FILTERS3 1050
+#define IDC_STATIC_FILTER3 1051
+#define IDC_CHECK_USECDRIP 1052
+#define IDC_CHECK_PC 1052
+#define IDC_CHECK_ALBUMART 1052
+#define IDC_STATIC_1 1053
+#define IDC_REMEMBER_SEARCH 1053
+#define IDC_STATIC_2 1054
+#define IDC_PC_ALL 1058
+#define IDC_PC_SEL 1059
+#define IDC_COMBO_PC_NUM 1060
+#define IDC_STATIC_PODTXT 1061
+#define IDC_STATIC_PODTIP 1062
+#define IDC_BUTTON3 1063
+#define IDC_CHECK_VIDEOVIEW 1063
+#define IDC_BUTTON4 1064
+#define IDC_PICTUREHOLDER 1064
+#define IDC_BUTTON_AUTOFILL 1065
+#define IDC_BUTTON5 1065
+#define IDC_ARTINFO 1065
+#define IDC_BUTTON2 1066
+#define IDC_ART_CHANGE 1067
+#define IDC_ART_CLEAR 1068
+#define IDC_BUTTON_ARTMODE 1069
+#define IDC_STATIC_PODCASTS 1070
+#define IDC_BUTTON_CLEARSEARCH 1071
+#define IDC_BUTTON_CLEARREFINE 1072
+#define IDC_DEVICE_HEADER 1074
+#define IDC_DEVICE_HEADER_SIZE 1075
+#define IDC_STATIC_FILTER2 1076
+#define IDC_SYNC_QUERY_EDIT 1077
+#define IDC_BUTTONCANCELSELECTED 1077
+#define IDC_AUTOFILL_QUERY_EDIT 1078
+#define IDC_BUTTON_SORT 1079
+#define IDC_DETAILS 1080
+#define IDC_CLOUDSYNC_ALL 1081
+#define IDC_CLOUDSYNC_SEL 1082
+#define IDC_CLOUDSYNC_NOTSEL 1083
+#define IDC_HEADER_DEVICE_NAME 1083
+#define IDC_HEADER_DEVICE_SIZE 1084
+#define IDC_HEADER_DEVICE_TRANSFER 1085
+#define IDC_HEADER_DEVICE_BAR 1086
+#define IDC_HDELIM2 1088
+#define IDC_HEADER_DEVICE_ICON 1089
+#define IDC_TX_MODE 1090
+#define IDC_SPACESLIDER 1109
+#define IDC_FILLCAPTION 1110
+#define IDC_LIST_ADD 1116
+#define IDC_LIST_REMOVE 1117
+#define IDC_LIST_ADD_PL 1118
+#define IDC_LESS 1119
+#define IDC_MORE 1120
+#define IDC_ADDLABEL 1124
+#define IDC_REMOVELABEL 1125
+#define IDC_ADD_REMSEL 1126
+#define IDC_ADD_CROPSEL 1127
+#define IDC_REM_REMSEL 1128
+#define IDC_REM_CROPSEL 1129
+#define IDC_DETAILSLABEL 1130
+#define IDC_AUTOFILLALBUMS 1131
+#define IDC_DEFS 1210
+#define ID_TRACKSLIST_PLAYSELECTION 40001
+#define ID_TRACKSLIST_ENQUEUESELECTION 40002
+#define ID_ADDTOPLAYLIST_NEWPLAYLIST 40004
+#define ID_RATE_5 40013
+#define ID_RATE_4 40014
+#define ID_RATE_3 40015
+#define ID_RATE_2 40016
+#define ID_RATE_1 40017
+#define ID_RATE_0 40018
+#define ID_TRACKSLIST_EDITSELECTEDITEMS 40019
+#define ID_TRACKSLIST_DELETE 40020
+#define ID_TRACKSLIST_SELECTALL 40021
+#define ID_TRACKSLIST_REMOVEFROMPLAYLIST 40022
+#define ID_TREEDEVICE_EJECTDEVICE 40023
+#define ID_TREEPLAYLIST_REMOVEPLAYLIST 40024
+#define ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES 40026
+#define ID_SORTPLAYLIST_ARTIST 40027
+#define ID_SORTPLAYLIST_ALBUM 40028
+#define ID_SORTPLAYLIST_TITLE 40029
+#define ID_SORTPLAYLIST_TRACK 40030
+#define ID_SORTPLAYLIST_DISC 40031
+#define ID_SORTPLAYLIST_GENRE 40032
+#define ID_SORTPLAYLIST_RATING 40033
+#define ID_SORTPLAYLIST_PLAYCOUNT 40034
+#define ID_SORTPLAYLIST_LASTPLAYED 40035
+#define ID_SORTPLAYLIST_RANDOMIZE 40036
+#define ID_SORTPLAYLIST_REVERSEPLAYLIST 40037
+#define ID_TREEPLAYLIST_RENAMEPLAYLIST 40038
+#define ID_TREEDEVICE_NEWPLAYLIST 40039
+#define ID_MAINTREEROOT_AUTOHIDEROOT 40041
+#define ID_TRACKSLIST_COPYTOLIBRARY 40042
+#define ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA 40043
+#define ID_HEADERWND_CUSTOMIZECOLUMNS 40045
+#define ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR 40046
+#define ID_ARTHEADERMENU_SMALLICON 40049
+#define ID_ARTHEADERMENU_MEDIUMICON 40050
+#define ID_ARTHEADERMENU_LARGEICON 40051
+#define ID_ARTHEADERMENU_SMALLDETAILS 40052
+#define ID_ARTHEADERMENU_LARGEDETAILS 40053
+#define ID_ARTHEADERMENU_MEDIUMDETAILS 40054
+#define ID_ARTEDITMENU_COPY 40055
+#define ID_ARTEDITMENU_PASTE 40056
+#define ID_ARTEDITMENU_DELETE 40057
+#define ID_ARTEDITMENU_SAVEAS 40058
+#define ID_ARTEDITMENU_DOWNLOAD 40059
+#define ID_MAINTREEROOT_PREFERENCES 40060
+#define ID_MAINTREEROOT_HELP 40061
+#define ID_CLOUDTRACKSLIST_CLOUDSOURCES 40062
+#define ID_Menu 40063
+#define ID_Menu40064 40064
+#define ID_CLOUDTRACKSLIST_CLOUDSOURCES40065 40065
+#define ID_CLOUDARTISTALBUMLIST_CLOUDSOURCES 40066
+#define IDS_NULLSOFT_PMP_SUPPORT 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 295
+#define _APS_NEXT_COMMAND_VALUE 40067
+#define _APS_NEXT_CONTROL_VALUE 1091
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_pmp/resources/albumArt.png b/Src/Plugins/Library/ml_pmp/resources/albumArt.png
new file mode 100644
index 00000000..9c23f46a
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/albumArt.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/columns.png b/Src/Plugins/Library/ml_pmp/resources/columns.png
new file mode 100644
index 00000000..14aace4b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/columns.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/deviceIcon.png b/Src/Plugins/Library/ml_pmp/resources/deviceIcon.png
new file mode 100644
index 00000000..3e477593
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/deviceIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/notfound.png b/Src/Plugins/Library/ml_pmp/resources/notfound.png
new file mode 100644
index 00000000..f76c0516
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/notfound.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/playlistIcon.png b/Src/Plugins/Library/ml_pmp/resources/playlistIcon.png
new file mode 100644
index 00000000..748a7aea
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/playlistIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/sync-command-small.png b/Src/Plugins/Library/ml_pmp/resources/sync-command-small.png
new file mode 100644
index 00000000..b161fc33
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/sync-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.png
new file mode 100644
index 00000000..4268c56b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.png
new file mode 100644
index 00000000..6bc33d38
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.png
new file mode 100644
index 00000000..75a16f38
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.png
new file mode 100644
index 00000000..3bb90d10
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/usb.png b/Src/Plugins/Library/ml_pmp/resources/usb.png
new file mode 100644
index 00000000..3896d1b3
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/usb.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/videoIcon.png b/Src/Plugins/Library/ml_pmp/resources/videoIcon.png
new file mode 100644
index 00000000..d1d043dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/videoIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/viewMode.png b/Src/Plugins/Library/ml_pmp/resources/viewMode.png
new file mode 100644
index 00000000..809bc1b7
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/viewMode.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp b/Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp
new file mode 100644
index 00000000..aca58562
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp
@@ -0,0 +1,675 @@
+#include "main.h"
+#include "metadata_utils.h"
+#include <strsafe.h>
+#include "../playlist/ifc_playlistloadercallback.h"
+#include "resource1.h"
+#include <shlwapi.h>
+
+#define SYNCCLOUDDIALOG_PROP L"SyncCloudDialogProp"
+
+typedef struct SyncCloudDialog
+{
+ HWND centerWindow;
+ DeviceView *device;
+ C_ItemList *libraryList;
+ C_ItemList *playlistsList;
+} SyncCloudDialog;
+
+#define SYNCCLOUDDIALOG(_hwnd) ((SyncCloudDialog*)GetPropW((_hwnd), SYNCCLOUDDIALOG_PROP))
+
+static INT_PTR CALLBACK SyncCloudDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+INT_PTR SyncCloudDialog_Show(HWND centerWindow, DeviceView *device, C_ItemList *libraryList)
+{
+ SyncCloudDialog param;
+
+ param.centerWindow = centerWindow;
+ param.device = device;
+ param.libraryList = libraryList;
+ param.playlistsList = new C_ItemList;
+
+ return WASABI_API_DIALOGBOXPARAMW(IDD_CLOUD_SYNC, plugin.hwndLibraryParent, SyncCloudDialog_DialogProc, (LPARAM)&param);
+}
+
+static BOOL SyncCloudDialog_FormatSongString(wchar_t *buffer, size_t bufferSize, const wchar_t *artist,
+ const wchar_t *title, const wchar_t *fileName)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return FALSE;
+
+ if (NULL == title || L'\0' == *title)
+ {
+ if (NULL == fileName || L'\0' == *fileName)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_TRACK, buffer, bufferSize);
+ hr = S_OK;
+ }
+ else
+ hr = StringCchCopyEx(buffer, bufferSize, fileName, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else if (NULL == artist || L'\0' == *artist)
+ {
+ hr = StringCchCopyEx(buffer, bufferSize, title, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else
+ {
+ hr = StringCchPrintfEx(buffer, bufferSize, NULL, NULL, STRSAFE_IGNORE_NULLS,
+ L"%s - %s", artist, title);
+ }
+
+ return SUCCEEDED(hr);
+}
+
+WORD WGetListboxStringExtent(HWND hList, wchar_t* psz){
+ WORD wExtent = 0;
+ HDC hDC = GetDC(hList);
+ int tab[] = {0};
+ SelectObject(hDC,(HFONT)SendMessage(hList,WM_GETFONT,0,0));
+ wExtent = LOWORD(GetTabbedTextExtent(hDC,psz,lstrlen(psz)+1,0,tab));
+ ReleaseDC(hList,hDC);
+ return wExtent;
+}
+
+DWORD fill_listbox(HWND list, wchar_t* string, DWORD width){
+ DWORD prev = width;
+ DWORD dwExtent = WGetListboxStringExtent(list,string);
+ if(prev < dwExtent){prev = dwExtent;}
+ return prev;
+}
+
+class SyncCloudItemListLoader : public ifc_playlistloadercallback
+{
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ if(pos < len)
+ {
+ if (info)
+ {
+ // look at the devices we've already uploaded to vs the destination
+ const wchar_t * devices = info->GetExtendedInfo(L"cloud_devices");
+ if (devices)
+ {
+ wchar_t* pt = wcstok((wchar_t*)devices, L"*");
+ while (pt)
+ {
+ if (_wtoi(pt) == device_id)
+ {
+ done++;
+ return;
+ }
+ pt = wcstok(NULL, L"*");
+ }
+ }
+ }
+
+ // check if the file exists, skipping if not
+ if (PathFileExistsW(filename))
+ {
+ __int64 file_size = INVALID_FILE_SIZE;
+ time_t file_time = 0;
+ GetFileSizeAndTime(filename, &file_size, &file_time);
+ if (!(file_size == INVALID_FILE_SIZE || file_size == 0))
+ {
+ total_size += file_size;
+ }
+ songs[pos].filename = _wcsdup(filename);
+ pos++;
+ }
+ else
+ {
+ done++;
+ }
+ }
+ }
+ int64_t total_size;
+ int pos, len, done, device_id;
+ songMapping * songs;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS SyncCloudItemListLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+static BOOL SyncCloudDialog_PopulatePlaylistLists(HWND hwnd)
+{
+ SyncCloudDialog *self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ HWND hList = GetDlgItem(hwnd, IDC_LIST_ADD_PL);
+ if(NULL != hList)
+ {
+ DWORD list_width = 0;
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ // first collect playlists without metadata
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_PLAYLIST_COUNT);
+ for(int i = 0, count = 0; i < playlistsnum; i++)
+ {
+ SyncCloudItemListLoader* list = new SyncCloudItemListLoader();
+ mlPlaylistInfo* playlist = (mlPlaylistInfo*)calloc(sizeof(mlPlaylistInfo),1);
+ playlist->size = sizeof(mlPlaylistInfo);
+ playlist->playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)playlist, ML_IPC_PLAYLIST_INFO);
+
+ if (playlist->numItems > 0)
+ {
+ wchar_t buf[256] = {0};
+ list->device_id = self->device->dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID, 0, 0, 0);
+ list->total_size = list->pos = list->done = 0;
+ list->len = playlist->numItems;
+ list->songs = (songMapping*)calloc(sizeof(songMapping), list->len);
+ playlistManager->Load(playlist->filename, list);
+ list->len -= list->done;
+ if (list->len > 0)
+ {
+ if (list->done > 0)
+ {
+ StringCchPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_CLOUD_SYNC_PL_SOME), playlist->playlistName, list->done);
+ }
+ int pos = SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)(buf[0] ? buf : playlist->playlistName));
+ SendMessage(hList, LB_SETITEMDATA, pos, count);
+ self->playlistsList->Add(list);
+ count++;
+ }
+ else
+ {
+ StringCchPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_CLOUD_SYNC_PL_EMPTY), playlist->playlistName);
+ int pos = SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)buf);
+ SendMessage(hList, LB_SETITEMDATA, pos, -1);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ SendMessage(hList, LB_SETHORIZONTALEXTENT, list_width, 0L);
+ }
+
+ return TRUE;
+}
+
+static BOOL SyncCloudDialog_PopulateTrackLists(HWND hwnd)
+{
+ SyncCloudDialog *self;
+ HWND hList;
+ wchar_t buffer[1024] = {0};
+ int index, count;
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ hList = GetDlgItem(hwnd, IDC_LIST_ADD);
+ if(NULL != hList)
+ {
+ DWORD list_width = 0;
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ count = (NULL != self->libraryList) ? self->libraryList->GetSize() : 0;
+ if (0 != count)
+ {
+ itemRecordW *record;
+
+ SendMessage(hList, LB_SETCOUNT , (WPARAM)count, 0L);
+
+ for(index = 0; index < count; index++)
+ {
+ record = (itemRecordW*)self->libraryList->Get(index);
+ if (NULL != record &&
+ FALSE != SyncCloudDialog_FormatSongString(buffer, ARRAYSIZE(buffer),
+ record->artist, record->title, record->filename))
+ {
+ SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)buffer);
+ list_width = fill_listbox(hList,buffer,list_width);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ SendMessage(hList, LB_SETHORIZONTALEXTENT, list_width, 0L);
+ }
+
+ return TRUE;
+}
+
+static int SyncCloudDialog_ReadTransferOptions(SyncCloudDialog *self)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ return config->ReadInt(L"CloudSyncMode", 0);
+ }
+
+ return 0;
+}
+
+static BOOL SyncCloudDialog_WriteTransferOptions(SyncCloudDialog *self, int mode)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ config->WriteInt(L"CloudSyncMode", mode);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void SyncCloudDialog_UpdateTransferOptions(HWND hwnd, int mode)
+{
+ const int control[] =
+ {
+ IDC_CLOUDSYNC_ALL,
+ IDC_CLOUDSYNC_SEL,
+ IDC_CLOUDSYNC_NOTSEL
+ };
+
+ const int controls[] =
+ {
+ IDS_CLOUD_SYNC_ALL_SONGS,
+ IDS_CLOUD_SYNC_SEL_SONGS,
+ IDS_CLOUD_SYNC_NONSEL_SONGS
+ };
+ const int controls_pl[] =
+ {
+ IDS_CLOUD_SYNC_ALL_PL,
+ IDS_CLOUD_SYNC_SEL_PL,
+ IDS_CLOUD_SYNC_NONSEL_PL
+ };
+
+ switch (mode)
+ {
+ case 0:
+ for (int i = 0; i < sizeof(controls_pl)/sizeof(controls_pl[0]); i++)
+ {
+ SetDlgItemText(hwnd, control[i], WASABI_API_LNGSTRINGW(controls_pl[i]));
+ }
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD_PL), TRUE);
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD), FALSE);
+ break;
+
+ case 1:
+ for (int i = 0; i < sizeof(controls)/sizeof(controls[0]); i++)
+ {
+ SetDlgItemText(hwnd, control[i], WASABI_API_LNGSTRINGW(controls[i]));
+ }
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD_PL), FALSE);
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD), TRUE);
+ break;
+ }
+}
+
+static BOOL SyncCloudDialog_UpdateCaption(HWND hwnd)
+{
+ SyncCloudDialog *self;
+ HWND captionWindow, hList;
+ wchar_t buffer[1024] = {0};
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ captionWindow = GetDlgItem(hwnd, IDC_DETAILS);
+ if (NULL == captionWindow)
+ return FALSE;
+
+ int mode = SyncCloudDialog_ReadTransferOptions(self);
+ hList = GetDlgItem(hwnd, (mode ? IDC_LIST_ADD : IDC_LIST_ADD_PL));
+ if (NULL == hList)
+ return FALSE;
+
+ int64_t total_size = 0, sel_size = 0,
+ device_size = self->device->dev->getDeviceCapacityAvailable();
+
+ int all = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_ALL) == BST_CHECKED);
+ int sel = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_SEL) == BST_CHECKED);
+ if (mode)
+ {
+ for (int i = 0; i < self->libraryList->GetSize(); i++)
+ {
+ if (all || SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ itemRecordW *record = (itemRecordW *)self->libraryList->Get(i);
+ total_size += record->filesize * 1024;
+ sel_size += 1;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < SendMessage(hList, LB_GETCOUNT, 0, 0); i++)
+ {
+ if (all || SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ int item = SendMessage(hList, LB_GETITEMDATA, i, 0);
+ if (item != -1)
+ {
+ SyncCloudItemListLoader *list = (SyncCloudItemListLoader *)self->playlistsList->Get(item);
+ total_size += list->total_size;
+ sel_size += list->len;
+ }
+ }
+ }
+ }
+
+ wchar_t buf[64] = {0}, buf2[64] = {0}, buf3[128] = {0}, buf4[128] = {0},
+ buffer2[256] = {0}, local_lib[128] = {0};
+ WASABI_API_LNG->FormattedSizeString(buf, ARRAYSIZE(buf), total_size);
+ WASABI_API_LNG->FormattedSizeString(buf2, ARRAYSIZE(buf2), device_size);
+ int64_t over_limit = (total_size - device_size);
+ if (over_limit > 0)
+ {
+ WASABI_API_LNG->FormattedSizeString(buf4, ARRAYSIZE(buf4), over_limit);
+ }
+ self->device->GetDisplayName(buf3, ARRAYSIZE(buf3));
+
+ int over_size = (device_size > 0 && total_size <= device_size);
+
+ // enable the transfer button as applicable
+ EnableWindow(GetDlgItem(hwnd, IDOK), sel_size && over_size);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_LOCAL_LIBRARY, local_lib, 128);
+
+ StringCchPrintf(buffer, ARRAYSIZE(buffer),
+ WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_X_TO_Y),
+ local_lib, buf3);
+ SetWindowText(hwnd, buffer);
+
+ StringCchPrintf(buffer, ARRAYSIZE(buffer), WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_HAVE_SELECTED_X), local_lib, buf3, sel_size, buf, buf3, buf2);
+
+ if (sel_size && over_limit > 0)
+ {
+ StringCchPrintf(buffer2, ARRAYSIZE(buffer2), WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_OVER_LIMIT), buf4, buf3);
+ StringCchCat(buffer, ARRAYSIZE(buffer), buffer2);
+ }
+ else if (!sel_size)
+ {
+ StringCchPrintf(buffer, ARRAYSIZE(buffer), WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_NO_SEL), local_lib, buf3);
+ }
+
+ SendMessage(captionWindow, WM_SETTEXT, 0, (LPARAM)buffer);
+ return TRUE;
+}
+
+static void SyncCloudDialog_GenerateList(HWND hwnd)
+{
+ SyncCloudDialog *self;
+ HWND hList;
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return;
+
+ int mode = SyncCloudDialog_ReadTransferOptions(self);
+ hList = GetDlgItem(hwnd, (mode ? IDC_LIST_ADD : IDC_LIST_ADD_PL));
+ if (NULL == hList)
+ return;
+
+ if (mode)
+ {
+ if ((IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_ALL) == BST_UNCHECKED))
+ {
+ int sel = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_SEL) == BST_CHECKED),
+ current = 0, l = self->libraryList->GetSize();
+ for (int i = 0; i < l; i++)
+ {
+ if (SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ current++;
+ }
+ else
+ {
+ self->libraryList->Del(current);
+ }
+ }
+ }
+ }
+ else
+ {
+ // no need for this so clear out
+ int l = self->libraryList->GetSize();
+ for (int i = 0; i < l; i++)
+ {
+ self->libraryList->Del(0);
+ }
+
+ int all = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_ALL) == BST_CHECKED);
+ int sel = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_SEL) == BST_CHECKED);
+ for (int i = 0; i < SendMessage(hList, LB_GETCOUNT, 0, 0); i++)
+ {
+ if (all || SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ int item = SendMessage(hList, LB_GETITEMDATA, i, 0);
+ if (item != -1)
+ {
+ SyncCloudItemListLoader *list = (SyncCloudItemListLoader *)self->playlistsList->Get(item);
+ if (list)
+ {
+ for (int j = 0; j < list->len; j++)
+ {
+ songMapping* song = &list->songs[j];
+ if (song)
+ {
+ itemRecordW *result = AGAVE_API_MLDB->GetFile(song->filename);
+ song->ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1);
+ if (result)
+ {
+ copyRecord(song->ice, result);
+ AGAVE_API_MLDB->FreeRecord(result);
+ }
+ else
+ {
+ filenameToItemRecord(song->filename, song->ice); // ugh. Disk intensive.
+ }
+ if (song->ice)
+ {
+ self->libraryList->Add(song->ice);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+typedef enum DeviceSyncPolicy
+{
+ DeviceSyncPolicy_CopyAllTracks = 0,
+ DeviceSyncPolicy_CopySelTracks = 1,
+ DeviceSyncPolicy_CopyNotSelTracks = 2,
+} DeviceSyncPolicy;
+
+static DeviceSyncPolicy SyncCloudDialog_ReadDeviceSyncPolicy(SyncCloudDialog *self)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ return (DeviceSyncPolicy)config->ReadInt(L"CloudSync", (int)DeviceSyncPolicy_CopyAllTracks);
+ }
+
+ return DeviceSyncPolicy_CopyAllTracks;
+}
+
+static BOOL SyncCloudDialog_WriteDeviceSyncPolicy(SyncCloudDialog *self, DeviceSyncPolicy policy)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ config->WriteInt(L"CloudSync", (int)policy);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL SyncCloudDialog_SelectDeviceSyncPolicy(HWND hwnd, DeviceSyncPolicy policy)
+{
+ SyncCloudDialog *self;
+ int policyControl;
+
+ const int controls[] =
+ {
+ IDC_CLOUDSYNC_ALL,
+ IDC_CLOUDSYNC_SEL,
+ IDC_CLOUDSYNC_NOTSEL
+ };
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ switch(policy)
+ {
+ case DeviceSyncPolicy_CopyAllTracks:
+ policyControl = IDC_CLOUDSYNC_ALL;
+ break;
+ case DeviceSyncPolicy_CopySelTracks:
+ policyControl = IDC_CLOUDSYNC_SEL;
+ break;
+ case DeviceSyncPolicy_CopyNotSelTracks:
+ policyControl = IDC_CLOUDSYNC_NOTSEL;
+ break;
+ default:
+ return FALSE;
+ }
+
+ CheckRadioButton(hwnd, controls[0], controls[2], policyControl);
+
+ return TRUE;
+}
+
+static INT_PTR SyncCloudDialog_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ SyncCloudDialog *self;
+ C_Config *config;
+
+ self = (SyncCloudDialog*)param;
+
+ if (NULL == self ||
+ FALSE == SetProp(hwnd, SYNCCLOUDDIALOG_PROP, self))
+ {
+ EndDialog(hwnd, -1);
+ return 0;
+ }
+
+ config = self->device->config;
+
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, SyncCloudDialog_ReadDeviceSyncPolicy(self));
+
+ if (FALSE != CenterWindow(hwnd, self->centerWindow))
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+
+ int mode = SyncCloudDialog_ReadTransferOptions(self);
+ SendDlgItemMessageW(hwnd, IDC_TX_MODE, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(IDS_PLAYLISTS));
+ SendDlgItemMessageW(hwnd, IDC_TX_MODE, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(IDS_SONGS));
+ SendDlgItemMessageW(hwnd, IDC_TX_MODE, CB_SETCURSEL, (WPARAM)mode, 0);
+
+ SyncCloudDialog_UpdateTransferOptions(hwnd, mode);
+ SyncCloudDialog_PopulateTrackLists(hwnd);
+ SyncCloudDialog_PopulatePlaylistLists(hwnd);
+ SyncCloudDialog_UpdateCaption(hwnd);
+
+ return 0;
+}
+
+static void SyncCloudDialog_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, SYNCCLOUDDIALOG_PROP);
+}
+
+static void SyncCloudDialog_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ SyncCloudDialog *self;
+ C_Config *config;
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return;
+
+ config = self->device->config;
+
+ switch(commandId)
+ {
+ case IDC_TX_MODE:
+ if (CBN_SELCHANGE == eventId)
+ {
+ int mode = SendDlgItemMessage(hwnd, commandId, CB_GETCURSEL, 0, 0);
+ SyncCloudDialog_WriteTransferOptions(self, mode);
+ SyncCloudDialog_UpdateTransferOptions(hwnd, mode);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_CLOUDSYNC_ALL:
+ if (BN_CLICKED == eventId)
+ {
+ SyncCloudDialog_WriteDeviceSyncPolicy(self, DeviceSyncPolicy_CopyAllTracks);
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopyAllTracks);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_CLOUDSYNC_SEL:
+ if (BN_CLICKED == eventId)
+ {
+ SyncCloudDialog_WriteDeviceSyncPolicy(self, DeviceSyncPolicy_CopySelTracks);
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopySelTracks);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_CLOUDSYNC_NOTSEL:
+ if (BN_CLICKED == eventId)
+ {
+ SyncCloudDialog_WriteDeviceSyncPolicy(self, DeviceSyncPolicy_CopyNotSelTracks);
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopyNotSelTracks);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDOK:
+ SyncCloudDialog_GenerateList(hwnd);
+ EndDialog(hwnd, IDOK);
+ break;
+
+ case IDCANCEL:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+
+ case IDC_LIST_ADD:
+ case IDC_LIST_ADD_PL:
+ if (eventId == LBN_SELCHANGE)
+ {
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+ }
+}
+
+static INT_PTR CALLBACK SyncCloudDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return SyncCloudDialog_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: SyncCloudDialog_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: SyncCloudDialog_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/syncCloudDialog.h b/Src/Plugins/Library/ml_pmp/syncCloudDialog.h
new file mode 100644
index 00000000..8c93c569
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncCloudDialog.h
@@ -0,0 +1,17 @@
+#ifndef _NULLSOFT_WINAMP_ML_PMP_SYNC_CLOUD_DIALOG_HEADER
+#define _NULLSOFT_WINAMP_ML_PMP_SYNC_CLOUD_DIALOG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class DeviceView;
+class C_ItemList;
+
+INT_PTR SyncCloudDialog_Show(HWND centerWindow, DeviceView *device,
+ C_ItemList *libraryList/*, C_ItemList *deviceList,
+ BOOL autofillMode*/);
+
+#endif //_NULLSOFT_WINAMP_ML_PMP_SYNC_CLOUD_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/syncDialog.cpp b/Src/Plugins/Library/ml_pmp/syncDialog.cpp
new file mode 100644
index 00000000..08badf7c
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncDialog.cpp
@@ -0,0 +1,443 @@
+#include "main.h"
+#include <strsafe.h>
+
+#define SYNCDIALOG_PROP L"SyncDialogProp"
+
+typedef struct SyncDialog
+{
+ HWND centerWindow;
+ DeviceView *device;
+ C_ItemList *libraryList;
+ C_ItemList *deviceList;
+ BOOL autofillMode;
+} SyncDialog;
+
+#define SYNCDIALOG(_hwnd) ((SyncDialog*)GetPropW((_hwnd), SYNCDIALOG_PROP))
+
+static INT_PTR CALLBACK
+SyncDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+INT_PTR
+SyncDialog_Show(HWND centerWindow, DeviceView *device,
+ C_ItemList *libraryList, C_ItemList *deviceList, BOOL autofillMode)
+{
+ SyncDialog param;
+
+ param.centerWindow = centerWindow;
+ param.device = device;
+ param.libraryList = libraryList;
+ param.deviceList = deviceList;
+ param.autofillMode = autofillMode;
+
+ return WASABI_API_DIALOGBOXPARAMW(IDD_SYNC, plugin.hwndLibraryParent, SyncDialog_DialogProc, (LPARAM)&param);
+}
+
+static BOOL
+SyncDialog_FormatSongString(wchar_t *buffer, size_t bufferSize,
+ const wchar_t *artist, const wchar_t *title, const wchar_t *fileName)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return FALSE;
+
+ if (NULL == title || L'\0' == *title)
+ {
+ if (NULL == fileName || L'\0' == *fileName)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_TRACK, buffer, bufferSize);
+ hr = S_OK;
+ }
+ else
+ hr = StringCchCopyEx(buffer, bufferSize, fileName, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else if (NULL == artist || L'\0' == *artist)
+ {
+ hr = StringCchCopyEx(buffer, bufferSize, title, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else
+ {
+ hr = StringCchPrintfEx(buffer, bufferSize, NULL, NULL, STRSAFE_IGNORE_NULLS,
+ L"%s - %s", artist, title);
+ }
+
+ return SUCCEEDED(hr);
+}
+
+static BOOL
+SyncDialog_PopulateTrackLists(HWND hwnd)
+{
+ SyncDialog *self;
+ HWND hList;
+ wchar_t buffer[1024] = {0};
+ int index, count;
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ hList = GetDlgItem(hwnd, IDC_LIST_ADD);
+ if(NULL != hList)
+ {
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ count = (NULL != self->libraryList) ? self->libraryList->GetSize() : 0;
+ if (0 != count)
+ {
+ SendMessage(hList, LB_SETCOUNT , (WPARAM)count, 0L);
+
+ for(index = 0; index < count; index++)
+ {
+ itemRecordW *record = (itemRecordW*)self->libraryList->Get(index);
+ if (NULL != record &&
+ FALSE != SyncDialog_FormatSongString(buffer, ARRAYSIZE(buffer),
+ record->artist, record->title, record->filename))
+ {
+ SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)buffer);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ }
+
+ hList = GetDlgItem(hwnd, IDC_LIST_REMOVE);
+ if(NULL != hList)
+ {
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ count = (NULL != self->deviceList) ? self->deviceList->GetSize() : 0;
+ if (0 != count)
+ {
+ Device *device;
+ wchar_t artist[256] = {0}, title[256] = {0};
+
+ SendMessage(hList, LB_SETCOUNT , (WPARAM)count, 0L);
+
+ device = self->device->dev;
+
+ for(index = 0; index < count; index++)
+ {
+ songid_t songId = (songid_t)self->deviceList->Get(index);
+
+ device->getTrackArtist(songId, artist,ARRAYSIZE(artist));
+ device->getTrackTitle(songId, title, ARRAYSIZE(title));
+
+ if (FALSE != SyncDialog_FormatSongString(buffer, ARRAYSIZE(buffer),
+ artist, title, NULL))
+ {
+ SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)buffer);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ }
+
+ return TRUE;
+}
+
+static BOOL
+SyncDialog_UpdateCaption(HWND hwnd)
+{
+ SyncDialog *self;
+ HWND captionWindow;
+ int resourceId;
+ wchar_t buffer[1024] = {0}, format[1024] = {0};
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ captionWindow = GetDlgItem(hwnd, IDC_CAPTION);
+ if (NULL == captionWindow)
+ return FALSE;
+
+ resourceId = (FALSE == self->autofillMode) ?
+ IDS_X_SONGS_WILL_BE_TRANSFERRED_TO_THE_DEVICE :
+ IDS_THIS_WILL_ADD_X_SONGS_AND_DELETE_X_SONGS;
+
+
+ WASABI_API_LNGSTRINGW_BUF(resourceId, format, ARRAYSIZE(format));
+
+
+ if (FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), format,
+ self->libraryList->GetSize(), self->deviceList->GetSize())))
+ {
+ return FALSE;
+ }
+
+ SendMessage(captionWindow, WM_SETTEXT, 0, (LPARAM)buffer);
+ return TRUE;
+}
+
+typedef enum DeviceSyncPolicy
+{
+ DeviceSyncPolicy_LeaveTracks = 0,
+ DeviceSyncPolicy_DeleteTracks = 1,
+ DeviceSyncPolicy_CopyTracks = 2,
+} DeviceSyncPolicy;
+
+static BOOL
+SyncDialog_GetDeviceSyncPolicyEnabled(SyncDialog *self)
+{
+ if (NULL != self &&
+ FALSE == self->autofillMode &&
+ 0 != self->deviceList->GetSize())
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static DeviceSyncPolicy
+SyncDialog_ReadDeviceSyncPolicy(SyncDialog *self)
+{
+ if (FALSE != SyncDialog_GetDeviceSyncPolicyEnabled(self) &&
+ NULL != self->device &&
+ NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ return (DeviceSyncPolicy)config->ReadInt(L"TrueSync", (int)DeviceSyncPolicy_LeaveTracks);
+ }
+
+ return DeviceSyncPolicy_LeaveTracks;
+}
+
+static BOOL
+SyncDialog_WriteDeviceSyncPolicy(SyncDialog *self, DeviceSyncPolicy policy)
+{
+ if (FALSE != SyncDialog_GetDeviceSyncPolicyEnabled(self) &&
+ NULL != self->device &&
+ NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ config->WriteInt(L"TrueSync", (int)policy);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL
+SyncDialog_SelectDeviceSyncPolicy(HWND hwnd, DeviceSyncPolicy policy)
+{
+ SyncDialog *self;
+ HWND hControl;
+ size_t index;
+ int policyControl;
+ int checkMode;
+ int labelText;
+ BOOL enableControls;
+
+ const int controls[] =
+ {
+ IDC_TRUESYNC_LEAVE,
+ IDC_TRUESYNC_DELETE,
+ IDC_TRUESYNC_COPY
+ };
+
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ switch(policy)
+ {
+ case DeviceSyncPolicy_LeaveTracks:
+ policyControl = IDC_TRUESYNC_LEAVE;
+ labelText = IDS_SONGS_NOT_IN_MEDIA_LIBRARY;
+ break;
+ case DeviceSyncPolicy_DeleteTracks:
+ policyControl = IDC_TRUESYNC_DELETE;
+ labelText = IDS_SONGS_TO_BE_DELETED;
+ break;
+ case DeviceSyncPolicy_CopyTracks:
+ policyControl = IDC_TRUESYNC_COPY;
+ labelText = IDS_SONGS_TO_BE_COPIED;
+ break;
+ default:
+ return FALSE;
+ }
+
+ enableControls = SyncDialog_GetDeviceSyncPolicyEnabled(self);
+
+ for (index = 0; index < ARRAYSIZE(controls); index++)
+ {
+ hControl = GetDlgItem(hwnd, controls[index]);
+ if (NULL == hControl)
+ continue;
+
+ checkMode = (controls[index] == policyControl) ?
+ BST_CHECKED :
+ BST_UNCHECKED;
+
+ SendMessage(hControl, BM_SETCHECK, (WPARAM)checkMode, 0L);
+ EnableWindow(hControl, enableControls);
+ }
+
+ hControl = GetDlgItem(hwnd, IDC_REMOVELABEL);
+ if (NULL != hControl)
+ {
+ wchar_t buffer[256] = {0};
+ WASABI_API_LNGSTRINGW_BUF(labelText, buffer, ARRAYSIZE(buffer));
+ SendMessage(hControl, WM_SETTEXT, 0, (LPARAM)buffer);
+ }
+
+ return TRUE;
+}
+
+static INT_PTR
+SyncDialog_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ SyncDialog *self;
+ C_Config *config;
+
+ self = (SyncDialog*)param;
+
+ if (NULL == self ||
+ FALSE == SetProp(hwnd, SYNCDIALOG_PROP, self))
+ {
+ EndDialog(hwnd, -1);
+ return 0;
+ }
+
+ config = self->device->config;
+
+ SyncDialog_UpdateCaption(hwnd);
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, SyncDialog_ReadDeviceSyncPolicy(self));
+
+
+ if(config->ReadInt(L"syncOnConnect", self->device->SyncConnectionDefault) == (self->autofillMode ? 2:1))
+ CheckDlgButton(hwnd, IDC_SYNCONCONNECT, BST_CHECKED);
+
+ SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_LESS,0), NULL);
+
+ if (FALSE != CenterWindow(hwnd, self->centerWindow))
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+
+ SyncDialog_PopulateTrackLists(hwnd);
+
+ return 0;
+}
+
+static void
+SyncDialog_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, SYNCDIALOG_PROP);
+}
+
+static void
+SyncDialog_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ SyncDialog *self;
+ C_Config *config;
+ BOOL enableRadios;
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return;
+
+ config = self->device->config;
+
+ enableRadios = ((!self->autofillMode) && self->deviceList->GetSize());
+
+ switch(commandId)
+ {
+ case IDC_ADD_REMSEL:
+ case IDC_REM_REMSEL:
+ case IDC_ADD_CROPSEL:
+ case IDC_REM_CROPSEL:
+ {
+ bool rem = (commandId == IDC_ADD_REMSEL || commandId == IDC_REM_REMSEL);
+ bool addlist = (commandId == IDC_ADD_REMSEL || commandId == IDC_ADD_CROPSEL);
+ HWND box = GetDlgItem(hwnd,addlist?IDC_LIST_ADD:IDC_LIST_REMOVE);
+ C_ItemList * list = addlist?self->libraryList:self->deviceList;
+ int l=SendMessage(box,LB_GETCOUNT,0,0);
+ int current=0;
+ for(int i=0; i<l; ++i)
+ {
+ if((SendMessage(box,LB_GETSEL,current,0)!=0) == rem)
+ {
+ SendMessage(box,LB_DELETESTRING,current,0);
+ list->Del(current);
+ }
+ else current++;
+ }
+ SyncDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_TRUESYNC_DELETE:
+ if (BN_CLICKED == eventId)
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_DeleteTracks);
+ break;
+ case IDC_TRUESYNC_COPY:
+ if (BN_CLICKED == eventId)
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopyTracks);
+ break;
+ case IDC_TRUESYNC_LEAVE:
+ if (BN_CLICKED == eventId)
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_LeaveTracks);
+ break;
+
+ case IDC_MORE:
+ case IDC_LESS:
+ {
+ bool more = commandId == IDC_MORE;
+ int show = more?SW_SHOW:SW_HIDE;
+ int hide = more?SW_HIDE:SW_SHOW;
+ ShowWindow(GetDlgItem(hwnd,IDCANCEL),hide);
+ ShowWindow(GetDlgItem(hwnd,IDOK),hide);
+ ShowWindow(GetDlgItem(hwnd,IDC_MORE),hide);
+ ShowWindow(GetDlgItem(hwnd,IDC_LESS),show);
+ ShowWindow(GetDlgItem(hwnd,IDCANCEL2),show);
+ ShowWindow(GetDlgItem(hwnd,IDOK2),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_ADDLABEL),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_REMOVELABEL),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_LIST_ADD),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_LIST_REMOVE),show);
+ RECT r, r1, r2;
+ GetWindowRect(hwnd,&r);
+ GetWindowRect(GetDlgItem(hwnd,more?IDC_MORE:IDC_LESS),&r1);
+ GetWindowRect(GetDlgItem(hwnd,more?IDCANCEL2:IDCANCEL),&r2);
+ SetWindowPos(hwnd,HWND_TOP,r.left,r.top,
+ r2.right - r.left + (r1.left - r.left),
+ r2.bottom - r.top + (r1.left - r.left), 0);
+ }
+ break;
+ case IDOK2:
+ case IDOK:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwnd,IDC_SYNCONCONNECT)?(self->autofillMode?2:1):0);
+
+ if(enableRadios)
+ config->WriteInt(L"TrueSync",IsDlgButtonChecked(hwnd,IDC_TRUESYNC_LEAVE)?0:(IsDlgButtonChecked(hwnd,IDC_TRUESYNC_DELETE)?1:2));
+
+ EndDialog(hwnd, IDOK);
+ break;
+
+ case IDCANCEL2:
+ case IDCANCEL:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+ }
+}
+
+static INT_PTR CALLBACK
+SyncDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return SyncDialog_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: SyncDialog_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: SyncDialog_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/syncDialog.h b/Src/Plugins/Library/ml_pmp/syncDialog.h
new file mode 100644
index 00000000..82ce32a0
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncDialog.h
@@ -0,0 +1,20 @@
+#ifndef _NULLSOFT_WINAMP_ML_PMP_SYNC_DIALOG_HEADER
+#define _NULLSOFT_WINAMP_ML_PMP_SYNC_DIALOG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class DeviceView;
+class C_ItemList;
+
+INT_PTR
+SyncDialog_Show(HWND centerWindow,
+ DeviceView *device,
+ C_ItemList *libraryList,
+ C_ItemList *deviceList,
+ BOOL autofillMode);
+
+#endif //_NULLSOFT_WINAMP_ML_PMP_SYNC_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transcoder.h b/Src/Plugins/Library/ml_pmp/transcoder.h
new file mode 100644
index 00000000..2aad4ac7
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transcoder.h
@@ -0,0 +1,38 @@
+#ifndef _TRANSCODER_H_
+#define _TRANSCODER_H_
+
+class Transcoder {
+protected:
+ Transcoder(){}
+ virtual ~Transcoder(){}
+public:
+ // done at init()
+ virtual void LoadConfigProfile(wchar_t *profile)=0; // deprecated, do not call
+ virtual void AddAcceptableFormat(wchar_t *format)=0; // eg, L"mp3" or L"wma"
+ virtual void AddAcceptableFormat(unsigned int format)=0; // eg, mmioFOURCC('M','4','A',' ')
+
+ // done when file is added to transfer queue
+ // returns:
+ // -1 for can't transcode
+ // output file size estimate if can transcode
+ // if ext is supplied, it should be a buffer with space for 5 characters, and will be filled with
+ // the output file type file extention, eg, L".mp3"
+ virtual int CanTranscode(wchar_t *file, wchar_t *ext = NULL, int length = -1)=0;
+
+ // false if no transcoding needed
+ virtual bool ShouldTranscode(wchar_t *file)=0;
+
+ // done just before transfer OR in background after file is added to queue
+ // extention is added to outputFile, allow 5 extra chars
+ // callback, callbackcontext and killswitch should be similar to those passed by ml_pmp
+ // return 0 for success, -1 for failed or cancelled
+ virtual int TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callback)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption=L"Transcoding %d%%")=0;
+
+
+ // get a filename which can be used as a staging area.
+ // ext should be for example L".mp3"
+ // make sure filename is a buffer at least MAX_PATH characters long.
+ virtual void GetTempFilePath(const wchar_t *ext, wchar_t *filename)=0;
+};
+
+#endif //_TRANSCODER_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp b/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp
new file mode 100644
index 00000000..571c0289
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp
@@ -0,0 +1,539 @@
+#include "api__ml_pmp.h"
+#include "transcoder_imp.h"
+#include "nu/ns_wc.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <mmiscapi.h>
+
+extern HWND CreateDummyWindow();
+
+static std::vector<TranscoderImp*> transcoders;
+
+LRESULT CALLBACK TranscodeMsgProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_WA_IPC && ( lParam == IPC_CB_CONVERT_STATUS || lParam == IPC_CB_CONVERT_DONE ) )
+ {
+ for ( TranscoderImp *t : transcoders )
+ {
+ if ( t->cfs.callbackhwnd == hwnd )
+ {
+ t->TranscodeProgress( (int)wParam, lParam == IPC_CB_CONVERT_DONE );
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void fourccToString(unsigned int f, wchar_t * str) {
+ wchar_t s[4] = {(wchar_t)(f&0xFF),(wchar_t)((f>>8)&0xFF),(wchar_t)((f>>16)&0xFF),0};
+ wcsncpy(str,s,4);
+ CharLower(str);
+}
+
+static unsigned int stringToFourcc(const wchar_t * str) {
+ FOURCC cc = 0;
+ char *ccc = (char *)&cc;
+ // unrolled loop (this function gets called a lot on sync and autofill)
+ if (str[0])
+ {
+ ccc[0] = (char)str[0];
+ if (str[1])
+ {
+ ccc[1] = (char)str[1];
+ if (str[2])
+ {
+ ccc[2] = (char)str[2];
+ if (str[3])
+ {
+ ccc[3] = (char)str[3];
+ }
+ }
+ }
+ }
+ CharUpperBuffA(ccc, 4);
+ return cc;
+}
+
+static bool fourccEqual(unsigned int a, unsigned int b) {
+ if((a & 0xFF000000) == 0 || (b & 0xFF000000) == 0)
+ return (a & 0x00FFFFFF) == (b & 0x00FFFFFF);
+ return a == b;
+}
+
+class TranscodeProfileCache {
+public:
+ unsigned int inputformat;
+ unsigned int outputformat;
+ int outputbitrate;
+ TranscodeProfileCache(unsigned int inputformat,unsigned int outputformat,int outputbitrate) :
+ inputformat(inputformat), outputformat(outputformat),outputbitrate(outputbitrate){}
+};
+
+static void enumProc(intptr_t user_data, const char *desc, int fourcc)
+{
+ ((FormatList *)user_data)->push_back(new EncodableFormat((unsigned int)fourcc,AutoWide(desc)));
+}
+
+static void BuildEncodableFormatsList(FormatList &list, HWND winampWindow, Device *device)
+{
+ converterEnumFmtStruct e = {enumProc,(intptr_t)&list};
+ SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS);
+
+ // filter out unacceptable formats
+ int i = list.size();
+ while (i--)
+ {
+ if (device && device->extraActions(DEVICE_VETO_ENCODER, list[i]->fourcc, 0, 0) == 1)
+ {
+ list.erase(list.begin() + i);
+ }
+ }
+}
+
+static CRITICAL_SECTION csTranscoder;
+
+void TranscoderImp::init() {
+ InitializeCriticalSection(&csTranscoder);
+}
+
+void TranscoderImp::quit() {
+ DeleteCriticalSection(&csTranscoder);
+}
+
+TranscoderImp::TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device)
+: device(device), config(config), winampParent(winampParent), hInst(hInst)
+{
+ EnterCriticalSection(&csTranscoder);
+
+ transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
+ transrate = !!config->ReadInt(L"transrate",0);
+ translossless = !!config->ReadInt(L"translossless",0);
+ TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
+
+ int current_fourcc = config->ReadInt(L"lastusedencoder", 0);
+ if (current_fourcc == 0)
+ {
+ // TODO: ask for default from plugin
+ config->WriteInt(L"lastusedencoder", ' A4M');
+ }
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_PERCENT,caption,100);
+ ZeroMemory(&cfs,sizeof(convertFileStruct));
+ this->callback = NULL;
+ transcoders.push_back(this);
+ callbackhwnd = CreateDummyWindow();
+ StringCchCopy(inifile, ARRAYSIZE(inifile), config->GetIniFile());
+ WideCharToMultiByteSZ(CP_ACP, 0, inifile, -1, inifileA, MAX_PATH, 0, 0);
+ BuildEncodableFormatsList(formats, winampParent, device);
+ LeaveCriticalSection(&csTranscoder);
+}
+
+TranscoderImp::~TranscoderImp()
+{
+ EnterCriticalSection(&csTranscoder);
+
+ //transcoders.eraseObject(this);
+ auto it = std::find(transcoders.begin(), transcoders.end(), this);
+ if (it != transcoders.end())
+ {
+ transcoders.erase(it);
+ }
+
+ //formats.deleteAll();
+ for (auto format : formats)
+ {
+ delete format;
+ }
+ formats.clear();
+
+ DestroyWindow(callbackhwnd);
+ LeaveCriticalSection(&csTranscoder);
+}
+
+void TranscoderImp::LoadConfigProfile(wchar_t *profile) {
+}
+
+void TranscoderImp::ReloadConfig()
+{
+ //formats.deleteAll();
+ for (auto format : formats)
+ {
+ delete format;
+ }
+ formats.clear();
+
+ BuildEncodableFormatsList(formats, winampParent, device);
+
+ transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
+ transrate = !!config->ReadInt(L"transrate",0);
+ translossless = !!config->ReadInt(L"translossless",0);
+ TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
+}
+
+void TranscoderImp::AddAcceptableFormat(unsigned int format)
+{
+ outformats.push_back(format);
+}
+
+void TranscoderImp::AddAcceptableFormat(wchar_t *format)
+{
+ outformats.push_back(stringToFourcc(format));
+}
+
+static bool FileExists(const wchar_t *file)
+{
+ return GetFileAttributesW(file) != INVALID_FILE_ATTRIBUTES;
+}
+
+static int getFileLength(const wchar_t * file, HWND winampParent) { // returns length in seconds
+ basicFileInfoStructW b={0};
+ b.filename=file;
+ SendMessage(winampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
+ return b.length;
+}
+
+static bool isFileLossless(const wchar_t * file) {
+ wchar_t ret[64] = {0};
+ if (AGAVE_API_METADATA && AGAVE_API_METADATA->GetExtendedFileInfo(file, L"lossless", ret, 64) && ret[0] == '1')
+ return true;
+ return false;
+}
+
+static int getFileBitrate(const wchar_t * file, HWND winampParent) { // returns bitrate in bits per second.
+ int secs = getFileLength(file,winampParent);
+ if(!secs) return 0;
+ FILE * f = _wfopen(file,L"rb");
+ int len = 0;
+ if(f) { fseek(f,0,2); len=ftell(f); fclose(f); }
+ return (len/secs)*8;
+}
+
+void TranscoderImp::GetTempFilePath(const wchar_t *ext, wchar_t *path) {
+ wchar_t dir[MAX_PATH] = {0};
+ GetTempPath(MAX_PATH,dir);
+ GetTempFileName(dir,L"transcode",0,path);
+ _wunlink(path);
+ wchar_t *e = wcsrchr(path,L'.');
+ if(e) *e=0;
+ wcscat(path,ext);
+ _wunlink(path);
+}
+
+void TranscoderImp::TranscodeProgress(int pc, bool done) {
+ if(!done) {
+ wchar_t buf[128] = {0};
+ StringCchPrintf(buf, ARRAYSIZE(buf), caption,pc);
+ if(callback) callback(callbackContext,buf);
+ }
+ else convertDone = done;
+}
+
+bool TranscoderImp::StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputFile, bool test) {
+ cfs.callbackhwnd = callbackhwnd;
+ cfs.sourcefile = _wcsdup(inputFile);
+ cfs.destfile = _wcsdup(outputFile);
+ cfs.destformat[0] = destformat;
+ cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
+ cfs.destformat[7] = (intptr_t)inifileA;
+ cfs.error = L"";
+ if(!SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILEW))
+ {
+ if(cfs.error && callback)
+ callback(callbackContext,cfs.error);
+ return false;
+ }
+ if(!test)
+ {
+ convertSetPriorityW csp = {&cfs,THREAD_PRIORITY_NORMAL};
+ SendMessage(winampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW);
+ TranscodeProgress(0,false);
+ }
+ return true;
+}
+
+void TranscoderImp::EndTranscode()
+{
+ cfs.callbackhwnd = NULL;
+ SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILE_END);
+ free(cfs.sourcefile);
+ free(cfs.destfile);
+ ZeroMemory(&cfs,sizeof(convertFileStruct));
+}
+
+bool TranscoderImp::TestTranscode(wchar_t * file, unsigned int destformat)
+{
+ wchar_t tempfn[MAX_PATH], ext[5]=L".";
+ fourccToString(destformat,&ext[1]);
+ GetTempFilePath(ext,tempfn);
+
+ convertFileStructW cfs;
+ cfs.callbackhwnd = callbackhwnd;
+ cfs.sourcefile = file;
+ cfs.destfile = tempfn;
+ cfs.destformat[0] = destformat;
+ cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
+ cfs.destformat[7] = (intptr_t)inifileA;
+ cfs.error = L"";
+
+ int v = SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERT_TEST);
+ _wunlink(tempfn);
+ return !!v;
+}
+
+bool TranscoderImp::FormatAcceptable(unsigned int format)
+{
+ int l = outformats.size();
+ for(int i=0; i<l; i++)
+ {
+ if(fourccEqual(outformats[i], format))
+ return true;
+ }
+ return false;
+}
+
+bool TranscoderImp::FormatAcceptable(wchar_t * format)
+{
+ return FormatAcceptable(stringToFourcc(format));
+}
+
+int TranscoderImp::GetOutputFormat(wchar_t * file, int *bitrate)
+{
+ if (!FileExists(file))
+ return 0;
+ int fourcc = config->ReadInt(L"lastusedencoder",' A4M');
+
+ if (TestTranscode(file,fourcc))
+ {
+ char buf[100]="128";
+ convertConfigItem ccs={fourcc,"bitrate",buf,100, inifileA};
+ SendMessage(winampParent,WM_WA_IPC,(WPARAM)&ccs,IPC_CONVERT_CONFIG_GET_ITEM);
+ int br = atoi(buf)*1000;
+ if(bitrate) *bitrate = br;
+ return fourcc;
+ }
+ return 0;
+}
+
+bool TranscoderImp::ShouldTranscode(wchar_t * file)
+{
+ if(TranscoderDisabled) return false;
+ wchar_t * ext = wcsrchr(file,L'.');
+ if(ext && FormatAcceptable(&ext[1])) {
+ if(transrate && getFileBitrate(file,winampParent) > 1000*transratethresh)
+ return true;
+ else if (translossless && isFileLossless(file))
+ return true;
+ else return false;
+ }
+ return true;
+}
+
+int TranscoderImp::CanTranscode(wchar_t * file, wchar_t * ext, int length)
+{
+ if(TranscoderDisabled) return -1;
+ int bitrate;
+ unsigned int fmt = GetOutputFormat(file,&bitrate);
+ if(fmt) {
+ if(ext) {
+ ext[0]=L'.'; ext[1]=0;
+ char extA[8]=".";
+ convertConfigItem c = {fmt,"extension",&extA[1],6,AutoCharDup(config->GetIniFile())};
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM);
+ if(extA[1]) wcsncpy(ext,AutoWide(extA), 10);
+ else fourccToString(fmt,&ext[1]);
+ free(c.configfile);
+ }
+ if (length <= 0)
+ length = getFileLength(file,winampParent);
+ return (bitrate/8) * length; // should transcode
+ }
+ return -1; // transcoding impossible
+}
+
+extern void filenameToItemRecord(wchar_t * file, itemRecordW * ice);
+extern void copyTags(itemRecordW * in, wchar_t * out);
+
+int TranscoderImp::TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callbackFunc)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption)
+{
+ if(caption) lstrcpyn(this->caption,caption,100);
+ this->callback = callbackFunc;
+ this->callbackContext = callbackContext;
+ convertDone = false;
+ int format = GetOutputFormat(inputFile);
+ if(!format) return -1;
+ if(!StartTranscode(format,inputFile,outputFile))
+ return -1;
+ while(!convertDone && !(*killswitch))
+ Sleep(50);
+ EndTranscode();
+
+ // copy the tags over
+ itemRecordW ice={0};
+ filenameToItemRecord(inputFile,&ice);
+ copyTags(&ice,outputFile);
+ freeRecord(&ice);
+
+ if(convertDone && callback)
+ callback(callbackContext,L"Done");
+ this->callback = NULL;
+ return convertDone?0:-1;
+}
+
+static void doConfigResizeChild(HWND parent, HWND child)
+{
+ if (child)
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r);
+ ScreenToClient(parent, (LPPOINT)&r);
+ SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ ShowWindow(child, SW_SHOWNA);
+ }
+}
+
+struct ConfigTranscoderParam
+{
+ ConfigTranscoderParam()
+ {
+ winampParent=0;
+ configfile=0;
+ memset(&ccs, 0, sizeof(ccs));
+ config=0;
+ dev=0;
+ }
+
+ ~ConfigTranscoderParam()
+ {
+ //list.deleteAll();
+ for (auto l : list)
+ {
+ delete l;
+ }
+ list.clear();
+
+
+ free((char*)ccs.extra_data[7]);
+ free(configfile);
+ }
+ HWND winampParent;
+ wchar_t *configfile;
+ FormatList list;
+ convertConfigStruct ccs;
+ C_Config * config;
+ Device *dev;
+};
+
+static INT_PTR CALLBACK config_dlgproc_transcode_advanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static ConfigTranscoderParam *p;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ p = (ConfigTranscoderParam *)lParam;
+ SetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,p->config->ReadString(L"forcetranscodingbitrate",L"250"));
+ if(p->config->ReadInt(L"transrate",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE,BST_CHECKED);
+ if(p->config->ReadInt(L"translossless",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE_LOSSLESS,BST_CHECKED);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ {
+ wchar_t buf[10]=L"";
+ GetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,buf,10);
+ p->config->WriteString(L"forcetranscodingbitrate",buf);
+ p->config->WriteInt(L"transrate",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE)?1:0);
+ p->config->WriteInt(L"translossless",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE_LOSSLESS)?1:0);
+ }
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+BOOL TranscoderImp::transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static ConfigTranscoderParam *p;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ p = (ConfigTranscoderParam *)lParam;
+ if(p->config->ReadInt(L"enableTranscoder",1)) CheckDlgButton(hwndDlg,IDC_ENABLETRANSCODER,BST_CHECKED);
+
+ BuildEncodableFormatsList(p->list, p->winampParent, p->dev);
+ p->ccs.hwndParent = hwndDlg;
+
+ int encdef = p->config->ReadInt(L"lastusedencoder",0);
+ for(size_t i=0; i < p->list.size(); i++)
+ {
+ EncodableFormat * f = p->list[i];
+ int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc);
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f);
+ if(i==0 && encdef == 0) encdef = f->fourcc;
+ if(f->fourcc == encdef)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0);
+ p->ccs.format = f->fourcc;
+ }
+ }
+
+ p->ccs.hwndParent = hwndDlg;
+ HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_ENABLETRANSCODER:
+ p->config->WriteInt(L"enableTranscoder",IsDlgButtonChecked(hwndDlg,IDC_ENABLETRANSCODER)?1:0);
+ break;
+ case IDC_ADVANCED:
+ return WASABI_API_DIALOGBOXPARAMW(IDD_CONFIG_TRANSCODING_ADVANCED,hwndDlg,config_dlgproc_transcode_advanced,(LPARAM)p);
+ case IDC_ENCFORMAT:
+ if (HIWORD(wParam) != CBN_SELCHANGE) return 0;
+ {
+ int sel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
+ if (sel != CB_ERR)
+ {
+ SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
+ EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0);
+ p->ccs.format = f->fourcc;
+
+ HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ p->config->WriteInt(L"lastusedencoder",p->ccs.format);
+ }
+ }
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ p->config->WriteInt(L"lastusedencoder",p->ccs.format);
+ SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
+ delete p;
+
+ for( TranscoderImp *l_transcoder : transcoders )
+ l_transcoder->ReloadConfig();
+ }
+ break;
+ }
+ return 0;
+}
+
+void* TranscoderImp::ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev)
+{
+ ConfigTranscoderParam * p = new ConfigTranscoderParam;
+ p->config = config;
+ p->winampParent=winampParent;
+ p->configfile=_wcsdup(config->GetIniFile());
+ p->ccs.extra_data[6] = mmioFOURCC('I','N','I',' ');
+ p->ccs.extra_data[7] = (int)AutoCharDup(p->configfile);
+ p->dev = dev;
+ return p;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transcoder_imp.h b/Src/Plugins/Library/ml_pmp/transcoder_imp.h
new file mode 100644
index 00000000..bb41a366
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transcoder_imp.h
@@ -0,0 +1,122 @@
+#ifndef _TRANSCODER_IMP_H_
+#define _TRANSCODER_IMP_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../winamp/wa_ipc.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "DeviceView.h"
+#include "nu/AutoWide.h"
+#include "nu/AutoChar.h"
+#include "transcoder.h"
+#include "resource1.h"
+#include <wchar.h>
+#include <stdio.h>
+#include <vector>
+
+#define ENCODER_HIGHLY_PREFERRED 2
+#define ENCODER_PREFERRED 1
+#define ENCODER_NO_PREFERENCE 0
+#define ENCODER_NOT_PREFERRED -1
+#define ENCODER_DO_NOT_USE -2
+
+class EncodableFormat
+{
+public:
+ unsigned int fourcc;
+ wchar_t *desc;
+ EncodableFormat(unsigned int fourcc,wchar_t *desc) :
+ fourcc(fourcc)
+ {
+ this->desc = _wcsdup(desc);
+ }
+
+ ~EncodableFormat()
+ {
+ free(desc);
+ }
+};
+
+typedef std::vector<EncodableFormat*> FormatList;
+
+
+class TranscoderImp : public Transcoder
+{
+protected:
+ bool TranscoderDisabled;
+ bool transrate;
+ int transratethresh;
+ bool translossless;
+ wchar_t caption[100];
+ wchar_t inifile[MAX_PATH];
+ char inifileA[MAX_PATH];
+ HINSTANCE hInst;
+ HWND winampParent;
+ HWND callbackhwnd;
+ std::vector<unsigned int> outformats;
+ FormatList formats;
+ C_Config * config;
+ convertFileStructW cfs;
+ void (*callback)(void * callbackContext, wchar_t * status);
+ void* callbackContext;
+ bool convertDone;
+ Device *device;
+
+ void TranscodeProgress(int pc, bool done);
+ bool StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputfile, bool test=false); // returns true if started ok.
+ void EndTranscode();
+ bool TestTranscode(wchar_t * file, unsigned int destformat);
+ bool FormatAcceptable(wchar_t * format);
+ bool FormatAcceptable(unsigned int format);
+ int GetOutputFormat(wchar_t * file, int *bitrate=NULL);
+ void AddEncodableFormat(const char *desc, unsigned int fourcc);
+ void ReloadConfig();
+
+public:
+ TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device);
+ virtual ~TranscoderImp();
+
+ virtual void LoadConfigProfile(wchar_t *profile);
+ virtual void AddAcceptableFormat(wchar_t *format);
+ virtual void AddAcceptableFormat(unsigned int format);
+
+ // done when file is added to transfer queue
+ // returns:
+ // -1 for can't transcode
+ // output file size estimate if can transcode
+ // if ext is supplied, it should be a buffer with space for 5 characters, and will be filled with
+ // the output file type file extention, eg, L".mp3"
+ virtual int CanTranscode(wchar_t *file, wchar_t *ext = NULL, int length = -1);
+
+ virtual bool ShouldTranscode(wchar_t * file); // false if no transcoding needed
+
+ virtual int TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callback)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption=L"Transcoding %d%%");
+ // done just before transfer OR in background after file is added to queue
+ // extention is added to outputFile, allow 5 extra chars
+ // callback, callbackcontext and killswitch should be similar to those passed by ml_pmp
+
+ //virtual int TranscodeFileASync(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callback)(void * callbackContext, wchar_t * status), void* callbackContext){return 0;};
+ //asynchronous version of the above, details obvious
+
+ virtual void GetTempFilePath(const wchar_t *ext, wchar_t *filename); // get a filename which can be used as a staging area. ext should be, i.e L".mp3"
+
+ /* remember to call DestroyWindow on the return value when parent recieves the WM_DESTROY message.
+ use like this:
+ transcoderConfig = TranscoderImp::ConfigureTranscoder(hwndDlg,L"ml_pmp");
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_PLACEHOLDER),&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+ SetWindowPos(transcoderConfig,NULL,r.left,r.top,0,0,SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
+ ShowWindow(transcoderConfig,SW_SHOWNA);
+ where IDC_PLACEHOLDER is an invisible group box of size 259x176 (in dialog units)
+ */
+ static void* ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev);
+ static BOOL transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ static void init();
+ static void quit();
+
+ friend LRESULT CALLBACK TranscodeMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+};
+
+#endif //_TRANSCODER_IMP_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transfer_thread.cpp b/Src/Plugins/Library/ml_pmp/transfer_thread.cpp
new file mode 100644
index 00000000..0399562e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transfer_thread.cpp
@@ -0,0 +1,503 @@
+#include "DeviceView.h"
+#include <time.h>
+#include <shlwapi.h>
+#include "SkinnedListView.h"
+#include "metadata_utils.h"
+#include "IconStore.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "main.h"
+
+static void TransferCallback(void * callBackContext, wchar_t * status);
+extern void TransfersListUpdateItem(CopyInst * item);
+void TransfersListUpdateItem(CopyInst * item, DeviceView *view);
+extern void TransfersListPushPopItem(CopyInst * item);
+void TransfersListPushPopItem(CopyInst * item, DeviceView *view);
+
+extern HWND mainMessageWindow;
+extern HANDLE hMainThread;
+
+/*
+How to add new ways of copying files.
+Subclass CopyInst, over-ride CopyAction and Equals
+Optionally over-ride PreCopyAction, PostCopyAction and Cancelled
+
+Add to transfer queue as normal.
+*/
+
+SongCopyInst::SongCopyInst(DeviceView * dev, itemRecordW * song0)
+{
+ usesPreCopy = false;
+ usesPostCopy = true;
+ this->dev = dev;
+ equalsType = 0;
+ res = 0;
+ copyRecord(&song, song0);
+ songid = NULL;
+ status = STATUS_WAITING;
+ // status caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+
+ SYSTEMTIME system_time;
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, lastChanged, sizeof(lastChanged)/sizeof(wchar_t));
+
+ // make the itemRecord a little safer
+ if(!song.album) song.album = _wcsdup(L"");
+ if(!song.artist) song.artist = _wcsdup(L"");
+ if(!song.title) song.title = _wcsdup(L"");
+ if(!song.genre) song.genre = _wcsdup(L"");
+ if(!song.filename) song.filename = _wcsdup(L"");
+ if(!song.comment) song.comment = _wcsdup(L"");
+ if(!song.albumartist) song.albumartist = _wcsdup(L"");
+ if(!song.publisher) song.publisher = _wcsdup(L"");
+ if(!song.composer) song.composer = _wcsdup(L"");
+
+ // track caption
+ lstrcpyn(trackCaption, song.artist, 128);
+
+ int l = lstrlen(trackCaption);
+ if(128 - l > 1) lstrcpyn(trackCaption + l, L" - ", 128-l);
+ l = lstrlen(trackCaption);
+ if(128 - l > 1) lstrcpyn(trackCaption + l, song.title, 128 - l);
+
+ // type caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER, typeCaption, sizeof(typeCaption)/sizeof(wchar_t));
+
+ // TODO fill out when other actions become done
+ // source and destination details
+ //this->dev->GetDisplayName(sourceDevice, sizeof(sourceDevice)/sizeof(wchar_t));
+ WASABI_API_LNGSTRINGW_BUF(IDS_LOCAL_MACHINE, sourceDevice, ARRAYSIZE(sourceDevice));
+ this->dev->GetDisplayName(destDevice, ARRAYSIZE(destDevice));
+
+ lstrcpynW(sourceFile, song.filename, sizeof(sourceFile)/sizeof(wchar_t));
+}
+
+SongCopyInst::~SongCopyInst()
+{
+ freeRecord(&song);
+}
+
+void SongCopyInst::Cancelled() {
+ // helps us to do appropriate handling
+ if (status == STATUS_TRANSFERRING)
+ {
+ dev->threadKillswitch = -2;
+ }
+ status = STATUS_CANCELLED;
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_CANCELLED, statusCaption, ARRAYSIZE(statusCaption));
+
+ SYSTEMTIME system_time = {0};
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, lastChanged, ARRAYSIZE(lastChanged));
+
+ dev->dev->trackRemovedFromTransferQueue(&song);
+}
+
+static void TransferCallback(void * callBackContext, wchar_t * status)
+{
+ CopyInst * c = (CopyInst *)callBackContext;
+ if(!wcscmp(status, c->statusCaption)) return;
+
+ if (status && *status) lstrcpyn(c->statusCaption, status, sizeof(c->statusCaption)/sizeof(wchar_t));
+ TransfersListUpdateItem(c);
+ TransfersListUpdateItem(c, c->dev);
+ int pc=0;
+ // copes with 'transferring %'
+ if(swscanf(status,L"%*s %d%%",&pc))
+ {
+ if (c->dev->isCloudDevice) cloudTransferProgress = pc;
+ else c->dev->currentTransferProgress = pc;
+ }
+ // copes with 'transferring (%)'
+ else if(swscanf(status,L"%*s %*1c %d%%",&pc))
+ {
+ if (c->dev->isCloudDevice) cloudTransferProgress = pc;
+ else c->dev->currentTransferProgress = pc;
+ }
+ c->dev->UpdateSpaceInfo(TRUE, TRUE);
+}
+
+bool SongCopyInst::CopyAction()
+{
+ int r = dev->dev->transferTrackToDevice(&song,this,TransferCallback,&songid,&dev->threadKillswitch);
+ if (r==0 && AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::PMP_TRANSFER_COUNT);
+
+ dev->dev->trackRemovedFromTransferQueue(&song);
+ return r!=0;
+}
+
+void SongCopyInst::PostCopyAction()
+{
+ if(status == STATUS_DONE && songid)
+ {
+ dev->dev->addTrackToPlaylist(0, songid);
+
+ if (dev->metadata_fields & SUPPORTS_ALBUMART)
+ {
+ int w,h;
+ ARGB32 *bits;
+ if (AGAVE_API_ALBUMART->GetAlbumArt_NoAMG(song.filename, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS)
+ {
+ dev->dev->setArt(songid,bits,w,h);
+ WASABI_API_MEMMGR->sysFree(bits);
+ }
+ }
+ }
+}
+
+bool SongCopyInst::Equals(CopyInst * b) {
+ if(this->equalsType == b->equalsType) {
+ SongCopyInst * c = (SongCopyInst*)b;
+ bool ret = (compareItemRecords(&this->song,&c->song) == 0);
+
+ // for cloud then we do some extra checks to allow different formats
+ // and also for sending the same file to a different cloud device...
+ if (c->dev->isCloudDevice)
+ {
+ const wchar_t * mime_1 = getRecordExtendedItem(&c->song, L"mime");
+ const wchar_t * mime_2 = getRecordExtendedItem(&this->song, L"mime");
+ int mime_match = lstrcmpiW(mime_1 ? mime_1 : L"", mime_2 ? mime_2 : L"");
+ int device_match = lstrcmpiW(c->destDevice ? c->destDevice : L"", this->destDevice ? this->destDevice : L"");
+
+ if (!device_match)
+ {
+ if (!mime_match)
+ {
+ return ret;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return ret;
+ }
+ return false;
+}
+
+PlaylistCopyInst::PlaylistCopyInst(DeviceView * dev, itemRecordW * song, wchar_t * plName0, int plid0) : SongCopyInst(dev,song)
+{
+ lstrcpyn(plName,plName0,255);
+ plid=plid0;
+ plAddSongs = NULL;
+ usesPreCopy = false;
+ usesPostCopy = true;
+}
+
+PlaylistCopyInst::~PlaylistCopyInst()
+{
+ SongCopyInst::~SongCopyInst();
+ if(plAddSongs) delete plAddSongs;
+}
+
+bool PlaylistCopyInst::PreCopyAction() {return false;}
+
+void PlaylistCopyInst::PostCopyAction() {
+ SongCopyInst::PostCopyAction();
+ if(!plAddSongs || plid == -1) return;
+ if(plid >= dev->dev->getPlaylistCount()) return;
+ int l = plAddSongs->GetSize();
+ wchar_t pln[256] = {0};
+ dev->dev->getPlaylistName(plid,pln,255);
+ if(wcscmp(pln,plName) == 0) {
+ if(status == STATUS_DONE && songid) dev->dev->addTrackToPlaylist(plid,songid);
+ for(int i=0; i < l; i++) {
+ songid_t s = (songid_t)plAddSongs->Get(i);
+ if(s) dev->dev->addTrackToPlaylist(plid,s);
+ }
+ }
+}
+
+ReverseCopyInst::ReverseCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, bool addToLibrary, bool uppercaseext) : uppercaseext(uppercaseext) {
+ usesPreCopy = false;
+ usesPostCopy = addToLibrary;
+ this->dev = dev;
+ equalsType = 1;
+ this->songid = song;
+ status = STATUS_WAITING;
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPY_TO_LIBRARY, typeCaption, sizeof(typeCaption)/sizeof(wchar_t));
+ dev->dev->getTrackArtist(song,trackCaption,128);
+ int l = lstrlen(trackCaption);
+ if(128 - l > 1) lstrcpyn(trackCaption + l,L" - ",128-l);
+ l = lstrlen(trackCaption);
+ if(128 - l > 1) dev->dev->getTrackTitle(song,trackCaption + l,128 - l);
+
+ // find path for song
+ lstrcpyn(path,format,2036);
+ FixReplacementVars(path,2036,dev->dev,song);
+ PathCombine(path,filepath,path);
+}
+
+bool ReverseCopyInst::Equals(CopyInst *b) {
+ if(this->equalsType == b->equalsType) {
+ ReverseCopyInst* c = (ReverseCopyInst*)b;
+ return (c->dev == this->dev) && (c->songid == this->songid);
+ }
+ return false;
+}
+
+bool ReverseCopyInst::CopyAction() { //Return true if failed.
+ wchar_t * lastslash = wcsrchr(path,L'\\');
+ if(!lastslash) {
+ WASABI_API_LNGSTRINGW_BUF(IDS_INVALID_PATH, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+ return true;
+ }
+ *lastslash=0;
+ if(RecursiveCreateDirectory(path)) {
+ WASABI_API_LNGSTRINGW_BUF(IDS_INVALID_PATH, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+ return true;
+ }
+ *lastslash=L'\\';
+ // path created, copy file over.
+ int r = dev->dev->copyToHardDrive(songid, path, this, TransferCallback, &dev->threadKillswitch);
+ return r!=0;
+}
+
+void ReverseCopyInst::PostCopyAction()
+{
+ itemRecordW ice={0};
+ filenameToItemRecord(path,&ice);
+
+ ice.rating = dev->dev->getTrackRating(songid);
+ ice.playcount = dev->dev->getTrackPlayCount(songid);
+ ice.lastplay = dev->dev->getTrackLastPlayed(songid);
+ ice.lastupd = dev->dev->getTrackLastUpdated(songid);
+ ice.type = dev->dev->getTrackType(songid);
+
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&ice,ML_IPC_DB_ADDORUPDATEITEMW);
+ freeRecord(&ice);
+}
+
+ReversePlaylistCopyInst::ReversePlaylistCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, wchar_t * playlistFile0, wchar_t * playlistName0, bool last,bool addToLibrary)
+ : ReverseCopyInst(dev,filepath,format,song,addToLibrary,false), last(last) {
+ lstrcpyn(playlistFile,playlistFile0,MAX_PATH);
+ lstrcpyn(playlistName,playlistName0,128);
+}
+
+bool ReversePlaylistCopyInst::CopyAction()
+{
+ bool r = ReverseCopyInst::CopyAction();
+ return r;
+}
+
+void ReversePlaylistCopyInst::PostCopyAction()
+{
+ ReverseCopyInst::PostCopyAction();
+ FILE * f = _wfopen(playlistFile,L"at");
+ if(f) {
+ fputws(L"#EXTINF:",f);
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"%d",dev->dev->getTrackLength(songid)/1000);
+ fputws(buf,f);
+ fputws(L",",f);
+ wchar_t title[2048] = {0};
+ getTitle(dev->dev,songid,path,title,2048);
+ fputws(title,f);
+ fputws(L"\n",f);
+ fputws(path,f);
+ fputws(L"\n",f);
+ fclose(f);
+ }
+ if(last) {
+ mlAddPlaylist a = {sizeof(mlAddPlaylist),playlistName,playlistFile,PL_FLAG_SHOW | PL_FLAGS_IMPORT,-1,-1};
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&a,ML_IPC_PLAYLIST_ADD);
+ _wunlink(playlistFile);
+ }
+}
+
+// HERE BE DRAGONS
+static VOID CALLBACK APC_PreCopy(ULONG_PTR dwParam) {
+ CopyInst * a = (CopyInst *)dwParam;
+ a->res = a->PreCopyAction()?2:1;
+}
+
+static VOID CALLBACK APC_PostCopy(ULONG_PTR dwParam) {
+ CopyInst * a = (CopyInst *)dwParam;
+ a->PostCopyAction();
+ a->res = 1;
+}
+
+void CALLBACK TransferNavTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ TransferContext *context = (TransferContext *)eventId;
+ if (context && context->dev->queueTreeItem)
+ {
+ NAVITEM item;
+ item.cbSize = sizeof(NAVITEM);
+ item.hItem = context->dev->queueTreeItem;
+ item.iSelectedImage = item.iImage = icon_store.GetQueueIcon(context->dev->queueActiveIcon);
+ context->dev->queueActiveIcon = (context->dev->queueActiveIcon + 1) % 4;
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+}
+
+void TransferContext::DoOneTransfer(HANDLE handle)
+{
+ if (TryEnterCriticalSection(&transfer_lock))
+ {
+ if(dev->threadKillswitch == 1)
+ {
+ WASABI_API_THREADPOOL->RemoveHandle(transfer_thread, handle);
+ dev->threadKillswitch = 100;
+ SetEvent(killer);
+ LeaveCriticalSection(&transfer_lock);
+ return;
+ }
+
+ if (IsPaused())
+ {
+ LeaveCriticalSection(&transfer_lock);
+ return;
+ }
+
+ LinkedQueue * txQueue = getTransferQueue(this->dev);
+ CopyInst * c = (txQueue ? (CopyInst *)txQueue->Peek() : NULL);
+ if (c)
+ {
+ if (c->res != 2 && c->status != STATUS_CANCELLED)
+ {
+ c->status = STATUS_TRANSFERRING;
+ start = time(NULL);
+ TransferCallback(c, WASABI_API_LNGSTRINGW_BUF(IDS_STARTING_TRANSFER, c->statusCaption, sizeof(c->statusCaption)/sizeof(wchar_t)));
+ c->res = 0;
+ if(c->usesPreCopy)
+ SynchronousProcedureCall(APC_PreCopy,(ULONG_PTR)c);
+ }
+ if(dev->threadKillswitch)
+ {
+ WASABI_API_THREADPOOL->RemoveHandle(transfer_thread, handle);
+ dev->threadKillswitch = 100;
+ SetEvent(killer);
+ LeaveCriticalSection(&transfer_lock);
+ return;
+ }
+ if(c->res == 2)
+ { // dupe
+ WASABI_API_LNGSTRINGW_BUF((dev->isCloudDevice ? IDS_ALREADY_UPLOADED : IDS_DUPLICATE), c->statusCaption, sizeof(c->statusCaption)/sizeof(wchar_t));
+ c->status = STATUS_DONE;
+ }
+ else if (c->status != STATUS_CANCELLED)
+ {
+ // do the transfer
+ int r = c->CopyAction();
+ c->status = (r == -1 ? STATUS_ERROR : STATUS_DONE);
+
+ SYSTEMTIME system_time = {0};
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, c->lastChanged, sizeof(c->lastChanged)/sizeof(wchar_t));
+
+ // Now do whatever needs to be done post-copy (add to playlist or whatever)
+ c->res = 0;
+ if(c->usesPostCopy && c->status == STATUS_DONE)
+ SynchronousProcedureCall(APC_PostCopy,(ULONG_PTR)c);
+ // now work out the moving average time per transfer
+ end = time(NULL);
+ if(c->status == STATUS_DONE)
+ {
+ times[numTransfers % AVERAGEBASIS] = (int)((long)end - (long)start);
+ numTransfers++;
+ }
+ int n = min(AVERAGEBASIS,numTransfers);
+ if(n > 0)
+ {
+ int t = 0;
+ for(int i = 0; i < n; i++) t += times[i];
+ dev->transferRate = ((double)t) / ((double)n);
+ }
+ }
+ if(dev->threadKillswitch == 2)
+ { // a transfer has been cancelled part way through
+ dev->threadKillswitch = 0;
+ delete txQueue->Poll();
+ }
+ else
+ {
+ LinkedQueue * finishedTX = getFinishedTransferQueue(this->dev);
+ if (finishedTX)
+ {
+ txQueue->lock();
+ finishedTX->lock();
+ finishedTX->Offer(txQueue->Poll());
+ finishedTX->unlock();
+ txQueue->unlock();
+ TransfersListPushPopItem(c);
+ TransfersListPushPopItem(c, dev);
+ }
+ }
+ dev->commitNeeded = true;
+ if (dev->isCloudDevice) cloudTransferProgress = 0;
+ else dev->currentTransferProgress = 0;
+
+ LeaveCriticalSection(&transfer_lock);
+ SetEvent(handle);
+ }
+ else
+ {
+ if(dev->commitNeeded)
+ PostMessage(mainMessageWindow,WM_TIMER,COMMITTIMERID,0);
+ if (dev->isCloudDevice) cloudTransferProgress = 0;
+ else dev->currentTransferProgress = 0;
+ dev->UpdateActivityState();
+ LeaveCriticalSection(&transfer_lock);
+ }
+ }
+}
+
+int TransferThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ TransferContext *context = (TransferContext *)user_data;
+ SetTimer(plugin.hwndLibraryParent, (UINT_PTR)user_data, 1000, TransferNavTimer);
+ // allows cancels to continue even if a cloud device upload had been cancelled
+ if (context->dev->threadKillswitch == -2) context->dev->threadKillswitch = 0;
+ context->DoOneTransfer(handle);
+ KillTimer(plugin.hwndLibraryParent, (UINT_PTR)user_data);
+ return 0;
+}
+
+bool TransferContext::IsPaused()
+{
+ return (paused_all || paused);
+}
+
+void TransferContext::Pause()
+{
+ if (1 == InterlockedIncrement(&paused))
+ {
+ if(dev->commitNeeded)
+ PostMessage(mainMessageWindow,WM_TIMER,COMMITTIMERID,0);
+
+ SetEvent(notifier);
+ }
+}
+
+void TransferContext::Resume()
+{
+ if (0 == InterlockedDecrement(&paused))
+ {
+ SetEvent(notifier);
+ }
+}
+
+bool TransferContext::IsAllPaused()
+{
+ return paused_all?true:false;
+}
+
+void TransferContext::PauseAll()
+{
+ InterlockedIncrement(&paused_all);
+}
+
+void TransferContext::ResumeAll()
+{
+ InterlockedDecrement(&paused_all);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transfer_thread.h b/Src/Plugins/Library/ml_pmp/transfer_thread.h
new file mode 100644
index 00000000..0e317041
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transfer_thread.h
@@ -0,0 +1,94 @@
+#ifndef __TRANSFER_THREAD_
+#define __TRANSFER_THREAD_
+#include <wchar.h>
+
+class DeviceView;
+
+#define STATUS_ERROR -1
+#define STATUS_WAITING 0
+#define STATUS_TRANSFERRING 1
+#define STATUS_DONE 3
+#define STATUS_CANCELLED 4
+
+extern int SynchronousProcedureCall(void * p, ULONG_PTR dwParam);
+extern BOOL RecursiveCreateDirectory(wchar_t* buf1); // from replaceVars.cpp
+extern wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song); // from replaceVars.cpp
+
+class CopyInst {
+public:
+ CopyInst() : status(STATUS_WAITING), res(0), dev(NULL), equalsType(-1), usesPreCopy(false), usesPostCopy(true), songid(NULL) {
+ typeCaption[0] = 0;
+ statusCaption[0] = 0;
+ trackCaption[0] = 0;
+ sourceDevice[0] = 0;
+ destDevice[0] = 0;
+ lastChanged[0] = 0;
+ sourceFile[0] = 0;
+ }
+ virtual bool PreCopyAction() {return false;} // called in main window thread. Return true to skip.
+ virtual bool CopyAction()=0; // Do the actual transfer, called in transfer thread. Return true if failed.
+ virtual void PostCopyAction() {} // called in main window thread
+ virtual void Cancelled() {} // the transfer has been cancelled
+ virtual bool Equals(CopyInst * b)=0; // use equalsType to check if it is safe to cast b to your own type. Return true if equal.
+ int status; // one of STATUS_*
+ int res; // don't mess with this!
+ DeviceView * dev;
+ wchar_t typeCaption[128];
+ wchar_t statusCaption[128];
+ wchar_t trackCaption[128];
+ wchar_t sourceDevice[128];
+ wchar_t destDevice[128];
+ wchar_t lastChanged[128];
+ wchar_t sourceFile[MAX_PATH];
+ int equalsType; // 0 for SongCopyInst, 1 for ReverseCopyInst, -1 for unknown
+ bool usesPreCopy;
+ bool usesPostCopy;
+ songid_t songid;
+};
+
+class SongCopyInst : public CopyInst {
+public:
+ SongCopyInst(DeviceView * dev,itemRecordW * song);
+ virtual ~SongCopyInst();
+ itemRecordW song;
+ virtual bool CopyAction();
+ virtual void PostCopyAction();
+ virtual void Cancelled();
+ virtual bool Equals(CopyInst * b);
+};
+
+class PlaylistCopyInst : public SongCopyInst {
+public:
+ wchar_t plName[256];
+ int plid;
+ C_ItemList * plAddSongs;
+ PlaylistCopyInst(DeviceView * dev, itemRecordW * song, wchar_t * plName0, int plid0);
+ virtual ~PlaylistCopyInst();
+ virtual bool PreCopyAction();
+ virtual void PostCopyAction();
+};
+
+class ReverseCopyInst : public CopyInst {
+public:
+ ReverseCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, bool addToLibrary, bool uppercaseext);
+ virtual bool CopyAction(); // Do the actual transfer, called in transfer thread. Return true if failed.
+ virtual void PostCopyAction();
+ virtual bool Equals(CopyInst * b); // use equalsType to check if it is safe to cast b to your own type. Return true if equal.
+ wchar_t path[2048];
+ bool uppercaseext;
+};
+
+// simple subclass which appends the copied filename to an m3u playlist file after copy.
+class ReversePlaylistCopyInst : public ReverseCopyInst {
+public:
+ ReversePlaylistCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, wchar_t * playlistFile, wchar_t * playlistName, bool last,bool addToLibrary=true);
+ virtual bool CopyAction();
+ virtual void PostCopyAction();
+ wchar_t playlistFile[MAX_PATH];
+ wchar_t playlistName[128];
+ bool last;
+};
+
+//DWORD WINAPI ThreadFunc_Transfer(LPVOID lpParam);
+
+#endif //__TRANSFER_THREAD_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/version.rc2 b/Src/Plugins/Library/ml_pmp/version.rc2
new file mode 100644
index 00000000..af678fba
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,25,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", "2,25,0,0"
+ VALUE "InternalName", "Nullsoft Portable Music Player Support"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_pmp.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_pmp/view_pmp_devices.cpp b/Src/Plugins/Library/ml_pmp/view_pmp_devices.cpp
new file mode 100644
index 00000000..42937088
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/view_pmp_devices.cpp
@@ -0,0 +1,764 @@
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "resource1.h"
+#include "SkinnedListView.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include "metadata_utils.h"
+
+static int (*wad_handleDialogMsgs)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void (*wad_DrawChildWindowBorders)(HWND hwndDlg, int *tab, int tabsize);
+static void (*cr_init)(HWND hwndDlg, ChildWndResizeItem *list, int num);
+static void (*cr_resize)(HWND hwndDlg, ChildWndResizeItem *list, int num);
+
+
+static HWND m_hwnd;
+
+static ChildWndResizeItem resize_rlist[]=
+{
+ {IDC_LIST_DEVICES, 0x0010},
+ {IDC_LIST_TRANSFERS, 0x0011},
+ {IDC_TQ_STATIC, 0x0000},
+ {IDC_HDELIM, 0x0010},
+ {IDC_BUTTON_PAUSETRANSFERS, 0x0101},
+ {IDC_BUTTON_CLEARFINISHED, 0x0101},
+ {IDC_BUTTON_REMOVESELECTED, 0x0101},
+ {IDC_STATUS,0x0111},
+};
+static int m_nodrawtopborders=0,adiv_clickoffs,adivpos=-1;
+SkinnedListView * listDevices=NULL;
+static SkinnedListView * listTransfers=NULL;
+//CRITICAL_SECTION listTransfersLock;
+
+static INT_PTR CALLBACK adiv_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static void adiv_UpdPos(int xp);
+static WNDPROC adiv_oldWndProc;
+
+class DeviceContents : public ListContents
+{
+public:
+ virtual int GetNumColumns()
+ {
+ return 2;
+ }
+ virtual int GetNumRows()
+ {
+ return devices.GetSize();
+ }
+ virtual wchar_t * GetColumnTitle(int num)
+ {
+ switch (num)
+ {
+ case 0: return WASABI_API_LNGSTRINGW(IDS_NAME);
+ case 1: return WASABI_API_LNGSTRINGW(IDS_CAPACITY_FREE);
+ }
+ return L"";
+ }
+ virtual int GetColumnWidth(int num)
+ {
+ switch (num)
+ {
+ case 0: return global_config->ReadInt(L"devices_col0_width",150);
+ case 1: return global_config->ReadInt(L"devices_col1_width",100);
+ default: return 0;
+ }
+ }
+ virtual void ColumnResize(int col, int newWidth)
+ {
+ if (col==0) global_config->WriteInt(L"devices_col0_width",newWidth);
+ else if (col==1) global_config->WriteInt(L"devices_col1_width",newWidth);
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)
+ {
+ switch (col)
+ {
+ case 0:
+ ((DeviceView *)devices.Get(row))->dev->getPlaylistName(0,buf,buflen);
+ return;
+ case 1:
+ {
+ wchar_t capacity[20]=L"";
+ Device * dev = ((DeviceView *)devices.Get(row))->dev;
+ WASABI_API_LNG->FormattedSizeString(capacity, ARRAYSIZE(capacity), dev->getDeviceCapacityTotal());
+ __int64 cap = dev->getDeviceCapacityTotal();
+ int pc;
+ if (cap) pc = (int)((((__int64)100)*dev->getDeviceCapacityAvailable()) / cap);
+ else pc = 0;
+ wsprintf(buf,L"%s (%d%%)",capacity,pc);
+ }
+ return;
+ }
+ }
+ virtual songid_t GetTrack(int pos) { return 0; }
+};
+
+static DeviceContents deviceListContents;
+
+class TransferItemShadow
+{
+public:
+ CopyInst * c;
+ wchar_t * status, * type, * track;
+ wchar_t device[128];
+ bool changed;
+ TransferItemShadow(CopyInst * c)
+ {
+ changed = false;
+ this->c = c;
+ status = _wcsdup(c->statusCaption);
+ type = _wcsdup(c->typeCaption);
+ track = _wcsdup(c->trackCaption);
+ device[0] = 0;
+ c->dev->dev->getPlaylistName(0,device,128);
+ }
+ ~TransferItemShadow()
+ {
+ free(status);
+ free(type);
+ free(track);
+ }
+ bool Equals(TransferItemShadow * a)
+ {
+ if (!a) return false;
+ return (c == a->c) && !wcscmp(track,a->track) && !wcscmp(status,a->status) && !wcscmp(device,a->device) && !wcscmp(type,a->type);
+ }
+};
+
+static C_ItemList *getTransferListShadow()
+{
+ C_ItemList * list = new C_ItemList;
+
+ for (int i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView *device = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(device);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ if (txQueue)
+ {
+ txQueue->lock();
+ int l = txQueue->GetSize();
+
+ for (int j=0; j<l; j++)
+ list->Add(new TransferItemShadow((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ int l = finishedTX->GetSize();
+
+ for (int j=0; j<l; j++)
+ list->Add(new TransferItemShadow((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+ }
+ return list;
+}
+
+class TransferContents2 : public ListContents
+{
+public:
+ CRITICAL_SECTION cs;
+ C_ItemList * listShadow;
+ TransferContents2() : listShadow(0)
+ {
+ oldSize = 0;
+ listShadow = getTransferListShadow();
+ InitializeCriticalSection(&cs);
+ }
+ virtual ~TransferContents2()
+ {
+ DeleteCriticalSection(&cs); delete listShadow;
+ }
+ virtual int GetNumColumns()
+ {
+ return 4;
+ }
+ virtual int GetNumRows()
+ {
+ return (listShadow ? listShadow->GetSize() : 0);
+ }
+ virtual wchar_t * GetColumnTitle(int num)
+ {
+ switch (num)
+ {
+ case 0: return WASABI_API_LNGSTRINGW(IDS_TYPE);
+ case 1: return WASABI_API_LNGSTRINGW(IDS_TRACK);
+ case 2: return WASABI_API_LNGSTRINGW(IDS_STATUS);
+ case 3: return WASABI_API_LNGSTRINGW(IDS_DEVICE);
+ }
+ return L"";
+ }
+ virtual int GetColumnWidth(int num)
+ {
+ switch (num)
+ {
+ case 0: return global_config->ReadInt(L"transfers_col0_width",100);
+ case 1: return global_config->ReadInt(L"transfers_col1_width",300);
+ case 2: return global_config->ReadInt(L"transfers_col2_width",150);
+ case 3: return global_config->ReadInt(L"transfers_col3_width",150);
+ default: return 0;
+ }
+ }
+ virtual void ColumnResize(int col, int newWidth)
+ {
+ if (col==0) global_config->WriteInt(L"transfers_col0_width",newWidth);
+ else if (col==1) global_config->WriteInt(L"transfers_col1_width",newWidth);
+ else if (col==2) global_config->WriteInt(L"transfers_col2_width",newWidth);
+ else if (col==3) global_config->WriteInt(L"transfers_col3_width",newWidth);
+ }
+ void lock()
+ {
+ EnterCriticalSection(&cs);
+ }
+ void unlock()
+ {
+ LeaveCriticalSection(&cs);
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)
+ {
+ lock();
+ if (row >= listShadow->GetSize())
+ {
+ unlock(); return;
+ }
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(row);
+ switch (col)
+ {
+ case 0: lstrcpyn(buf,t->type,buflen); break;
+ case 1: lstrcpyn(buf,t->track,buflen); break;
+ case 2: lstrcpyn(buf,t->status,buflen); break;
+ case 3: lstrcpyn(buf,t->device,buflen); break;
+ }
+ unlock();
+ }
+
+ void PushPopItem(CopyInst *c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ t->changed=true;
+ listShadow->Del(i);
+ listShadow->Add(t);
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd,LVM_DELETEITEM,i,0);
+ PostMessage(hwnd,LVM_SETITEMCOUNT,size,LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+
+ void ItemUpdate(CopyInst * c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ TransferItemShadow * n = new TransferItemShadow(c);
+ n->changed=true;
+ listShadow->Set(i,n);
+ delete t;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+ int oldSize;
+ void FullUpdate()
+ {
+ C_ItemList * newListShadow = getTransferListShadow();
+ int newSize = newListShadow->GetSize();
+ lock();
+ oldSize = listShadow->GetSize();
+ for (int i=0; i<newSize; i++)
+ {
+ TransferItemShadow * newt = (TransferItemShadow *)newListShadow->Get(i);
+ TransferItemShadow * oldt = i<oldSize?(TransferItemShadow *)listShadow->Get(i):NULL;
+ newt->changed = !newt->Equals(oldt);
+ }
+
+ C_ItemList * oldListShadow = listShadow;
+ listShadow = newListShadow;
+ for (int i=0; i<oldListShadow->GetSize(); i++) delete(TransferItemShadow *)oldListShadow->Get(i);
+ delete oldListShadow;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ if (newSize != oldSize) PostMessage(hwnd,LVM_SETITEMCOUNT,newSize, 0);
+ for (int i=0; i<newSize; i++)
+ {
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(i);
+ if (t->changed) PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ }
+ unlock();
+ }
+ virtual songid_t GetTrack(int pos) { return 0; }
+};
+
+static void updateStatus()
+{
+ int pcnum=0,num=0,time=0,total=0;
+
+ for (int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView* device = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(device);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ int txProgress = getTransferProgress(device);
+ int s = (txQueue ? txQueue->GetSize() : 0);
+ int n = (s * 100) - txProgress;
+ total += s * 100;
+ total += 100 * (finishedTX ? finishedTX->GetSize() : 0);
+ pcnum += n;
+ num += s;
+ int t = (int)(device->transferRate * (((double)n) / 100.0));
+ if (time < t) time = t;
+ }
+ if (total)
+ {
+ wchar_t caption[256] = {0};
+ int pc = ((total-pcnum)*100)/total;
+ wsprintf(caption,WASABI_API_LNGSTRINGW((time > 0 ? IDS_TRANFERS_PERCENT_REMAINING : IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME)),num,pc,time/60,time%60);
+ SetDlgItemText(m_hwnd,IDC_STATUS,caption);
+ }
+ else SetDlgItemText(m_hwnd,IDC_STATUS,L"");
+}
+
+static TransferContents2 transferListContents;
+
+void TransfersListUpdateItem(CopyInst * item)
+{
+ transferListContents.ItemUpdate(item);
+}
+
+void TransfersListPushPopItem(CopyInst * item)
+{
+ transferListContents.PushPopItem(item);
+}
+
+void UpdateDevicesListView(bool softUpdate)
+{
+ if (listDevices) listDevices->UpdateList(softUpdate);
+}
+
+static bool AddSelectedItems(C_ItemList *items, W_ListView *listview, LinkedQueue *transfer_queue, int &row, DeviceView *&dev)
+{
+ transfer_queue->lock();
+ int l = transfer_queue->GetSize();
+ for (int j=0; j<l; j++)
+ {
+ if (listview->GetSelected(row++))
+ {
+ CopyInst * c = (CopyInst *)transfer_queue->Get(j);
+ if (c->songid)
+ {
+ if (!dev && c->dev) dev = c->dev;
+ if (dev)
+ {
+ if (c->dev != dev)
+ {
+ transfer_queue->unlock();
+ return false;
+ }
+ else
+ items->Add((void*)c->songid);
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+ return true;
+}
+
+static void RemoveSelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, int &row, bool finished_queue)
+{
+ transfer_queue->lock();
+ int j = transfer_queue->GetSize();
+ while (j-- > 0)
+ {
+ if (listview->GetSelected(--row))
+ {
+ if (j == 0 && !finished_queue)
+ {
+ CopyInst * d = (CopyInst *)transfer_queue->Get(j);
+ if (d && d->status == STATUS_WAITING && device->transferContext.IsPaused())
+ {
+ transfer_queue->Del(j);
+ d->Cancelled();
+ delete d;
+ } // otherwise don't bother
+ }
+ else
+ {
+ CopyInst * d = (CopyInst*)transfer_queue->Del(j);
+ if (d)
+ {
+ if (d->status == STATUS_WAITING && !finished_queue)
+ d->Cancelled();
+ delete d;
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+}
+
+extern void handleContextMenuResult(int r, C_ItemList * items=NULL, DeviceView * dev=NULL);
+extern int showContextMenu(int context,HWND hwndDlg, Device * dev, POINT pt);
+extern int (*wad_getColor)(int idx);
+
+INT_PTR CALLBACK pmp_devices_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if (wad_handleDialogMsgs)
+ {
+ BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (listDevices)
+ {
+ BOOL a=listDevices->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (listTransfers)
+ {
+ BOOL a=listTransfers->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ m_hwnd=hwndDlg;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&cr_init=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,32,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&cr_resize=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,33,ML_IPC_SKIN_WADLG_GETFUNC);
+ if (cr_init) cr_init(hwndDlg,resize_rlist,sizeof(resize_rlist)/sizeof(resize_rlist[0]));
+ listDevices = new SkinnedListView(&deviceListContents,IDC_LIST_DEVICES,plugin.hwndLibraryParent, hwndDlg);
+ listDevices->DialogProc(hwndDlg,uMsg,0,0);
+ transferListContents.lock();
+ listTransfers = new SkinnedListView(&transferListContents,IDC_LIST_TRANSFERS,plugin.hwndLibraryParent, hwndDlg);
+ listTransfers->DialogProc(hwndDlg,uMsg,0,0);
+ transferListContents.unlock();
+
+ adiv_oldWndProc=(WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_HDELIM),GWLP_WNDPROC,(LONG_PTR)adiv_newWndProc);
+
+ SetDlgItemText(hwndDlg,IDC_BUTTON_PAUSETRANSFERS,WASABI_API_LNGSTRINGW((TransferContext::IsAllPaused()?IDS_RESUME:IDS_PAUSE)));
+ SetTimer(hwndDlg,1,250,NULL);
+ updateStatus();
+ break;
+ case WM_TIMER:
+ if (wParam == 1)
+ {
+ updateStatus();
+ if (device_update_map[0])
+ {
+ device_update_map[0]=0;
+ transferListContents.FullUpdate();
+ }
+ }
+ break;
+ case WM_DISPLAYCHANGE:
+ break;
+ case WM_SETCURSOR: // set cursor when near the dividers
+ {
+ RECT r;
+ POINT p;
+ GetCursorPos(&p);
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_HDELIM),&r);
+ r.top-=3;
+ r.bottom+=3;
+ if (PtInRect(&r,p))
+ {
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ return uMsg == WM_SETCURSOR;
+ }
+
+ break;
+ }
+ case WM_LBUTTONDOWN:
+ {
+ // forward dialog clicks to the dividers if they get near them
+ POINT p;
+ RECT r3;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_VDELIM),&r3);
+ GetCursorPos(&p);
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_HDELIM),&r3);
+ if (p.x >= r3.left && p.x <= r3.right)
+ {
+ int d=p.y-r3.bottom;
+ int d2=p.y-r3.top;
+ if (d<0)d=-d;
+ if (d2<0)d2=-d2;
+ if (d < 6 || d2 < 6) SendDlgItemMessage(hwndDlg,IDC_HDELIM,uMsg,0,0);
+ }
+ }
+ break;
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ {
+ if (cr_resize) cr_resize(hwndDlg,resize_rlist,sizeof(resize_rlist)/sizeof(resize_rlist[0]));
+ }
+ break;
+ case WM_PAINT:
+ {
+ if (wad_DrawChildWindowBorders)
+ {
+ int tab[] = {m_nodrawtopborders==1?0:(IDC_LIST_DEVICES|DCW_SUNKENBORDER),
+ m_nodrawtopborders==2?0:(IDC_LIST_TRANSFERS|DCW_SUNKENBORDER),
+ IDC_HDELIM|DCW_DIVIDER
+ };
+ wad_DrawChildWindowBorders(hwndDlg,tab,3);
+ }
+ }
+ break;
+ case WM_DESTROY:
+ {
+ KillTimer(hwndDlg, 1);
+ m_hwnd=NULL;
+ SkinnedListView * ld = listDevices; listDevices=NULL; delete ld;
+ SkinnedListView * lt = listTransfers;
+ transferListContents.lock();
+ listTransfers=NULL;
+ transferListContents.unlock();
+ delete lt;
+ }
+ break;
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_LIST_TRANSFERS)
+ switch (l->code)
+ {
+ case NM_RETURN: // enter!
+ case NM_RCLICK: // right click!
+ case LVN_KEYDOWN:
+ {
+ DeviceView * dev = NULL;
+ C_ItemList items;
+ int row = 0;
+ for (int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView *device = (DeviceView*)devices.Get(i);
+ if (!AddSelectedItems(&items, &listTransfers->listview, getTransferQueue(device), row, dev))
+ return 0;
+ if (!AddSelectedItems(&items, &listTransfers->listview, getFinishedTransferQueue(device), row, dev))
+ return 0;
+ }
+ bool foundDev=false;
+ for (int k=0; k<devices.GetSize(); k++) if (dev == (DeviceView*)devices.Get(k)) foundDev=true;
+ if (dev && foundDev && items.GetSize())
+ {
+ if (l->code == NM_RCLICK)
+ {
+ LPNMITEMACTIVATE lva=(LPNMITEMACTIVATE)lParam;
+ handleContextMenuResult(showContextMenu(7,l->hwndFrom,dev->dev,lva->ptAction),&items,dev);
+ }
+ else if (l->code == NM_RETURN)
+ {
+ handleContextMenuResult((!GetAsyncKeyState(VK_SHIFT)?ID_TRACKSLIST_PLAYSELECTION:ID_TRACKSLIST_ENQUEUESELECTION),&items,dev);
+ }
+ else if (l->code == LVN_KEYDOWN)
+ {
+ switch (((LPNMLVKEYDOWN)lParam)->wVKey)
+ {
+ case VK_DELETE:
+ {
+ if (!(GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_DELETE,&items,dev);
+ }
+ }
+ break;
+ case 0x45: //E
+ if ((GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_EDITSELECTEDITEMS,&items,dev);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON_CLEARFINISHED:
+ {
+ for (int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView *device = (DeviceView*)devices.Get(i);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ int j=finishedTX->GetSize();
+ while (j-- > 0) delete(CopyInst*)finishedTX->Del(j);
+ finishedTX->unlock();
+ }
+ }
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_REMOVESELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ int i = devices.GetSize();
+ while (i-- > 0)
+ {
+ DeviceView *device = (DeviceView *)devices.Get(i);
+ RemoveSelectedItems(device, &listTransfers->listview, getTransferQueue(device), row, false);
+ RemoveSelectedItems(device, &listTransfers->listview, getFinishedTransferQueue(device), row, true);
+ }
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_PAUSETRANSFERS:
+
+ if (false != TransferContext::IsAllPaused())
+ TransferContext::ResumeAll();
+ else
+ TransferContext::PauseAll();
+
+ for (int i=0;i<devices.GetSize();i++)
+ {
+ DeviceView *device_view = (DeviceView*)devices.Get(i);
+ SetEvent(device_view->transferContext.notifier);
+ }
+
+ SetDlgItemText(hwndDlg,IDC_BUTTON_PAUSETRANSFERS,WASABI_API_LNGSTRINGW((TransferContext::IsAllPaused()?IDS_RESUME:IDS_PAUSE)));
+ break;
+ }
+ break;
+ case WM_MOUSEMOVE:
+ if (wParam==MK_LBUTTON)
+ {
+ if (GetCapture() == hwndDlg) ReleaseCapture();
+ }
+ break;
+ }
+ return 0;
+}
+
+// deals with the horizontal (IDC_HDELIM) divider
+
+static void adiv_UpdPos(int yp)
+{
+ RECT r,old_divider_rect;
+ GetClientRect(m_hwnd,&r);
+
+ GetWindowRect(GetDlgItem(m_hwnd,IDC_HDELIM),&old_divider_rect);
+ ScreenToClient(m_hwnd,(LPPOINT)&old_divider_rect);
+ ScreenToClient(m_hwnd,((LPPOINT)&old_divider_rect)+1);
+ if (yp < 50)
+ {
+ m_nodrawtopborders=1;
+ yp=20;
+ }
+ else m_nodrawtopborders=0;
+ if (yp > r.bottom-42-30)
+ {
+ yp=r.bottom-42;
+ m_nodrawtopborders=2;
+ }
+
+ int x;
+ for (x = 0; x < 4; x ++)
+ {
+ RECT myoldr;
+ GetWindowRect(GetDlgItem(m_hwnd,resize_rlist[x].id),&myoldr);
+
+ ScreenToClient(m_hwnd,(LPPOINT)&myoldr);
+ ScreenToClient(m_hwnd,((LPPOINT)&myoldr)+1);
+ switch (x)
+ {
+ case 0:
+ resize_rlist[x].rinfo.bottom=yp - old_divider_rect.top + myoldr.bottom;
+ break;
+ case 1:
+ resize_rlist[x].rinfo.top=yp - old_divider_rect.top + myoldr.top;
+ break;
+ case 2:
+ case 3:
+ {
+ int h=resize_rlist[x].rinfo.bottom - resize_rlist[x].rinfo.top;
+ resize_rlist[x].rinfo.top = yp - old_divider_rect.top + resize_rlist[x].rinfo.top;
+ resize_rlist[x].rinfo.bottom = resize_rlist[x].rinfo.top + h;
+ }
+ break;
+ }
+ }
+ cr_resize(m_hwnd,resize_rlist,4);
+}
+
+static INT_PTR CALLBACK adiv_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if (uMsg == WM_LBUTTONDOWN)
+ {
+ SetForegroundWindow(hwndDlg);
+ SetCapture(hwndDlg);
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ POINT p;
+ GetCursorPos(&p);
+ ScreenToClient(hwndDlg,&p);
+ adiv_clickoffs=p.y;
+ }
+ else if (uMsg == WM_SETCURSOR)
+ {
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ return TRUE;
+ }
+ else if (uMsg == WM_MOUSEMOVE && GetCapture()==hwndDlg)
+ {
+ POINT p;
+ GetCursorPos(&p);
+ ScreenToClient(GetParent(hwndDlg),&p);
+ adiv_UpdPos(p.y-adiv_clickoffs);
+ RECT r;
+ GetClientRect(GetParent(hwndDlg),&r);
+ if (r.bottom > r.top)
+ {
+ int percent = ((p.y-adiv_clickoffs) * 100000) / (r.bottom-r.top);
+ adivpos = percent;
+ }
+ }
+ else if (uMsg == WM_MOUSEMOVE)
+ {
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ }
+ else if (uMsg == WM_LBUTTONUP)
+ {
+ ReleaseCapture();
+ }
+ return CallWindowProc(adiv_oldWndProc,hwndDlg,uMsg,wParam,lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/view_pmp_media.cpp b/Src/Plugins/Library/ml_pmp/view_pmp_media.cpp
new file mode 100644
index 00000000..ade8f61d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/view_pmp_media.cpp
@@ -0,0 +1,3205 @@
+#include "main.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "resource1.h"
+#include "SkinnedListView.h"
+#include "DeviceView.h"
+#include "ArtistAlbumLists.h"
+#include "mt19937ar.h" // random number generator
+#include "api__ml_pmp.h"
+#include "..\..\General\gen_ml/graphics.h"
+#include <tataki/export.h>
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+#include "AlbumArtListView.h"
+#include "./local_menu.h"
+#include "metadata_utils.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+
+extern winampMediaLibraryPlugin plugin;
+extern int currentViewedPlaylist;
+extern DeviceView * currentViewedDevice;
+extern HMENU m_context_menus;
+extern C_ItemList devices;
+
+extern void editInfo(C_ItemList * items, Device * dev, HWND centerWindow); // from editinfo.cpp
+
+static INT_PTR CALLBACK pmp_common_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int (*wad_handleDialogMsgs)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void (*wad_DrawChildWindowBorders)(HWND hwndDlg, int *tab, int tabsize);
+
+HWND hwndMediaView;
+static SkinnedListView *artistList=NULL, *albumList=NULL, *albumList2=NULL, *tracksList=NULL;
+
+static int adiv1_nodraw=0,adiv3_nodraw=0;
+static int m_nodrawtopborders=0;
+static int numFilters;
+
+static int adiv1pos=-1, adiv2pos=-1, adiv3pos=-1;
+static int refineHidden=0;
+
+static BOOL g_displaysearch = TRUE;
+static BOOL g_displayrefine = TRUE;
+static BOOL g_displaystatus = TRUE;
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0, header = 0;
+viewButtons view = {0};
+static bool noSearchTimer=false;
+
+static ArtistAlbumLists *aacontents=NULL;
+static PrimaryListContents *tracks=NULL;
+
+static void UpdateStatus(HWND hwndDlg, bool full = false) {
+ wchar_t buf[1024]=L"";
+ if (tracks)
+ {
+ tracks->GetInfoString(buf);
+ SetDlgItemText(hwndDlg,IDC_STATUS,buf);
+ }
+
+ if (full && currentViewedDevice && currentViewedDevice->isCloudDevice)
+ {
+ int usedPercent = 0;
+
+ __int64 available = currentViewedDevice->dev->getDeviceCapacityAvailable();
+ __int64 capacity = currentViewedDevice->dev->getDeviceCapacityTotal();
+
+ if(capacity > 0) usedPercent = (int)((((__int64)100)*available) / capacity);
+
+ if (!header)
+ {
+ wchar_t buf[128], status[128], availStr[100]=L"";
+ currentViewedDevice->dev->getTrackExtraInfo(0, L"cloud_status", status, ARRAYSIZE(status));
+
+ WASABI_API_LNG->FormattedSizeString(availStr, ARRAYSIZE(availStr), (available > 0 ? available : capacity));
+ wsprintf(buf, L"%s %s %s", availStr, (available > 0 ? L"free" : L"used"), status);
+ SetDlgItemText(hwndDlg, IDC_HEADER_DEVICE_SIZE, buf);
+ SendDlgItemMessage(hwndDlg, IDC_HEADER_DEVICE_BAR, PBM_SETPOS, (100 - usedPercent), 0);
+ }
+ }
+}
+
+static wchar_t *playmode = L"viewplaymode";
+
+typedef void (WINAPI *DIVIDERMOVED)(HWND, INT, LPARAM);
+typedef struct _DIVIDER
+{
+ BOOL fVertical;
+ DIVIDERMOVED callback;
+ LPARAM param;
+ WNDPROC fnOldProc;
+ BOOL fUnicode;
+ INT clickoffs;
+} DIVIDER;
+
+#define GET_DIVIDER(hwnd) (DIVIDER*)GetPropW(hwnd, L"DIVDATA")
+
+static BOOL AttachDivider(HWND hwnd, BOOL fVertical, DIVIDERMOVED callback, LPARAM param);
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void WINAPI OnDividerMoved(HWND hwnd, INT nPos, LPARAM param);
+void LayoutWindows(HWND hwnd, BOOL fRedraw, INT simple);
+
+static HBITMAP ConvertTo24bpp(HBITMAP bmp, int bpp)
+{
+ HDC hdcMem, hdcMem2;
+ HBITMAP hbm24;
+ BITMAP bm;
+
+ GetObjectW(bmp, sizeof(BITMAP), &bm);
+
+ hdcMem = CreateCompatibleDC(0);
+ hdcMem2 = CreateCompatibleDC(0);
+
+ void *bits;
+ BITMAPINFOHEADER bi;
+
+ ZeroMemory (&bi, sizeof (bi));
+ bi.biSize = sizeof (bi);
+ bi.biWidth= bm.bmWidth;
+ bi.biHeight = -bm.bmHeight;
+ bi.biPlanes = 1;
+ bi.biBitCount= bpp;
+
+ hbm24 = CreateDIBSection(hdcMem2, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, NULL);
+
+ HBITMAP oBmp = (HBITMAP)SelectObject(hdcMem, bmp);
+ HBITMAP oBmp24 = (HBITMAP)SelectObject(hdcMem2, hbm24);
+
+ BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
+
+ SelectObject(hdcMem, oBmp);
+ SelectObject(hdcMem2, oBmp24);
+
+ DeleteDC(hdcMem);
+ DeleteDC(hdcMem2);
+
+
+ return hbm24;
+}
+
+C_ItemList * getSelectedItems(bool all=false) {
+ if(!currentViewedDevice) return NULL;
+ C_ItemList * selected = new C_ItemList;
+ int l = tracks->GetNumRows();
+ if(all || tracksList->listview.GetSelectedCount()==0) for(int i=0; i<l; i++) selected->Add((void*)tracks->GetTrack(i));
+ else for(int i=0; i<l; i++) if(tracksList->listview.GetSelected(i)) selected->Add((void*)tracks->GetTrack(i));
+ return selected;
+}
+
+int showContextMenu(int context, HWND hwndDlg, Device * dev, POINT pt) {
+
+ // does cloud specific menu hacks
+ int cloud_devices = 0;
+ HMENU cloud_menu = 0;
+ if (context < 2)
+ {
+ int mark = tracksList->listview.GetSelectionMark();
+ if (mark != -1)
+ {
+ // if wanting to do on the selection then use getSelectedItems()
+ //C_ItemList * items = getSelectedItems();
+ // otherwise only work on the selection mark for speed (and like how ml_local does things)
+ C_ItemList * items = new C_ItemList;
+ items->Add((void*)tracks->GetTrack(mark));
+ cloud_menu = (HMENU)dev->extraActions(DEVICE_GET_CLOUD_SOURCES_MENU, (intptr_t)&cloud_devices, 0, (intptr_t)items);
+ delete items;
+ }
+ }
+
+ HMENU menu = GetSubMenu(m_context_menus, context);
+ HMENU sendto = GetSubMenu(menu, 2);
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA, 0, 0, 0);
+
+ bool noRatings = !(!fieldsBits || (fieldsBits & SUPPORTS_RATING));
+ bool noEdit = dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA, 0, 0, 0) != 0;
+
+ HMENU ratingMenu = Menu_FindRatingMenu(menu, FALSE);
+ if (NULL != ratingMenu)
+ {
+ Menu_SetRatingValue(ratingMenu, -1);
+ }
+
+ // toggle text of the delete menu item as needed
+ MENUITEMINFOW mii = {sizeof(MENUITEMINFOW), 0};
+ mii.fMask = MIIM_STRING;
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(!currentViewedDevice->isCloudDevice ? IDS_DELETE : IDS_REMOVE);
+ mii.cch = wcslen(mii.dwTypeData);
+ // just make sure we've got a string to use here
+ if (mii.cch > 0)
+ {
+ SetMenuItemInfoW(menu, ID_TRACKSLIST_DELETE, FALSE, &mii);
+ }
+ EnableMenuItem(menu, ID_TRACKSLIST_DELETE, MF_BYCOMMAND |
+ (!currentViewedDevice->isCloudDevice ||
+ (currentViewedDevice->isCloudDevice &&
+ !currentViewedDevice->dev->extraActions(DEVICE_NOT_READY_TO_VIEW, 0, 0, 0))) ? MF_ENABLED : MF_GRAYED);
+
+ EnableMenuItem(menu, ID_TRACKSLIST_EDITSELECTEDITEMS, MF_BYCOMMAND | noEdit ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_0, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_1, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_2, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_3, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_4, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_5, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+
+ EnableMenuItem(menu, ID_TRACKSLIST_COPYTOLIBRARY, MF_BYCOMMAND | dev->copyToHardDriveSupported() ? MF_ENABLED : MF_GRAYED);
+ int num = 0;
+ // TODO remove once we've got cloud playlist implemented
+ if (0 == dev->extraActions(DEVICE_PLAYLISTS_UNSUPPORTED,0,0,0))
+ {
+ if (EnableMenuItem(menu, ID_ADDTOPLAYLIST_NEWPLAYLIST, MF_BYCOMMAND | (!cloud_menu ? MF_ENABLED : MF_GRAYED)) == -1)
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_STRING, 0};
+ wchar_t a[100] = {0};
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_SEND_TO_PL, a, 100);
+ m.wID = 2;
+ sendto = m.hSubMenu = CreatePopupMenu();
+ InsertMenuItemW(menu, 2, TRUE, &m);
+
+ m.fMask -= MIIM_SUBMENU;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_CMD_PLAYLIST_CREATE, a, 100);
+ m.wID = ID_ADDTOPLAYLIST_NEWPLAYLIST;
+ m.fState = (!cloud_menu ? MF_ENABLED : MF_GRAYED);
+ InsertMenuItemW(m.hSubMenu, 0, FALSE, &m);
+ }
+
+ num = dev->getPlaylistCount();
+ if(num > 1) AppendMenu(sendto, MF_SEPARATOR, 0, L"");
+ for(int i=1; i<num; i++) {
+ wchar_t buf[100] = {0};
+ dev->getPlaylistName(i, buf, sizeof(buf)/sizeof(wchar_t));
+ AppendMenu(sendto, 0, 100000 + i, buf);
+ }
+ }
+ else
+ {
+ if (DeleteMenu(menu, ID_ADDTOPLAYLIST_NEWPLAYLIST, MF_BYCOMMAND))
+ {
+ DeleteMenu(menu, 2, MF_BYPOSITION);
+ sendto = NULL;
+ }
+ }
+
+ if (cloud_menu)
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0};
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ m.fType = MFT_SEPARATOR;
+ InsertMenuItemW(menu, (sendto ? 3 : 2), TRUE, &m);
+
+ wchar_t a[100] = {0};
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100);
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_menu;
+ InsertMenuItemW(menu, (sendto ? 4 : 3), TRUE, &m);
+ }
+
+ if (-1 == pt.x && -1 == pt.y)
+ {
+ RECT itemRect;
+ int selected = ListView_GetNextItem(hwndDlg, -1, LVNI_ALL | LVNI_SELECTED);
+ ListView_GetItemRect(hwndDlg, (selected != -1 ? selected : 0), &itemRect, LVIR_BOUNDS);
+ pt.x = itemRect.left;
+ pt.y = itemRect.top;
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&pt, 1);
+ }
+
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY, pt.x, pt.y, hwndDlg, NULL);
+ if(num > 1) DeleteMenu(sendto,1,MF_BYPOSITION);
+ for(int i = 1; i < num; i++) DeleteMenu(sendto, 100000+i, MF_BYCOMMAND);
+ if (cloud_menu)
+ {
+ DeleteMenu(menu, (sendto ? 4 : 3), MF_BYPOSITION);
+ DeleteMenu(menu, (sendto ? 3 : 2), MF_BYPOSITION);
+ DestroyMenu(cloud_menu);
+ }
+ return r;
+}
+
+void handleContextMenuResult(int r, C_ItemList * items0=NULL, DeviceView * dev=NULL) {
+ if(!dev) dev = currentViewedDevice;
+ if(!dev) return;
+ C_ItemList * items = items0;
+ switch(r) {
+ case ID_TRACKSLIST_PLAYSELECTION:
+ case ID_TRACKSLIST_ENQUEUESELECTION:
+ {
+ if (!items) items = getSelectedItems();
+ dev->PlayTracks(items, 0, r==ID_TRACKSLIST_ENQUEUESELECTION, true, hwndMediaView);
+ if (!items0) delete items;
+ }
+ break;
+ case ID_ADDTOPLAYLIST_NEWPLAYLIST:
+ {
+ int n = dev->CreatePlaylist();
+ if (n == -1) return;
+ r = 100000 + n;
+ // the selected tracks will be added by the code at the end of this function
+ }
+ break;
+ case ID_TRACKSLIST_SELECTALL:
+ {
+ int num = tracksList->listview.GetCount();
+ for (int x = 0; x < num; x ++) tracksList->listview.SetSelected(x);
+ }
+ break;
+ case ID_TRACKSLIST_EDITSELECTEDITEMS:
+ {
+ if (!items) items = getSelectedItems();
+ editInfo(items, dev->dev, CENTER_OVER_ML_VIEW);
+ }
+ break;
+ case ID_RATE_5:
+ case ID_RATE_4:
+ case ID_RATE_3:
+ case ID_RATE_2:
+ case ID_RATE_1:
+ case ID_RATE_0:
+ {
+ if (!items) items = getSelectedItems();
+ for(int i = 0; i<items->GetSize(); i++)
+ dev->dev->setTrackRating((songid_t)items->Get(i), ID_RATE_0 - r);
+ if (tracksList) tracksList->UpdateList(true);
+ dev->DevicePropertiesChanges();
+ }
+ break;
+ case ID_TRACKSLIST_DELETE:
+ {
+ wchar_t buf[256] = {0};
+ if (!items) items = getSelectedItems();
+ wsprintf(buf,WASABI_API_LNGSTRINGW((!currentViewedDevice->isCloudDevice ? IDS_PHYSICALLY_REMOVE_X_TRACKS : IDS_CLOUD_REMOVE_X_TRACKS)),items->GetSize());
+ bool ckdev = (dev == currentViewedDevice);
+ wchar_t titleStr[32] = {0};
+ if (MessageBox(hwndMediaView, buf, WASABI_API_LNGSTRINGW_BUF(IDS_ARE_YOU_SURE, titleStr, 32), MB_YESNO | MB_ICONQUESTION) == IDYES) {
+ if (ckdev && !currentViewedDevice) return;
+ dev->DeleteTracks(items, CENTER_OVER_ML_VIEW);
+ if (aacontents && dev == currentViewedDevice) { // full artist-album refresh
+ GetDlgItemText(hwndMediaView, IDC_QUICKSEARCH, buf, sizeof(buf)/sizeof(wchar_t));
+ // TODO async
+ if (aacontents) aacontents->SetSearch(buf);
+ GetDlgItemText(hwndMediaView, IDC_REFINE, buf, sizeof(buf)/sizeof(wchar_t));
+ if (aacontents) aacontents->SetRefine(buf);
+ if (artistList) artistList->UpdateList();
+ if (albumList) albumList->UpdateList();
+ if (albumList2) albumList2->UpdateList();
+ }
+ if (tracksList) tracksList->UpdateList();
+ dev->DevicePropertiesChanges();
+ UpdateStatus(hwndMediaView, true);
+ }
+ }
+ break;
+ case ID_TRACKSLIST_COPYTOLIBRARY:
+ {
+ if (!items) items = getSelectedItems();
+ dev->CopyTracksToHardDrive(items);
+ }
+ break;
+ }
+
+ if(r > 100000) { // add songs to existing playlist
+ int num = dev->dev->getPlaylistCount();
+ if(r < num + 100000) {
+ r-=100000;
+ if(!items) items = getSelectedItems();
+ for(int i = 0; i < items->GetSize(); i++) dev->dev->addTrackToPlaylist(r,(songid_t)items->Get(i));
+ dev->DevicePropertiesChanges();
+ }
+ }
+ else if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER) { // deals with cloud specific menus
+ if (!items) items = getSelectedItems();
+ int ret = dev->dev->extraActions(DEVICE_DO_CLOUD_SOURCES_MENU, (intptr_t)r, items->GetSize(), (intptr_t)items);
+ // only send a removal from the view if plug-in says so
+ if (ret) SendMessage(hwndMediaView, WM_USER+1, (WPARAM)items->Get(0), ret);
+ }
+
+ if (!items0) delete items;
+}
+
+void localizeFilter(const wchar_t *f, wchar_t *buf, int len) {
+ int r=0;
+ if(!_wcsicmp(f,L"Artist")) r = IDS_ARTIST;
+ else if(!_wcsicmp(f,L"Album")) r = IDS_ALBUM;
+ else if(!_wcsicmp(f,L"Genre")) r = IDS_GENRE;
+ else if(!_wcsicmp(f,L"Artist Index")) r = IDS_ARTIST_INDEX;
+ else if(!_wcsicmp(f,L"Year")) r = IDS_YEAR;
+ else if(!_wcsicmp(f,L"Album Artist")) r = IDS_ALBUM_ARTIST;
+ else if(!_wcsicmp(f,L"Publisher")) r = IDS_PUBLISHER;
+ else if(!_wcsicmp(f,L"Composer")) r = IDS_COMPOSER;
+ else if(!_wcsicmp(f,L"Album Artist Index")) r = IDS_ALBUM_ARTIST_INDEX;
+ else if(!_wcsicmp(f,L"Album Art")) r = IDS_ALBUM_ART;
+ else {buf[0]=0; return;}
+ WASABI_API_LNGSTRINGW_BUF(r,buf,len);
+}
+
+wchar_t *GetDefFilter(int i,int n) {
+ if(n==2) i++;
+ if(i==0) return L"Genre";
+ if(i==1) return L"Artist";
+ return L"Album";
+}
+
+static __forceinline BYTE pm(int a, int b) {
+ return (BYTE) ((a * b) / 0xff);
+}
+
+extern svc_imageLoader *GetPngLoaderService();
+
+static ARGB32 *LoadPngResource(HINSTANCE module, const wchar_t *name, const wchar_t *type,
+ BOOL premultily, int *width, int *height)
+{
+ svc_imageLoader *wasabiPngLoader;
+ HRSRC resource;
+ HANDLE resourceHandle;
+ ARGB32 *result;
+
+ if (NULL == WASABI_API_MEMMGR)
+ return NULL;
+
+ wasabiPngLoader = GetPngLoaderService();
+ if (NULL == wasabiPngLoader)
+ return NULL;
+
+ resource = FindResourceW(module, name, type);
+ if (NULL == resource)
+ return NULL;
+
+ result = NULL;
+
+ resourceHandle = LoadResource(module, resource);
+ if (NULL != resourceHandle)
+ {
+ unsigned long resourceSize = SizeofResource(module, resource);
+ if (0 != resourceSize)
+ {
+ void *resourceData = LockResource(resourceHandle);
+ if (NULL != resourceData)
+ {
+ result = (FALSE != premultily) ?
+ wasabiPngLoader->loadImage(resourceData, resourceSize, width, height) :
+ wasabiPngLoader->loadImageData(resourceData, resourceSize, width, height);
+ }
+ }
+ }
+
+ return result;
+}
+
+void DeleteSkinBitmap(SkinBitmap *skinBitmap)
+{
+ void *bits;
+ if (NULL == skinBitmap)
+ return;
+
+ bits = skinBitmap->getBits();
+ if (NULL != bits)
+ {
+ if (NULL != WASABI_API_MEMMGR)
+ WASABI_API_MEMMGR->sysFree(bits);
+ }
+
+ delete skinBitmap;
+}
+
+BOOL SetToolbarButtonBitmap(HWND hwnd, int controlId, const wchar_t *resourceName, ARGB32 fc)
+{
+ int width, height;
+ ARGB32 *data, *x, *end;
+ BYTE r, g, b;
+ SkinBitmap *sbm, *old;
+ HWND controlWindow;
+
+ controlWindow = GetDlgItem(hwnd, controlId);
+ if (NULL == controlWindow)
+ return FALSE;
+
+ data = LoadPngResource(plugin.hDllInstance, resourceName, RT_RCDATA, FALSE, &width, &height);
+ if (NULL == data)
+ return FALSE;
+
+ r = (BYTE)(fc & 0x00ff0000 >> 16);
+ g = (BYTE)(fc & 0x0000ff00 >> 8);
+ b = (BYTE)(fc & 0x000000ff);
+
+ x = data;
+ end = data + width*height;
+
+ while(x < end)
+ {
+ BYTE a = (BYTE)(~(*x))&0xff;
+ *(x++) = (a<<24) | (pm(r,a)<<16) | (pm(g,a)<<8) | pm(b,a);
+ }
+
+ sbm = new SkinBitmap(data, width, height);
+ old = (SkinBitmap*)SetWindowLongPtr(controlWindow, GWLP_USERDATA,(LONG_PTR)sbm);
+ DeleteSkinBitmap(old);
+ InvalidateRect(controlWindow, NULL, TRUE);
+
+ return TRUE;
+}
+
+typedef struct { int id, id2; } hi;
+
+void do_help(HWND hwnd, UINT id, HWND hTooltipWnd)
+{
+ RECT r;
+ POINT p;
+ GetWindowRect(GetDlgItem(hwnd, id), &r);
+ GetCursorPos(&p);
+ if (p.x >= r.left && p.x < r.right && p.y >= r.top && p.y < r.bottom)
+ {}
+ else
+ {
+ r.left += r.right;
+ r.left /= 2;
+ r.top += r.bottom;
+ r.top /= 2;
+ SetCursorPos(r.left, r.top);
+ }
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_INITIAL, 0);
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_RESHOW, 0);
+}
+
+#define C_BLAH
+#define DO_HELP() \
+static HWND hTooltipWnd; \
+C_BLAH \
+if (uMsg == WM_HELP) { \
+ HELPINFO *hi=(HELPINFO *)(lParam); \
+ if (hi->iContextType == HELPINFO_WINDOW) { do_help(hwndDlg,hi->iCtrlId,hTooltipWnd);} \
+ return TRUE; \
+} \
+if (uMsg == WM_NOTIFY) { LPNMHDR t=(LPNMHDR)lParam; if (t->code == TTN_POP) { SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,1000); SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,1000); } } \
+if (uMsg == WM_DESTROY && IsWindow(hTooltipWnd)) { DestroyWindow(hTooltipWnd); hTooltipWnd=NULL; } \
+if (uMsg == WM_INITDIALOG) { \
+ int x; \
+ hTooltipWnd = CreateWindow(TOOLTIPS_CLASS,(LPCWSTR)NULL,TTS_ALWAYSTIP|TTS_NOPREFIX, \
+ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, hwndDlg,NULL,GetModuleHandle(NULL),NULL); \
+ SendMessage(hTooltipWnd,TTM_SETMAXTIPWIDTH,0,587); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,250); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,500); \
+ for (x = 0; x < sizeof(helpinfo)/sizeof(helpinfo[0]); x ++) { \
+ TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_SUBCLASS|TTF_IDISHWND; \
+ ti.uId=(UINT_PTR)GetDlgItem(hwndDlg,helpinfo[x].id); ti.hwnd=hwndDlg; ti.lpszText=WASABI_API_LNGSTRINGW(helpinfo[x].id2); \
+ SendMessage(hTooltipWnd,TTM_ADDTOOL,0,(LPARAM) &ti); \
+ } \
+}
+
+static BOOL ListView_OnCustomDraw(HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static BOOL bDrawFocus;
+ static RECT rcView;
+ static CLOUDCOLUMNPAINT cloudColumnPaint;
+
+ *pResult = CDRF_DODEFAULT;
+
+ switch (plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ *pResult |= CDRF_NOTIFYITEMDRAW;
+ CopyRect(&rcView, &plvcd->nmcd.rc);
+
+ cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ cloudColumnPaint.hdc = plvcd->nmcd.hdc;
+ cloudColumnPaint.prcView = &rcView;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ *pResult |= CDRF_NOTIFYSUBITEMDRAW;
+ bDrawFocus = (CDIS_FOCUS & plvcd->nmcd.uItemState);
+ if (bDrawFocus)
+ {
+ plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
+ *pResult |= CDRF_NOTIFYPOSTPAINT;
+ }
+ return TRUE;
+
+ case CDDS_ITEMPOSTPAINT:
+ if (bDrawFocus)
+ {
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc);
+ rc.left += 3;
+ DrawFocusRect(plvcd->nmcd.hdc, &rc);
+ plvcd->nmcd.uItemState |= CDIS_FOCUS;
+ bDrawFocus = FALSE;
+ }
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+
+ case(CDDS_SUBITEM | CDDS_ITEMPREPAINT):
+ {
+ if (aacontents && aacontents->bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break;
+ cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ cloudColumnPaint.iSubItem = plvcd->iSubItem;
+
+ if (plvcd->nmcd.hdr.idFrom == IDC_LIST_TRACKS)
+ {
+ if (plvcd->iSubItem == tracks->cloudcol)
+ {
+ cloudColumnPaint.value = tracks->cloud_cache[plvcd->nmcd.dwItemSpec];
+ }
+ else
+ break;
+ }
+ else if (plvcd->nmcd.hdr.idFrom == IDC_LIST_ARTIST)
+ {
+ if (plvcd->iSubItem == aacontents->GetFilterList(0)->cloudcol)
+ {
+ wchar_t buf[16] = {0};
+ aacontents->GetFilterList(0)->GetCellText(plvcd->nmcd.dwItemSpec, plvcd->iSubItem, buf, 16);
+ cloudColumnPaint.value = _wtoi(buf);
+ }
+ else
+ break;
+ }
+ else if (plvcd->nmcd.hdr.idFrom == IDC_LIST_ALBUM)
+ {
+ if (plvcd->iSubItem == aacontents->GetFilterList(1)->cloudcol)
+ {
+ wchar_t buf[16] = {0};
+ aacontents->GetFilterList(1)->GetCellText(plvcd->nmcd.dwItemSpec, plvcd->iSubItem, buf, 16);
+ cloudColumnPaint.value = _wtoi(buf);
+ }
+ else
+ break;
+ }
+ else if (plvcd->nmcd.hdr.idFrom == IDC_LIST_ALBUM2)
+ {
+ if (plvcd->iSubItem == aacontents->GetFilterList(2)->cloudcol)
+ {
+ wchar_t buf[16] = {0};
+ aacontents->GetFilterList(2)->GetCellText(plvcd->nmcd.dwItemSpec, plvcd->iSubItem, buf, 16);
+ cloudColumnPaint.value = _wtoi(buf);
+ }
+ else
+ break;
+ }
+
+ cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
+ cloudColumnPaint.rgbBk = plvcd->clrTextBk;
+ cloudColumnPaint.rgbFg = plvcd->clrText;
+
+ if (MLCloudColumn_Paint(plugin.hwndLibraryParent, &cloudColumnPaint))
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void pmp_common_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL pmp_common_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0)
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem(hwndDlg, buttonId);
+ GetWindowRect(buttonHWND, &r);
+ MLSkinnedButton_SetDropDownState(buttonHWND, TRUE);
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if (!(flags & BPM_WM_COMMAND))
+ tpmFlags |= TPM_RETURNCMD;
+ int x = Menu_TrackSkinnedPopup(menu, tpmFlags, r.left, r.top, hwndDlg, NULL);
+ if ((flags & BPM_ECHO_WM_COMMAND) && x)
+ SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(x, 0), 0);
+ MLSkinnedButton_SetDropDownState(buttonHWND, FALSE);
+ return x;
+}
+
+static void pmp_common_PlayEnqueue(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(m_context_menus2, 0);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ pmp_common_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+static BOOL restoreDone;
+INT_PTR CALLBACK pmp_artistalbum_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ hi helpinfo[]={
+ {IDC_BUTTON_ARTMODE,IDS_AUDIO_BUTTON_TT1},
+ {IDC_BUTTON_VIEWMODE,IDS_AUDIO_BUTTON_TT2},
+ {IDC_BUTTON_COLUMNS,IDS_AUDIO_BUTTON_TT3},
+ };
+ DO_HELP();
+
+ if(hwndMediaView != hwndDlg && uMsg != WM_INITDIALOG) return 0;
+ if (wad_handleDialogMsgs) { BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a; }
+ if (artistList) { BOOL a=artistList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+ if (albumList) { BOOL a=albumList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+ if (albumList2) { BOOL a=albumList2->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+ if (tracksList) { BOOL a=tracksList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ groupBtn = gen_mlconfig->ReadInt(L"groupbtn", 1);
+ enqueuedef = (gen_mlconfig->ReadInt(L"enqueuedef", 0) == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_CUSTOM, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_pmp"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_CUSTOM, pszTextW);
+ }
+ else
+ {
+ customAllowed = FALSE;
+ }
+
+ restoreDone = FALSE;
+ playmode = L"viewplaymode";
+ hwndMediaView = hwndDlg;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+
+ if (!lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ header = 1;
+ else
+ header = currentViewedDevice->config->ReadInt(L"header",0);
+
+ numFilters = currentViewedDevice->config->ReadInt(L"media_numfilters",2);
+ if(numFilters != 3 && numFilters != 2) numFilters=2;
+
+ wchar_t filters[300] = {0}, *filtersp[3] = {0};
+ bool artfilter[3]={false,false,false};
+ for(int i = 0; i < numFilters; i++) {
+ filtersp[i]=&filters[i*100];
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",i);
+ lstrcpyn(filtersp[i],currentViewedDevice->config->ReadString(name,GetDefFilter(i,numFilters)),100);
+ if(!_wcsicmp(filtersp[i],L"Album Art")) artfilter[i]=true;
+ }
+ aacontents = new ArtistAlbumLists(currentViewedDevice->dev, currentViewedPlaylist, currentViewedDevice->config, filtersp,
+ numFilters, (currentViewedDevice->videoView ? 0 : -1), (!!currentViewedDevice->isCloudDevice));
+
+ if (!artfilter[0]) artistList = new SkinnedListView(aacontents->GetFilterList(0),IDC_LIST_ARTIST,plugin.hwndLibraryParent, hwndDlg, false);
+ else artistList = new AlbumArtListView(aacontents->GetFilterList(0),IDC_LIST_ARTIST,plugin.hwndLibraryParent, hwndDlg, false);
+ artistList->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ artistList->InitializeFilterData(0, currentViewedDevice->config);
+
+ if (!artfilter[1]) albumList = new SkinnedListView(aacontents->GetFilterList(1),IDC_LIST_ALBUM,plugin.hwndLibraryParent, hwndDlg, false);
+ else albumList = new AlbumArtListView(aacontents->GetFilterList(1),IDC_LIST_ALBUM,plugin.hwndLibraryParent, hwndDlg, false);
+ albumList->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ albumList->InitializeFilterData(1, currentViewedDevice->config);
+
+ if (numFilters == 3) {
+ if(!artfilter[2]) albumList2 = new SkinnedListView(aacontents->GetFilterList(2),IDC_LIST_ALBUM2,plugin.hwndLibraryParent, hwndDlg, false);
+ else albumList2 = new AlbumArtListView(aacontents->GetFilterList(2),IDC_LIST_ALBUM2,plugin.hwndLibraryParent, hwndDlg, false);
+ albumList2->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ albumList2->InitializeFilterData(2, currentViewedDevice->config);
+ }
+
+ if (currentViewedDevice->config->ReadInt(L"savefilter", 1))
+ {
+ SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, currentViewedDevice->config->ReadString(L"savedfilter", L""));
+ SetDlgItemTextW(hwndDlg, IDC_REFINE, currentViewedDevice->config->ReadString(L"savedrefinefilter", L""));
+ }
+ else
+ restoreDone = TRUE;
+
+ HWND list = GetDlgItem(hwndDlg, IDC_LIST_TRACKS);
+ // TODO need to be able to change the order of the tracks items (so cloud is in a more appropriate place)
+ //ListView_SetExtendedListViewStyleEx(list, LVS_EX_HEADERDRAGDROP, LVS_EX_HEADERDRAGDROP);
+ ListView_SetExtendedListViewStyle(list, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP);
+
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = list;
+ skin.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ skin.hwndToSkin = artistList->listview.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ skin.hwndToSkin = albumList->listview.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ if (numFilters == 3) {
+ skin.hwndToSkin = albumList2->listview.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+
+ if(!currentViewedDevice->config->ReadInt(L"media_scroll_0",0))
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,artistList->skinlistview_handle,ML_IPC_LISTVIEW_DISABLEHSCROLL);
+ if(!currentViewedDevice->config->ReadInt(L"media_scroll_1",0))
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,albumList->skinlistview_handle,ML_IPC_LISTVIEW_DISABLEHSCROLL);
+ if(numFilters == 3 && !currentViewedDevice->config->ReadInt(L"media_scroll_2",0))
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,albumList2->skinlistview_handle,ML_IPC_LISTVIEW_DISABLEHSCROLL);
+
+ if (aacontents)
+ {
+ artistList->UpdateList();
+ tracks = aacontents->GetTracksList();
+ }
+
+ adiv1pos = numFilters == 3?33333:50000;
+ adiv3pos = numFilters == 3?66667:0;
+ adiv2pos = 50000;
+ adiv1pos = currentViewedDevice->config->ReadInt(L"adiv1pos",adiv1pos);
+ if(numFilters == 3) adiv3pos = currentViewedDevice->config->ReadInt(L"adiv3pos",adiv3pos);
+ adiv2pos = currentViewedDevice->config->ReadInt(L"adiv2pos",adiv2pos);
+ if(numFilters == 3 && adiv1pos>adiv3pos) {
+ adiv1pos=33333;
+ adiv3pos=66667;
+ }
+ AttachDivider(GetDlgItem(hwndDlg, IDC_VDELIM), TRUE, OnDividerMoved, IDC_VDELIM);
+ if(numFilters == 3) AttachDivider(GetDlgItem(hwndDlg, IDC_VDELIM2), TRUE, OnDividerMoved, IDC_VDELIM2);
+ AttachDivider(GetDlgItem(hwndDlg, IDC_HDELIM), FALSE, OnDividerMoved, IDC_HDELIM);
+
+ int fieldsBits = (int)currentViewedDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits = -1;
+ if(!(fieldsBits & SUPPORTS_ALBUMART))
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),0,0,0,0,0,0); // get rid of art mode button if we don't support albumart
+
+ FLICKERFIX ff = {0};
+ ff.mode = FFM_ERASEINPAINT;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ INT ffcl[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM,
+ IDC_BUTTON_CLEARSEARCH, IDC_BUTTON_CLEARREFINE,
+ IDC_BUTTON_EJECT, IDC_BUTTON_SYNC, IDC_BUTTON_AUTOFILL,
+ IDC_STATUS, IDC_SEARCH_TEXT, IDC_QUICKSEARCH, IDC_REFINE,
+ IDC_REFINE_TEXT, IDC_BUTTON_ARTMODE, IDC_BUTTON_VIEWMODE,
+ IDC_BUTTON_COLUMNS,
+ // disabled cloud parts
+ /*IDC_HEADER_DEVICE_ICON, IDC_HEADER_DEVICE_NAME, IDC_HEADER_DEVICE_BAR,
+ IDC_HEADER_DEVICE_SIZE, IDC_HEADER_DEVICE_TRANSFER,*/
+ };
+ for (int i = 0; i < ARRAYSIZE(ffcl); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+
+ // skip the mode buttons
+ if (i < 13)
+ {
+ if (i < 3)
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0);
+ else
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ skin.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+ }
+
+ if (0 != currentViewedDevice->dev->extraActions(DEVICE_SYNC_UNSUPPORTED,0,0,0))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC), FALSE);
+
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ // do this now to get the cloud columns correctly known (doesn't work correctly if done before the skinning is setup)
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_TRACKS)), tracks->cloudcol);
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_ARTIST)), artistList->contents->cloudcol);
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_ALBUM)), albumList->contents->cloudcol);
+ if (numFilters == 3) MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_ALBUM2)), albumList2->contents->cloudcol);
+
+ g_displayrefine = !!(gen_mlconfig->ReadInt(L"audiorefine", 0));
+ adiv1_nodraw=0;
+ adiv3_nodraw=0;
+ m_nodrawtopborders=0;
+ PostMessage(hwndDlg, WM_DISPLAYCHANGE, 0, 0);
+ break;
+ }
+
+ case WM_DISPLAYCHANGE:
+ {
+ int (*wad_getColor)(int idx);
+ *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ ARGB32 fc = (wad_getColor?wad_getColor(WADLG_BUTTONFG):RGB(0xFF,0xFF,0xFF)) & 0x00FFFFFF;
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_ARTMODE, MAKEINTRESOURCE(IDR_TOOL_ALBUMART_ICON),fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_VIEWMODE, MAKEINTRESOURCE(IDR_TOOL_VIEWMODE_ICON),fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_COLUMNS, MAKEINTRESOURCE(IDR_TOOL_COLUMNS_ICON),fc);
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE, 0);
+ }
+ break;
+
+ case WM_DROPFILES:
+ return currentViewedDevice->TransferFromDrop((HDROP)wParam);
+
+ case WM_DESTROY:
+ if (aacontents) aacontents->bgQuery_Stop();
+ currentViewedDevice->config->WriteInt(L"adiv1pos",adiv1pos);
+ if(numFilters == 3) currentViewedDevice->config->WriteInt(L"adiv3pos",adiv3pos);
+ currentViewedDevice->config->WriteInt(L"adiv2pos",adiv2pos);
+ tracks=NULL;
+ hwndMediaView=NULL;
+ if (albumList) delete albumList; albumList=NULL;
+ if (artistList) delete artistList; artistList=NULL;
+ if (albumList2) delete albumList2; albumList2=NULL;
+ if (aacontents) delete aacontents; aacontents=NULL;
+ {
+ SkinBitmap *s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),GWLP_USERDATA);
+ DeleteSkinBitmap(s);
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),GWLP_USERDATA);
+ DeleteSkinBitmap(s);
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),GWLP_USERDATA);
+ DeleteSkinBitmap(s);
+ }
+ break;
+
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON) {
+ if(di->CtlID == IDC_BUTTON_ARTMODE || di->CtlID == IDC_BUTTON_VIEWMODE || di->CtlID == IDC_BUTTON_COLUMNS)
+ { // draw the toolbar buttons!
+ SkinBitmap* hbm = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,di->CtlID),GWLP_USERDATA);
+ if(hbm && di->rcItem.left != di->rcItem.right && di->rcItem.top != di->rcItem.bottom) {
+ DCCanvas dc(di->hDC);
+ if (di->itemState & ODS_SELECTED) hbm->blitAlpha(&dc,di->rcItem.left+6,di->rcItem.top+5);
+ else hbm->blitAlpha(&dc,di->rcItem.left+4,di->rcItem.top+3);
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_SETCURSOR:
+ case WM_LBUTTONDOWN:
+ {
+ static INT id[] = { IDC_VDELIM, IDC_HDELIM, IDC_VDELIM2 };
+ RECT rw;
+ POINT pt;
+
+ GetCursorPos(&pt);
+ for (INT i = 0; i < sizeof(id)/sizeof(INT); i++)
+ {
+ HWND hwndDiv = GetDlgItem(hwndDlg, id[i]);
+ if (!hwndDiv) continue;
+
+ GetWindowRect(hwndDiv, &rw);
+ if (PtInRect(&rw, pt))
+ {
+ if (WM_SETCURSOR == uMsg)
+ {
+ SetCursor(LoadCursor(NULL, (IDC_VDELIM == id[i] || IDC_VDELIM2 == id[i]) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+ }
+ else
+ {
+ SendMessage(hwndDiv, uMsg, wParam, MAKELPARAM(pt.x - rw.left, pt.y - rw.top));
+ return TRUE;
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags), 0);
+ }
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ int tab[] = {(m_nodrawtopborders==2)?0:(IDC_LIST_TRACKS|DCW_SUNKENBORDER),
+ IDC_QUICKSEARCH|DCW_SUNKENBORDER,
+ (refineHidden!=0)?0:(IDC_REFINE|DCW_SUNKENBORDER),
+ IDC_HDELIM|DCW_DIVIDER,
+ (!header?IDC_HDELIM2|DCW_DIVIDER:0),
+ IDC_VDELIM|DCW_DIVIDER,
+ numFilters == 3?IDC_VDELIM2|DCW_DIVIDER:0,
+ adiv1_nodraw==1?0:(IDC_LIST_ARTIST|DCW_SUNKENBORDER),
+ (adiv1_nodraw==2 || adiv3_nodraw==1)?0:(IDC_LIST_ALBUM|DCW_SUNKENBORDER),
+ (numFilters != 3 || adiv3_nodraw==2)?0:(IDC_LIST_ALBUM2|DCW_SUNKENBORDER)};
+ int size = sizeof(tab) / sizeof(tab[0]);
+ // do this to prevent drawing parts when the views are collapsed
+ if (m_nodrawtopborders==1) size -= 6;
+ if (wad_DrawChildWindowBorders) wad_DrawChildWindowBorders(hwndDlg,tab,size);
+ }
+ return TRUE;
+
+ case WM_ERASEBKGND:
+ return 1;
+
+ case WM_USER:
+ {
+ if (aacontents && aacontents->bgThread_Handle) break;
+
+ if (aacontents)
+ {
+ SkinnedListView * lists[3]={artistList,albumList,albumList2};
+ aacontents->SelectionChanged(wParam==IDC_LIST_ARTIST?0:(wParam==IDC_LIST_ALBUM?1:2),lists);
+ }
+ noSearchTimer=true;
+ SetDlgItemText(hwndDlg,IDC_REFINE,L"");
+ noSearchTimer=false;
+ UpdateStatus(hwndDlg);
+ wParam=0;
+ }
+ // run through
+ case WM_USER+2:
+ if (tracksList) tracksList->UpdateList(wParam==1);
+ break;
+
+ case WM_USER+1:
+ if (tracks) tracks->RemoveTrack((songid_t)wParam);
+ if (aacontents) aacontents->RemoveTrack((songid_t)wParam);
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->idFrom==IDC_LIST_ARTIST || l->idFrom==IDC_LIST_ALBUM || l->idFrom==IDC_LIST_ALBUM2)
+ {
+ switch(l->code) {
+ case LVN_ITEMCHANGED:
+ {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if ((lv->uNewState ^ lv->uOldState) & LVIS_SELECTED) {
+ PostMessage(hwndDlg,WM_USER,l->idFrom,0);
+ }
+ }
+ break;
+ case NM_RETURN:
+ case NM_DBLCLK: // play some songs!
+ {
+ C_ItemList * items = getSelectedItems(true);
+ currentViewedDevice->PlayTracks(items, 0, (!(GetAsyncKeyState(VK_SHIFT)&0x8000) ? (enqueuedef == 1) : (enqueuedef != 1)), false);
+ delete items;
+ break;
+ }
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case VK_DELETE:
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ }
+ break;
+ case 0x43: //C
+ if(GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_COPYTOLIBRARY);
+ }
+ break;
+ }
+ break;
+ }
+ }
+ else if(l->idFrom == IDC_LIST_TRACKS)
+ {
+ switch(l->code) {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case VK_DELETE:
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ }
+ break;
+ case 0x45: //E
+ bool noEdit = currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA,0,0,0)!=0;
+ if(!noEdit && GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ C_ItemList * items = getSelectedItems();
+ editInfo(items,currentViewedDevice->dev, CENTER_OVER_ML_VIEW);
+ delete items;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_APP + 3: // send by bgthread
+ if (wParam == 0x69)
+ {
+ if (aacontents)
+ {
+ aacontents->bgQuery_Stop();
+ tracks = aacontents->GetTracksList();
+
+ if (artistList) artistList->UpdateList();
+ if (albumList) albumList->UpdateList();
+ if (albumList2) albumList2->UpdateList();
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg, true);
+ }
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ pmp_common_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE, 0);
+ return 0;
+ }
+
+ case WM_TIMER:
+ switch(wParam) {
+ case 123:
+ {
+ if (aacontents && aacontents->bgThread_Handle)
+ {
+ HWND hwndList;
+ hwndList = tracksList->listview.getwnd();
+ if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+ }
+ break;
+
+ case 400:
+ KillTimer(hwndDlg,400);
+ currentViewedDevice->config->WriteInt(L"adiv1pos",adiv1pos);
+ if(numFilters == 3) currentViewedDevice->config->WriteInt(L"adiv3pos",adiv3pos);
+ currentViewedDevice->config->WriteInt(L"adiv2pos",adiv2pos);
+ break;
+
+ case 500:
+ {
+ KillTimer(hwndDlg,500);
+ wchar_t buf[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_QUICKSEARCH,buf,sizeof(buf)/sizeof(wchar_t));
+ noSearchTimer=true;
+ if (restoreDone) SetDlgItemText(hwndDlg,IDC_REFINE,L"");
+ noSearchTimer=false;
+ aacontents->SetSearch(buf, (!!currentViewedDevice->isCloudDevice));
+ if (!aacontents->bgThread_Handle)
+ {
+ if (artistList) artistList->UpdateList();
+ if (albumList) albumList->UpdateList();
+ if (albumList2) albumList2->UpdateList();
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+
+ if (!restoreDone)
+ {
+ restoreDone = TRUE;
+ KillTimer(hwndDlg,501);
+ SetTimer(hwndDlg,501,250,NULL);
+ }
+ }
+ break;
+
+ case 501:
+ {
+ KillTimer(hwndDlg,501);
+ wchar_t buf[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_REFINE,buf,sizeof(buf)/sizeof(wchar_t));
+ aacontents->SetRefine(buf, (!!currentViewedDevice->isCloudDevice));
+ if (!aacontents->bgThread_Handle)
+ {
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+ }
+ break;
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ if(wParam==MK_LBUTTON) {
+ if(GetCapture() == hwndDlg) ReleaseCapture();
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_BUTTON_ARTMODE:
+ {
+ int changed=0;
+ for(int i=0; i<3; i++) {
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",i);
+ wchar_t * str = currentViewedDevice->config->ReadString(name,GetDefFilter(i,numFilters));
+ if(!_wcsicmp(str,L"Album")) { currentViewedDevice->config->WriteString(name,L"Album Art"); changed=1; }
+ else if(!_wcsicmp(str,L"Album Art")) { currentViewedDevice->config->WriteString(name,L"Album"); changed=1; }
+ }
+ if (changed) PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ break;
+ case IDC_BUTTON_VIEWMODE:
+ {
+ struct {
+ const wchar_t *name;
+ int numfilters;
+ wchar_t* f[3];
+ int requiredFields;
+ } presets[] = {
+ {0,2,{L"Artist",L"Album"},0},
+ {0,2,{L"Artist",L"Album Art"},SUPPORTS_ALBUMART},
+ {0,2,{L"Album Artist",L"Album"},SUPPORTS_ALBUMARTIST},
+ {0,2,{L"Album Artist",L"Album Art"},SUPPORTS_ALBUMARTIST | SUPPORTS_ALBUMART},
+ {0,3,{L"Genre",L"Artist",L"Album"},SUPPORTS_GENRE | SUPPORTS_ALBUMART},
+ {0,2,{L"Genre",L"Album Art"},SUPPORTS_GENRE},
+ {0,3,{L"Year",L"Artist",L"Album"},SUPPORTS_YEAR},
+ {0,2,{L"Composer",L"Album"},SUPPORTS_COMPOSER},
+ {0,3,{L"Publisher",L"Artist",L"Album"},SUPPORTS_PUBLISHER},
+ };
+ HMENU menu = CreatePopupMenu();
+ int fieldsBits = (int)currentViewedDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits = -1;
+ bool checked=false;
+
+ wchar_t filters[300] = {0};
+ wchar_t *filtersp[3] = {0};
+ for(int i=0; i<numFilters; i++) {
+ filtersp[i]=&filters[i*100];
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",i);
+ lstrcpyn(filtersp[i],currentViewedDevice->config->ReadString(name,GetDefFilter(i,numFilters)),100);
+ }
+
+ for(int i=0; i < sizeof(presets)/sizeof(presets[0]); i++) {
+ if(!presets[i].requiredFields || (presets[i].requiredFields & fieldsBits) == presets[i].requiredFields) {
+ wchar_t buf[350] = {0};
+ if(!presets[i].name) {
+ wchar_t a[100] = {0}, b[100] = {0}, c[100] = {0};
+ localizeFilter(presets[i].f[0],a,100);
+ localizeFilter(presets[i].f[1],b,100);
+ if(presets[i].numfilters == 3) localizeFilter(presets[i].f[2],c,100);
+ if(presets[i].numfilters == 3) wsprintf(buf,L"%s/%s/%s",a,b,c);
+ else wsprintf(buf,L"%s/%s",a,b);
+ }
+ AppendMenu(menu,MF_STRING,i+1,presets[i].name?presets[i].name:buf);
+ if(numFilters == presets[i].numfilters && !_wcsicmp(presets[i].f[0],filtersp[0]) && !_wcsicmp(presets[i].f[1],filtersp[1]) && (numFilters == 2 || !_wcsicmp(presets[i].f[2],filtersp[2])))
+ { // this is our view...
+ CheckMenuItem(menu,i+1,MF_CHECKED);
+ checked=true;
+ }
+ }
+ }
+ AppendMenu(menu,MF_STRING,0x4000,WASABI_API_LNGSTRINGW(IDS_OTHER2));
+ if(!checked)
+ CheckMenuItem(menu,0x4000,MF_CHECKED);
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),&rc);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,rc.left,rc.bottom,hwndDlg,NULL);
+ DestroyMenu(menu);
+ if(r==0) break;
+ else if(r == 0x4000) {
+ extern int g_prefs_openpage;
+ g_prefs_openpage = (!currentViewedDevice->isCloudDevice ? 4 : 0);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&currentViewedDevice->devPrefsPage,IPC_OPENPREFSTOPAGE);
+
+ extern HWND m_hwndTab;
+ if (IsWindow(m_hwndTab))
+ {
+ TabCtrl_SetCurSel(m_hwndTab, g_prefs_openpage);
+ extern HWND OnSelChanged(HWND hwndDlg, HWND external = NULL, DeviceView *dev = NULL);
+ OnSelChanged(GetParent(m_hwndTab));
+ }
+ }
+ else {
+ r--;
+ for(int j=0; j<presets[r].numfilters; j++) {
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",j);
+ currentViewedDevice->config->WriteString(name,presets[r].f[j]);
+ }
+ currentViewedDevice->config->WriteInt(L"media_numfilters",presets[r].numfilters);
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ case IDC_BUTTON_COLUMNS:
+ {
+ HMENU themenu1 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU themenu2 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU themenu3 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU themenu4 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+
+ HMENU menu = CreatePopupMenu();
+ MENUITEMINFO m={sizeof(m),MIIM_TYPE | MIIM_ID | MIIM_SUBMENU,MFT_STRING,0};
+ wchar_t a[100] = {0}, b[100] = {0}, c[100] = {0};
+ localizeFilter(currentViewedDevice->config->ReadString(L"media_filter0",GetDefFilter(0,numFilters)),a,100);
+ localizeFilter(currentViewedDevice->config->ReadString(L"media_filter1",GetDefFilter(1,numFilters)),b,100);
+ localizeFilter(currentViewedDevice->config->ReadString(L"media_filter2",GetDefFilter(2,numFilters)),c,100);
+ wchar_t * d = WASABI_API_LNGSTRINGW(IDS_TRACKS);
+ m.wID = 0;
+ m.dwTypeData = a;
+ m.hSubMenu = artistList->GetMenu(true,0,currentViewedDevice->config,themenu1);
+ InsertMenuItem(menu,0,FALSE,&m);
+ m.wID = 1;
+ m.dwTypeData = b;
+ m.hSubMenu = albumList->GetMenu(true,1,currentViewedDevice->config,themenu2);
+ InsertMenuItem(menu,1,FALSE,&m);
+ if(numFilters == 3) {
+ m.wID = 2;
+ m.dwTypeData = c;
+ m.hSubMenu = albumList2->GetMenu(true,2,currentViewedDevice->config,themenu3);
+ InsertMenuItem(menu,2,FALSE,&m);
+ }
+ m.wID = 3;
+ m.dwTypeData = d;
+ m.hSubMenu = tracksList->GetMenu(false,0,currentViewedDevice->config,themenu4);
+ InsertMenuItem(menu,3,FALSE,&m);
+
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),&rc);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,rc.left,rc.bottom,hwndDlg,NULL);
+
+ artistList->ProcessMenuResult(r,true,0,currentViewedDevice->config,hwndDlg);
+ albumList->ProcessMenuResult(r,true,1,currentViewedDevice->config,hwndDlg);
+ if(numFilters == 3) albumList2->ProcessMenuResult(r,true,2,currentViewedDevice->config,hwndDlg);
+ tracksList->ProcessMenuResult(r,false,0,currentViewedDevice->config,hwndDlg);
+
+ DestroyMenu(menu);
+ DestroyMenu(themenu1);
+ DestroyMenu(themenu2);
+ DestroyMenu(themenu3);
+ DestroyMenu(themenu4);
+ }
+ break;
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE && !noSearchTimer) {
+ KillTimer(hwndDlg, 500);
+ SetTimer(hwndDlg, 500, 350, NULL);
+
+ HWND hwndList = artistList->listview.getwnd();
+ if (IsWindow(hwndList))
+ {
+ ListView_SetItemCountEx(hwndList, 0, 0);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+
+ hwndList = albumList->listview.getwnd();
+ if (IsWindow(hwndList))
+ {
+ ListView_SetItemCountEx(hwndList, 0, 0);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+
+ if (numFilters == 3)
+ {
+ hwndList = albumList2->listview.getwnd();
+ if (IsWindow(hwndList))
+ {
+ ListView_SetItemCountEx(hwndList, 0, 0);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+ }
+ }
+ break;
+ case IDC_REFINE:
+ if (HIWORD(wParam) == EN_CHANGE && !noSearchTimer) {
+ KillTimer(hwndDlg,501);
+ SetTimer(hwndDlg,501,250,NULL);
+ }
+ break;
+ case IDC_BUTTON_CLEARSEARCH:
+ {
+ SetDlgItemText(hwndDlg,IDC_QUICKSEARCH,L"");
+ break;
+ }
+ case IDC_BUTTON_CLEARREFINE:
+ SetDlgItemText(hwndDlg,IDC_REFINE,L"");
+ break;
+ case IDC_HEADER_DEVICE_TRANSFER:
+ {
+ if (!IsWindowEnabled(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC)))
+ {
+ MessageBox(hwndDlg, L"Transferring files from this device to another is not currently supported.",
+ L"Cloud Transfer Not Supported", MB_ICONINFORMATION);
+ break;
+ }
+ }
+ case IDC_BUTTON_SYNC:
+ if (!currentViewedDevice->isCloudDevice)
+ currentViewedDevice->Sync();
+ else
+ currentViewedDevice->CloudSync();
+ break;
+ case IDC_BUTTON_AUTOFILL:
+ currentViewedDevice->Autofill();
+ break;
+ case IDC_SEARCH_TEXT:
+ if (HIWORD(wParam) == STN_DBLCLK)
+ {
+ // TODO decide what to do for the 'all_sources'
+ if (currentViewedDevice->isCloudDevice &&
+ lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ {
+ header = !header;
+ currentViewedDevice->config->WriteInt(L"header",header);
+ LayoutWindows(hwndMediaView, TRUE, 0);
+ UpdateStatus(hwndDlg, true);
+ ShowWindow(hwndDlg, 0);
+ ShowWindow(hwndDlg, 1);
+ }
+ }
+ break;
+ }
+ break;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+ }
+ return pmp_common_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+static void getStars(int stars, wchar_t * buf, int buflen) {
+ wchar_t * r=L"";
+ switch(stars) {
+ case 1: r=L"\u2605"; break;
+ case 2: r=L"\u2605\u2605"; break;
+ case 3: r=L"\u2605\u2605\u2605"; break;
+ case 4: r=L"\u2605\u2605\u2605\u2605"; break;
+ case 5: r=L"\u2605\u2605\u2605\u2605\u2605"; break;
+ }
+ lstrcpyn(buf,r,buflen);
+}
+
+__inline void TimetToFileTime(time_t t, LPFILETIME pft)
+{
+ LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
+ pft->dwLowDateTime = (DWORD) ll;
+ pft->dwHighDateTime = ll >>32;
+}
+
+void timeToString(__time64_t time, wchar_t * buf, int buflen) {
+ if((__int64)time<=0) { lstrcpyn(buf,L"",buflen); return; }
+
+ FILETIME ft = {0};
+ SYSTEMTIME st = {0};
+ TimetToFileTime(time, &ft);
+ FileTimeToSystemTime(&ft, &st);
+
+ int adjust = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, buf, buflen);
+ buf[adjust-1] = ' ';
+ GetTimeFormat(LOCALE_USER_DEFAULT, NULL, &st, NULL, &buf[adjust], buflen - adjust);
+}
+
+extern void GetInfoString(wchar_t * buf, Device * dev, int numTracks, __int64 totalSize, int totalPlayLength, int cloud);
+
+class PlaylistContents : public PrimaryListContents {
+public:
+ int playlistId;
+ Device * dev;
+ PlaylistContents(int playlistId, Device * dev) {
+ this->playlistId = playlistId;
+ this->dev = dev;
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits = -1;
+ C_Config *c = currentViewedDevice->config;
+ this->config = c;
+ fields.Add(new ListField(-1,20, WASABI_API_LNGSTRINGW(IDS_NUMBER),c));
+ if(fieldsBits & SUPPORTS_ARTIST) fields.Add(new ListField(1000, 200,WASABI_API_LNGSTRINGW(IDS_ARTIST),c));
+ if(fieldsBits & SUPPORTS_TITLE) fields.Add(new ListField(1001, 200,WASABI_API_LNGSTRINGW(IDS_TITLE),c));
+ if(fieldsBits & SUPPORTS_ALBUM) fields.Add(new ListField(1002, 200,WASABI_API_LNGSTRINGW(IDS_ALBUM),c));
+ if(fieldsBits & SUPPORTS_LENGTH) fields.Add(new ListField(1003, 64, WASABI_API_LNGSTRINGW(IDS_LENGTH),c));
+ if(fieldsBits & SUPPORTS_TRACKNUM) fields.Add(new ListField(1004, 50, WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER),c));
+ if(fieldsBits & SUPPORTS_DISCNUM) fields.Add(new ListField(1005, 38, WASABI_API_LNGSTRINGW(IDS_DISC),c));
+ if(fieldsBits & SUPPORTS_GENRE) fields.Add(new ListField(1006, 100,WASABI_API_LNGSTRINGW(IDS_GENRE),c));
+ if(fieldsBits & SUPPORTS_YEAR) fields.Add(new ListField(1007, 38, WASABI_API_LNGSTRINGW(IDS_YEAR),c));
+ if(fieldsBits & SUPPORTS_BITRATE) fields.Add(new ListField(1008, 45, WASABI_API_LNGSTRINGW(IDS_BITRATE),c));
+ if(fieldsBits & SUPPORTS_SIZE) fields.Add(new ListField(1009, 90, WASABI_API_LNGSTRINGW(IDS_SIZE),c));
+ if(fieldsBits & SUPPORTS_PLAYCOUNT) fields.Add(new ListField(1010, 64, WASABI_API_LNGSTRINGW(IDS_PLAY_COUNT),c));
+ if(fieldsBits & SUPPORTS_RATING) fields.Add(new ListField(1011, 64, WASABI_API_LNGSTRINGW(IDS_RATING),c));
+ if(fieldsBits & SUPPORTS_LASTPLAYED) fields.Add(new ListField(1012, 100,WASABI_API_LNGSTRINGW(IDS_LAST_PLAYED),c));
+ if(fieldsBits & SUPPORTS_ALBUMARTIST) fields.Add(new ListField(1013, 200,WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST),c,true));
+ if(fieldsBits & SUPPORTS_PUBLISHER) fields.Add(new ListField(1014, 200,WASABI_API_LNGSTRINGW(IDS_PUBLISHER),c,true));
+ if(fieldsBits & SUPPORTS_COMPOSER) fields.Add(new ListField(1015, 200,WASABI_API_LNGSTRINGW(IDS_COMPOSER),c,true));
+ if(fieldsBits & SUPPORTS_MIMETYPE) fields.Add(new ListField(1016, 100,WASABI_API_LNGSTRINGW(IDS_MIME_TYPE),c,true));
+ if(fieldsBits & SUPPORTS_DATEADDED) fields.Add(new ListField(1017, 100,WASABI_API_LNGSTRINGW(IDS_DATE_ADDED),c,true));
+ this->SortColumns();
+ }
+
+ //virtual ~PlaylistContents() { }
+ virtual int GetNumColumns() { return fields.GetSize(); }
+
+ virtual int GetNumRows() { return dev->getPlaylistLength(playlistId); }
+
+ virtual int GetColumnWidth(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->width;
+ return 0;
+ }
+
+ virtual wchar_t * GetColumnTitle(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->name;
+ return L"";
+ }
+
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) {
+ if(col >=0 && col < fields.GetSize()) col = ((ListField *)fields.Get(col))->field;
+ if(col != -1) col -= 1000;
+ songid_t s = dev->getPlaylistTrack(playlistId,row);
+ buf[0]=0;
+ switch(col) {
+ case -1: wsprintf(buf,L"%d",row+1); return;
+ case 0: dev->getTrackArtist(s,buf,buflen); return;
+ case 1: dev->getTrackTitle(s,buf,buflen); return;
+ case 2: dev->getTrackAlbum(s,buf,buflen); return;
+ case 3: { int l=dev->getTrackLength(s); if (l>=0) wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; }
+ case 4: { int d = dev->getTrackTrackNum(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 5: { int d = dev->getTrackDiscNum(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 6: dev->getTrackGenre(s,buf,buflen); return;
+ case 7: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 8: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; }
+ case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return;
+ case 10: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 11: getStars(dev->getTrackRating(s),buf,buflen); return;
+ case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return;
+ case 13: dev->getTrackAlbumArtist(s,buf,buflen); return;
+ case 14: dev->getTrackPublisher(s,buf,buflen); return;
+ case 15: dev->getTrackComposer(s,buf,buflen); return;
+ }
+ }
+
+ virtual void GetInfoString(wchar_t * buf) {
+ __int64 totalSize=0;
+ int totalPlayLength=0;
+ int millis=0;
+ int l = dev->getPlaylistLength(playlistId);
+ for(int i=0; i < l; i++) {
+ songid_t s = dev->getPlaylistTrack(playlistId,i);
+ totalSize += (__int64)dev->getTrackSize(s);
+ int len=dev->getTrackLength(s);
+ totalPlayLength += len/1000;
+ millis += len%1000;
+ }
+ totalPlayLength += millis/1000;
+ ::GetInfoString(buf, dev, l, totalSize, totalPlayLength, 0);
+ }
+
+ virtual songid_t GetTrack(int pos) { return dev->getPlaylistTrack(playlistId,pos); }
+
+ virtual void ColumnResize(int col, int newWidth) {
+ if(col >=0 && col < fields.GetSize()) {
+ ListField * lf = (ListField *)fields.Get(col);
+ lf->width = newWidth;
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"colWidth_%d",lf->field);
+ currentViewedDevice->config->WriteInt(buf,newWidth);
+ }
+ }
+};
+
+static INT_PTR CALLBACK find_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ SetDlgItemText(hwndDlg,IDC_EDIT,currentViewedDevice->config->ReadString(L"plfind",L""));
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ {
+ wchar_t buf[256] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT,buf,sizeof(buf)/sizeof(wchar_t));
+ buf[255]=0;
+ currentViewedDevice->config->WriteString(L"plfind",buf);
+ EndDialog(hwndDlg,(INT_PTR)_wcsdup(buf));
+ }
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+extern C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs, Device * dev, bool cloud);
+
+static int m_pldrag;
+
+INT_PTR CALLBACK pmp_playlist_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static bool pldrag_changed;
+ if (wad_handleDialogMsgs) { BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a; }
+ if (tracksList) { BOOL a=tracksList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ groupBtn = gen_mlconfig->ReadInt(L"groupbtn", 1);
+ enqueuedef = (gen_mlconfig->ReadInt(L"enqueuedef", 0) == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_CUSTOM, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_pmp"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_CUSTOM, pszTextW);
+ }
+ else
+ {
+ customAllowed = FALSE;
+ }
+
+ playmode = L"plplaymode";
+ hwndMediaView = hwndDlg;
+ pldrag_changed=false;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+ tracks = new PlaylistContents(currentViewedPlaylist,currentViewedDevice->dev);
+ m_pldrag=-1;
+
+ {
+ MLSKINWINDOW skin = {0};
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ FLICKERFIX ff = {0};
+ ff.mode = FFM_ERASEINPAINT;
+ INT ffcl[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_BUTTON_SORT, IDC_BUTTON_EJECT, IDC_STATUS};
+ for (int i = 0; i < sizeof(ffcl) / sizeof(INT); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn && (i < 3) ? SWBS_SPLITBUTTON : 0);
+ skin.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ if(tracks) delete tracks; tracks=0;
+ break;
+
+ case WM_DROPFILES:
+ return currentViewedDevice->TransferFromDrop((HDROP)wParam, currentViewedPlaylist);
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->idFrom==IDC_LIST_TRACKS)
+ switch(l->code) {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case 0x46: //F
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ wchar_t * findstr = (wchar_t*)WASABI_API_DIALOGBOXPARAMW(IDD_FIND,hwndDlg,find_dlgproc, (LPARAM)hwndDlg);
+ if(!findstr) break;
+ C_ItemList songs;
+ int l = currentViewedDevice->dev->getPlaylistLength(currentViewedPlaylist);
+ int i;
+ for(i=0; i<l; i++) songs.Add((void*)currentViewedDevice->dev->getPlaylistTrack(currentViewedPlaylist,i));
+ C_ItemList * found = FilterSongs(findstr,&songs,currentViewedDevice->dev,0);
+ free(findstr);
+ int j=0;
+ HWND wnd = tracksList->listview.getwnd();
+ for(i=0; i<l; i++) {
+ if(found->Get(j) == songs.Get(i)) {
+ ListView_SetItemState(wnd,i,LVIS_SELECTED,LVIS_SELECTED);
+ if(j++ == 0) ListView_EnsureVisible(wnd,i,TRUE);
+ }
+ else ListView_SetItemState(wnd,i,0,LVIS_SELECTED);
+ }
+ delete found;
+ }
+ }
+ break;
+ case VK_DELETE:
+ if(!GetAsyncKeyState(VK_CONTROL)){
+ if(!GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ else{
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(ID_TRACKSLIST_REMOVEFROMPLAYLIST,0),0);
+ }
+ }
+ break;
+ case 0x43: //C
+ if(GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_COPYTOLIBRARY);
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags), 2);
+ }
+ return TRUE;
+
+ case WM_DISPLAYCHANGE:
+ {
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE, 2);
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ pmp_common_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE, 2);
+ return 0;
+ }
+
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_LIST_TRACKS|DCW_SUNKENBORDER};
+ int size = sizeof(tab) / sizeof(tab[0]);
+ if (wad_DrawChildWindowBorders) wad_DrawChildWindowBorders(hwndDlg,tab,size);
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ m_pldrag=-1;
+ //m_pldrag = tracksList->listview.FindItemByPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
+ break;
+
+ case WM_LBUTTONUP:
+ m_pldrag=-1;
+ if(GetCapture() == hwndDlg) ReleaseCapture();
+ if(pldrag_changed) { currentViewedDevice->DevicePropertiesChanges(); pldrag_changed=false;}
+ break;
+
+ case WM_MOUSEMOVE:
+ if(wParam==MK_LBUTTON) {
+ if(m_pldrag==-1) { m_pldrag=tracksList->listview.FindItemByPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)); break; } // m_pldrag=GET_Y_LPARAM(lParam);
+ int p = tracksList->listview.FindItemByPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)); // new point
+ if(p==-1) break;
+ //work out how much to move and how
+ int h=1; //height of one item
+ HWND listWnd = tracksList->listview.getwnd();
+ Device * dev = currentViewedDevice->dev;
+ if(p - m_pldrag >= h) {
+ //moved down
+ int start=-1,end=-1;
+ int i;
+ int l = dev->getPlaylistLength(currentViewedPlaylist);
+ for(i=l-1; i>=0; i--) if(ListView_GetItemState(listWnd, i, LVIS_SELECTED) == LVIS_SELECTED) {
+ if(i == l-1) break;
+ if(end == -1) end = i+1;
+ start = i;
+ //change playlist
+ dev->playlistSwapItems(currentViewedPlaylist,i,i+1);
+ pldrag_changed=true;
+ //set selection correctly
+ ListView_SetItemState(listWnd,i,0,LVIS_SELECTED);
+ ListView_SetItemState(listWnd,i+1,LVIS_SELECTED,LVIS_SELECTED);
+ if(ListView_GetItemState(listWnd, i, LVIS_FOCUSED)==LVIS_FOCUSED) {
+ ListView_SetItemState(listWnd,i,0,LVIS_FOCUSED);
+ ListView_SetItemState(listWnd,i+1,LVIS_FOCUSED,LVIS_FOCUSED);
+ }
+ }
+ if(start != -1) {
+ ListView_RedrawItems(listWnd,start,end);
+ m_pldrag += h;
+ }
+ }
+ else if(m_pldrag - p >= h){
+ //moved up
+ int start=-1,end=-1;
+ int i;
+ int l = dev->getPlaylistLength(currentViewedPlaylist);
+ for(i=0; i<l; i++) if(ListView_GetItemState(listWnd, i, LVIS_SELECTED) == LVIS_SELECTED) {
+ if(i == 0) break;
+ if(start == -1) start = i-1;
+ end = i;
+ //change playlist
+ dev->playlistSwapItems(currentViewedPlaylist,i,i-1);
+ pldrag_changed=true;
+ //set selection correctly
+ ListView_SetItemState(listWnd,i,0,LVIS_SELECTED);
+ ListView_SetItemState(listWnd,i-1,LVIS_SELECTED,LVIS_SELECTED);
+ if(ListView_GetItemState(listWnd, i, LVIS_FOCUSED)==LVIS_FOCUSED) {
+ ListView_SetItemState(listWnd,i,0,LVIS_FOCUSED);
+ ListView_SetItemState(listWnd,i-1,LVIS_FOCUSED,LVIS_FOCUSED);
+ }
+ }
+ if(start != -1) {
+ ListView_RedrawItems(listWnd,start,end);
+ m_pldrag -= h;
+ }
+ }
+ return 0;
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case ID_TRACKSLIST_REMOVEFROMPLAYLIST:
+ {
+ int l = tracksList->listview.GetCount();
+ while(l-- > 0) if(tracksList->listview.GetSelected(l))
+ currentViewedDevice->dev->removeTrackFromPlaylist(currentViewedPlaylist,l);
+ tracksList->UpdateList();
+ currentViewedDevice->DevicePropertiesChanges();
+ }
+ break;
+ case IDC_BUTTON_SORT:
+ {
+ HMENU menu = GetSubMenu(m_context_menus,5);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY, p.x, p.y, hwndDlg, NULL);
+ if(r >= ID_SORTPLAYLIST_ARTIST && r <= ID_SORTPLAYLIST_LASTPLAYED) {
+ currentViewedDevice->dev->sortPlaylist(currentViewedPlaylist,r - ID_SORTPLAYLIST_ARTIST);
+ }
+ else if(r == ID_SORTPLAYLIST_RANDOMIZE) {
+ int elems = currentViewedDevice->dev->getPlaylistLength(currentViewedPlaylist);
+ if (elems > 1) {
+ for (int p = 0; p < elems; p ++) {
+ int np=genrand_int31()%elems;
+ if (p != np) // swap pp and np
+ currentViewedDevice->dev->playlistSwapItems(currentViewedPlaylist,p,np);
+ }
+ }
+ }
+ else if(r == ID_SORTPLAYLIST_REVERSEPLAYLIST) {
+ int i=0;
+ int j = currentViewedDevice->dev->getPlaylistLength(currentViewedPlaylist) - 1;
+ while(i < j)
+ currentViewedDevice->dev->playlistSwapItems(currentViewedPlaylist,i++,j--);
+ }
+ if(r > 0) {
+ tracksList->UpdateList(true);
+ currentViewedDevice->DevicePropertiesChanges();
+ }
+ }
+ break;
+ }
+ break; // WM_COMMAND
+ }
+ return pmp_common_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+INT_PTR CALLBACK pmp_video_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+
+ if (wad_handleDialogMsgs) { BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a; }
+ if (tracksList) { BOOL a=tracksList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ groupBtn = gen_mlconfig->ReadInt(L"groupbtn", 1);
+ enqueuedef = (gen_mlconfig->ReadInt(L"enqueuedef", 0) == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_CUSTOM, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_pmp"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_CUSTOM, pszTextW);
+ }
+ else
+ {
+ customAllowed = FALSE;
+ }
+
+ playmode = L"viewplaymode";
+ hwndMediaView = hwndDlg;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+
+ if (!lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ header = 1;
+ else
+ header = currentViewedDevice->config->ReadInt(L"header",0);
+
+ HWND list = GetDlgItem(hwndDlg, IDC_LIST_TRACKS);
+ // TODO need to be able to change the order of the tracks items (so cloud is in a more appropriate place)
+ //ListView_SetExtendedListViewStyleEx(list, LVS_EX_HEADERDRAGDROP, LVS_EX_HEADERDRAGDROP);
+ ListView_SetExtendedListViewStyle(list, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP);
+
+ if (currentViewedDevice->config->ReadInt(L"savefilter", 1))
+ {
+ SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, currentViewedDevice->config->ReadString(L"savedfilter", L""));
+ }
+
+ // depending on how this is called, it will either do a video only view or will be re-purposed as a 'simple' view
+ aacontents = new ArtistAlbumLists(currentViewedDevice->dev, currentViewedPlaylist, currentViewedDevice->config,
+ NULL, 0, (lParam ? -1 : (currentViewedDevice->videoView ? 1 : -1)), (lParam == 1));
+
+ if (aacontents)
+ {
+ tracks = aacontents->GetTracksList();
+ }
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = list;
+ skin.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(list), tracks->cloudcol);
+
+ FLICKERFIX ff = {0};
+ ff.mode = FFM_ERASEINPAINT;
+ INT ffcl[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM,
+ IDC_BUTTON_CLEARSEARCH, IDC_BUTTON_CLEARREFINE,
+ IDC_BUTTON_EJECT, IDC_BUTTON_SYNC, IDC_BUTTON_AUTOFILL,
+ IDC_STATUS, IDC_SEARCH_TEXT, IDC_QUICKSEARCH, IDC_REFINE,
+ IDC_REFINE_TEXT,
+ // disabled cloud parts
+ /*IDC_HEADER_DEVICE_ICON, IDC_HEADER_DEVICE_NAME,
+ IDC_HEADER_DEVICE_BAR, IDC_HEADER_DEVICE_SIZE,
+ IDC_HEADER_DEVICE_TRANSFER,*/
+ };
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ for (int i = 0; i < sizeof(ffcl) / sizeof(INT); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn && (i < 3) ? SWBS_SPLITBUTTON : 0);
+ skin.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ if (0 != currentViewedDevice->dev->extraActions(DEVICE_SYNC_UNSUPPORTED,0,0,0))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC), FALSE);
+ }
+ break;
+
+ case WM_USER+1:
+ if (tracks)
+ {
+ if (lParam)
+ {
+ tracks->RemoveTrack((songid_t)wParam);
+ }
+ tracksList->UpdateList();
+ }
+ break;
+
+ case WM_DESTROY:
+ if (aacontents) aacontents->bgQuery_Stop();
+ tracks = NULL;
+ if (aacontents) delete aacontents; aacontents=NULL;
+ break;
+
+ case WM_DROPFILES:
+ return currentViewedDevice->TransferFromDrop((HDROP)wParam);
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom == IDC_LIST_TRACKS)
+ {
+ switch (l->code)
+ {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case VK_DELETE:
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ }
+ break;
+ case 0x45: //E
+ bool noEdit = currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA,0,0,0)!=0;
+ if(!noEdit && GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ C_ItemList * items = getSelectedItems();
+ editInfo(items,currentViewedDevice->dev, CENTER_OVER_ML_VIEW);
+ delete items;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags), 1);
+ }
+ return TRUE;
+
+ case WM_DISPLAYCHANGE:
+ {
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE, 1);
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ pmp_common_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE, 1);
+ return 0;
+ }
+
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_LIST_TRACKS|DCW_SUNKENBORDER,
+ IDC_QUICKSEARCH|DCW_SUNKENBORDER,
+ (!header?IDC_HDELIM2|DCW_DIVIDER:0),
+ };
+ int size = sizeof(tab) / sizeof(tab[0]);
+ // do this to prevent drawing parts when the views are collapsed
+ if (!currentViewedDevice->isCloudDevice) size -= 1;
+ if (wad_DrawChildWindowBorders) wad_DrawChildWindowBorders(hwndDlg,tab,size);
+ }
+ return TRUE;
+
+ case WM_APP + 3: // send by bgthread
+ if (wParam == 0x69)
+ {
+ if (aacontents)
+ {
+ aacontents->bgQuery_Stop();
+ tracks = aacontents->GetTracksList();
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg, true);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE && !noSearchTimer) {
+ KillTimer(hwndDlg,500);
+ SetTimer(hwndDlg,500,250,NULL);
+ }
+ break;
+ case IDC_HEADER_DEVICE_TRANSFER:
+ {
+ if (!IsWindowEnabled(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC)))
+ {
+ MessageBox(hwndDlg, L"Transferring files from this device to another is not currently supported.",
+ L"Cloud Transfer Not Supported", MB_ICONINFORMATION);
+ break;
+ }
+ }
+ case IDC_BUTTON_SYNC:
+ if (!currentViewedDevice->isCloudDevice)
+ currentViewedDevice->Sync();
+ else
+ currentViewedDevice->CloudSync();
+ break;
+ case IDC_SEARCH_TEXT:
+ if (HIWORD(wParam) == STN_DBLCLK)
+ {
+ // TODO decide what to do for the 'all_sources'
+ if (currentViewedDevice->isCloudDevice &&
+ lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ {
+ header = !header;
+ currentViewedDevice->config->WriteInt(L"header",header);
+ LayoutWindows(hwndMediaView, TRUE, 1);
+ UpdateStatus(hwndDlg);
+ ShowWindow(hwndDlg, 0);
+ ShowWindow(hwndDlg, 1);
+ }
+ }
+ break;
+ case IDC_BUTTON_CLEARSEARCH:
+ SetDlgItemText(hwndDlg,IDC_QUICKSEARCH,L"");
+ break;
+ }
+ break; //WM_COMMAND
+
+ case WM_TIMER:
+ switch(wParam) {
+ case 123:
+ {
+ if (aacontents && aacontents->bgThread_Handle && tracksList)
+ {
+ HWND hwndList;
+ hwndList = tracksList->listview.getwnd();
+ if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+ }
+ break;
+
+ case 500:
+ {
+ KillTimer(hwndDlg,500);
+ if (currentViewedDevice && aacontents)
+ {
+ wchar_t buf[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_QUICKSEARCH,buf,sizeof(buf)/sizeof(wchar_t));
+ aacontents->SetSearch(buf, (!!currentViewedDevice->isCloudDevice));
+ if (!aacontents->bgThread_Handle)
+ {
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ return pmp_common_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+static INT_PTR CALLBACK pmp_common_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ hwndMediaView = hwndDlg;
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ if (currentViewedDevice->isCloudDevice)
+ {
+ wchar_t buf[256] = {0};
+ currentViewedDevice->GetDisplayName(buf, 128);
+ SetDlgItemText(hwndDlg, IDC_HEADER_DEVICE_NAME, buf);
+
+ int icon_id = currentViewedDevice->dev->extraActions(0x22, 0, 0, 0);
+ HBITMAP bm = (HBITMAP)LoadImage(GetModuleHandle(L"pmp_cloud.dll"),
+ MAKEINTRESOURCE(icon_id > 0 ? icon_id : 103), IMAGE_BITMAP, 16, 16,
+ LR_SHARED | LR_LOADTRANSPARENT | LR_CREATEDIBSECTION);
+
+ SendDlgItemMessage(hwndDlg, IDC_HEADER_DEVICE_ICON, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bm);
+
+ HWND progress = GetDlgItem(hwndDlg, IDC_HEADER_DEVICE_BAR);
+ if (IsWindow(progress))
+ {
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = progress;
+ skin.skinType = SKINNEDWND_TYPE_PROGRESSBAR;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+
+ tracksList = new SkinnedListView(tracks,IDC_LIST_TRACKS,plugin.hwndLibraryParent, hwndDlg);
+ tracksList->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ if (tracksList->contents && !tracksList->contents->cloud)
+ {
+ tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+
+ pmp_common_UpdateButtonText(hwndDlg, enqueuedef == 1);
+ }
+
+ case WM_DISPLAYCHANGE:
+ {
+ if (currentViewedDevice->isCloudDevice)
+ {
+ int (*wad_getColor)(int idx);
+ *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ ARGB32 fc = (!wad_getColor?wad_getColor(WADLG_WNDBG):RGB(0xFF,0xFF,0xFF)) & 0x00FFFFFF;
+ SetToolbarButtonBitmap(hwndDlg,IDC_HEADER_DEVICE_TRANSFER, MAKEINTRESOURCE(IDR_TRANSFER_SMALL_ICON),fc);
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ if (currentViewedDevice)
+ {
+ SkinBitmap *s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_HEADER_DEVICE_TRANSFER),GWLP_USERDATA);
+ if (s) DeleteSkinBitmap(s);
+
+ if (currentViewedDevice->config->ReadInt(L"savefilter", 1))
+ {
+ wchar_t buf[256] = {0};
+ GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, 256);
+ currentViewedDevice->config->WriteString(L"savedfilter", buf);
+ buf[0] = 0;
+ GetDlgItemTextW(hwndDlg, IDC_REFINE, buf, 256);
+ currentViewedDevice->config->WriteString(L"savedrefinefilter", buf);
+ }
+ }
+
+ WASABI_API_APP->app_removeAccelerators(hwndDlg);
+
+ hwndMediaView = NULL;
+ currentViewedDevice = NULL;
+ if (tracksList)
+ {
+ delete tracksList;
+ tracksList = NULL;
+ }
+ }
+ break;
+
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON || di->CtlType == ODT_STATIC) {
+ if(di->CtlID == IDC_HEADER_DEVICE_TRANSFER)
+ { // draw the toolbar buttons!
+ int (*wad_getColor)(int idx);
+ *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ HBRUSH hbr = CreateSolidBrush((wad_getColor?wad_getColor(WADLG_WNDBG):RGB(0xFF,0xFF,0xFF)));
+ FillRect(di->hDC, &di->rcItem, hbr);
+ DeleteBrush(hbr);
+
+ SkinBitmap* hbm = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,di->CtlID),GWLP_USERDATA);
+ if(hbm && di->rcItem.left != di->rcItem.right && di->rcItem.top != di->rcItem.bottom) {
+ DCCanvas dc(di->hDC);
+ if (di->itemState & ODS_SELECTED) hbm->blitAlpha(&dc,di->rcItem.left+1,di->rcItem.top+1);
+ else hbm->blitAlpha(&dc,di->rcItem.left,di->rcItem.top);
+ dc.Release();
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->code == LVN_KEYDOWN && ((LPNMLVKEYDOWN)lParam)->wVKey == VK_F5)
+ {
+ if (currentViewedDevice->dev->extraActions(DEVICE_REFRESH,0,0,0))
+ {
+ SetTimer(hwndDlg, (artistList ? 501 : 500), 250, NULL);
+ }
+ }
+ if(l->idFrom == IDC_LIST_TRACKS)
+ {
+ switch(l->code) {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case 0x45: //E
+ {
+ bool noEdit = currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA,0,0,0)!=0;
+ if(!noEdit && GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ C_ItemList * items = getSelectedItems();
+ editInfo(items,currentViewedDevice->dev, CENTER_OVER_ML_VIEW);
+ delete items;
+ }
+ }
+ break;
+ }
+ break;
+ case NM_RETURN:
+ case NM_DBLCLK:
+ {
+ C_ItemList * items = getSelectedItems(true);
+ int start=0;
+ int l=tracksList->listview.GetCount();
+
+ int i = 0;
+ for (; i < l; i++)
+ if (tracksList->listview.GetSelected(i))
+ {
+ start = i;
+ break;
+ }
+ if (items->GetSize() && (!!(GetAsyncKeyState(VK_SHIFT)&0x8000) || !gen_mlconfig->ReadInt(playmode,1))) {
+ void* x = items->Get(i);
+ delete items;
+ items = new C_ItemList;
+ items->Add(x);
+ start = 0;
+ }
+ currentViewedDevice->PlayTracks(items, start, (!(GetAsyncKeyState(VK_SHIFT)&0x8000) ? (enqueuedef == 1) : (enqueuedef != 1)), false);
+ delete items;
+ }
+ break;
+
+ case NM_CUSTOMDRAW:
+ {
+ LRESULT result = 0;
+ if (ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)lParam, &result))
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return 1;
+ }
+ }
+ break;
+
+ case LVN_GETDISPINFOW:
+ {
+ NMLVDISPINFOW* pdi = (NMLVDISPINFOW*)lParam;
+ if (aacontents && aacontents->bgThread_Handle)
+ {
+ LVITEMW *pItem = &pdi->item;
+ if (0 == pItem->iItem && (LVIF_TEXT & pItem->mask))
+ {
+ if (0 == pItem->iSubItem)
+ {
+ static char bufpos = 0;
+ static int buflen = 17;
+ static wchar_t buffer[64];//L"Scanning _\0/-\\|";
+ if (!buffer[0])
+ {
+ //WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_PLAIN,buffer,54);
+ StringCchCopyW(buffer,64,L"Scanning");
+ StringCchCatW(buffer,64,L" _");
+ StringCchCatW(buffer+(buflen=lstrlenW(buffer)+1),64,L"/-\\|");
+ buflen+=4;
+ }
+ int pos = buflen - 5;;
+ buffer[pos - 1] = buffer[pos + (bufpos++&3) + 1];
+ pItem->pszText = buffer;
+ SetDlgItemText(hwndDlg, IDC_STATUS, buffer);
+ }
+ else
+ {
+ pItem->pszText = L"";
+ }
+ }
+ return 1;
+ }
+ }
+ break;
+
+ case NM_CLICK:
+ {
+ // prevent the right-click menus appearing when scanning
+ if (aacontents && aacontents->bgThread_Handle) break;
+
+ LPNMITEMACTIVATE lpnmitem = (LPNMITEMACTIVATE)lParam;
+ if (lpnmitem->iItem != -1 && lpnmitem->iSubItem == tracks->cloudcol)
+ {
+ RECT itemRect = {0};
+ if (lpnmitem->iSubItem)
+ ListView_GetSubItemRect(l->hwndFrom, lpnmitem->iItem, lpnmitem->iSubItem, LVIR_BOUNDS, &itemRect);
+ else
+ {
+ ListView_GetItemRect(l->hwndFrom, lpnmitem->iItem, &itemRect, LVIR_BOUNDS);
+ itemRect.right = itemRect.left + ListView_GetColumnWidth(l->hwndFrom, tracks->cloudcol);
+ }
+ MapWindowPoints(l->hwndFrom, HWND_DESKTOP, (POINT*)&itemRect, 2);
+
+ int cloud_devices = 0;
+ int mark = tracksList->listview.GetSelectionMark();
+ if (mark != -1)
+ {
+ // if wanting to do on the selection then use getSelectedItems()
+ //C_ItemList * items = getSelectedItems();
+ // otherwise only work on the selection mark for speed (and like how ml_local does things)
+ C_ItemList * items = new C_ItemList;
+ items->Add((void*)tracks->GetTrack(mark));
+ HMENU cloud_menu = (HMENU)currentViewedDevice->dev->extraActions(DEVICE_GET_CLOUD_SOURCES_MENU, (intptr_t)&cloud_devices, 0, (intptr_t)items);
+ if (cloud_menu)
+ {
+ int r = Menu_TrackSkinnedPopup(cloud_menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,
+ itemRect.right, itemRect.top, hwndDlg, NULL);
+ if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER) { // deals with cloud specific menus
+ int ret = currentViewedDevice->dev->extraActions(DEVICE_DO_CLOUD_SOURCES_MENU, (intptr_t)r, 1, (intptr_t)items);
+ // only send a removal from the view if plug-in says so
+ if (ret) SendMessage(hwndDlg, WM_USER+1, (WPARAM)items->Get(0), ret);
+ }
+ DestroyMenu(cloud_menu);
+ }
+ delete items;
+ }
+ }
+ }
+ break;
+ }
+ }
+ else if(l->idFrom==IDC_LIST_ARTIST || l->idFrom==IDC_LIST_ALBUM || l->idFrom==IDC_LIST_ALBUM2)
+ {
+ switch(l->code) {
+ case NM_CUSTOMDRAW:
+ {
+ LRESULT result = 0;
+ if (ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)lParam, &result))
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return 1;
+ }
+ }
+ break;
+ }
+ }
+ }
+ break; //WM_NOTIFY
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_BUTTON_EJECT:
+ currentViewedDevice->Eject();
+ break;
+
+ case IDC_BUTTON_PLAY:
+ case ID_TRACKSLIST_PLAYSELECTION:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_TRACKSLIST_ENQUEUESELECTION:
+ case IDC_BUTTON_CUSTOM:
+ {
+ if (HIWORD(wParam) == MLBN_DROPDOWN)
+ {
+ pmp_common_PlayEnqueue(hwndDlg, (HWND)lParam, LOWORD(wParam));
+ }
+ else
+ {
+ bool action;
+ if (LOWORD(wParam) == IDC_BUTTON_PLAY || LOWORD(wParam) == ID_TRACKSLIST_PLAYSELECTION)
+ action = (HIWORD(wParam) == 1) ? enqueuedef == 1 : 0;
+ else if (LOWORD(wParam) == IDC_BUTTON_ENQUEUE || LOWORD(wParam) == ID_TRACKSLIST_ENQUEUESELECTION)
+ action = (HIWORD(wParam) == 1) ? (enqueuedef != 1) : 1;
+ else
+ // so custom can work with the menu item part
+ break;
+
+ C_ItemList * selected = getSelectedItems();
+ currentViewedDevice->PlayTracks(selected, 0, action, true);
+ delete selected;
+ }
+ }
+ break;
+
+ default:
+ handleContextMenuResult(LOWORD(wParam));
+ break;
+ }
+ break; //WM_COMMAND
+
+ case WM_CONTEXTMENU:
+ {
+ // prevent the right-click menus appearing when scanning
+ if (aacontents && aacontents->bgThread_Handle) break;
+
+ POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
+ UINT_PTR idFrom = GetDlgCtrlID((HWND)wParam);
+ HWND hwndFrom = (HWND)wParam;
+ HWND hwndFromChild = WindowFromPoint(pt);
+
+ // deal with the column headers first
+ SkinnedListView * list=NULL;
+ int n = 0;
+ if (artistList && hwndFromChild == ListView_GetHeader(artistList->listview.getwnd())) { n=0; list=artistList; }
+ if (albumList && hwndFromChild == ListView_GetHeader(albumList->listview.getwnd())) { n=1; list=albumList; }
+ if (albumList2 && hwndFromChild == ListView_GetHeader(albumList2->listview.getwnd())) { n=2; list=albumList2; }
+ if (list)
+ {
+ HMENU menu = list->GetMenu(true,n,currentViewedDevice->config,m_context_menus);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hwndDlg, NULL);
+ list->ProcessMenuResult(r,true,n,currentViewedDevice->config,hwndDlg);
+ return 0;
+ }
+
+ // and then do the list menus
+ if(idFrom==IDC_LIST_ARTIST || idFrom==IDC_LIST_ALBUM || idFrom==IDC_LIST_ALBUM2)
+ {
+ handleContextMenuResult(showContextMenu(1,hwndFrom,currentViewedDevice->dev,pt));
+ }
+ else if(idFrom == IDC_LIST_TRACKS)
+ {
+ // prevents funkiness if having shown the 'customize columns' menu before
+ // as well as alloing the shift+f10 key to work within the track listview
+ if ((hwndFrom == hwndFromChild) || (pt.x == -1 && pt.y == -1))
+ {
+ int r = showContextMenu((GetDlgItem(hwndDlg, IDC_BUTTON_SORT) ? 2 : 0), hwndFrom, currentViewedDevice->dev, pt);
+ switch(r) {
+ case ID_TRACKSLIST_REMOVEFROMPLAYLIST:
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(ID_TRACKSLIST_REMOVEFROMPLAYLIST,0),0);
+ break;
+ default:
+ handleContextMenuResult(r);
+ break;
+ }
+ }
+ }
+ break;
+ }
+
+ case WM_ML_CHILDIPC:
+ if(lParam == ML_CHILDIPC_GO_TO_SEARCHBAR)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ }
+ else if (lParam == ML_CHILDIPC_REFRESH_SEARCH)
+ {
+ restoreDone = FALSE;
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ }
+ break;
+ }
+ return 0;
+}
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+// disabled cloud parts
+/*#define GROUP_MIN 0x1
+#define GROUP_MAX 0x7
+#define GROUP_HEADER 0x1
+#define GROUP_SEARCH 0x2
+#define GROUP_FILTER 0x3
+#define GROUP_HDELIM 0x4
+#define GROUP_REFINE 0x5
+#define GROUP_TRACKS 0x6
+#define GROUP_STATUS 0x7*/
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x6
+#define GROUP_SEARCH 0x1
+#define GROUP_FILTER 0x2
+#define GROUP_HDELIM 0x3
+#define GROUP_REFINE 0x4
+#define GROUP_TRACKS 0x5
+#define GROUP_STATUS 0x6
+
+void LayoutWindows(HWND hwnd, BOOL fRedraw, int simple)
+{
+ static INT controls[] =
+ {
+ // disabled cloud parts
+ //GROUP_HEADER, IDC_HEADER_DEVICE_TRANSFER, IDC_HEADER_DEVICE_ICON, IDC_HEADER_DEVICE_NAME, IDC_HEADER_DEVICE_BAR, IDC_HEADER_DEVICE_SIZE, IDC_HDELIM2,
+ GROUP_SEARCH, IDC_BUTTON_ARTMODE, IDC_BUTTON_VIEWMODE, IDC_BUTTON_COLUMNS, IDC_SEARCH_TEXT, IDC_BUTTON_CLEARSEARCH, IDC_QUICKSEARCH,
+ GROUP_STATUS, IDC_BUTTON_EJECT, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_BUTTON_SYNC, IDC_BUTTON_AUTOFILL, IDC_BUTTON_SORT, IDC_STATUS,
+ GROUP_HDELIM, IDC_HDELIM,
+ GROUP_REFINE, IDC_REFINE_TEXT, IDC_BUTTON_CLEARREFINE, IDC_REFINE,
+ GROUP_TRACKS, IDC_LIST_TRACKS,
+ GROUP_FILTER, IDC_LIST_ARTIST, IDC_VDELIM, IDC_LIST_ALBUM, IDC_VDELIM2, IDC_LIST_ALBUM2,
+ };
+
+ INT index, divY, divX, divX2, divCY;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn;
+
+ rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.right == rc.left || rc.bottom == rc.top) return;
+ if (rc.right > 4) rc.right -= 4;
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ divX = (adiv1pos * (rc.right- rc.left)) / 100000;
+ divX2 = numFilters == 3 ? ((adiv3pos * (rc.right- rc.left)) / 100000) : rc.right;
+ divY = (((-1 == adiv2pos) ? 50000 : adiv2pos) * (rc.bottom- rc.top)) / 100000;
+ divCY = 0;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(INT); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch(controls[index])
+ {
+ // disabled cloud parts
+ /*case GROUP_HEADER:
+ if (currentViewedDevice->isCloudDevice && simple != 2)
+ {
+ SetRect(&rg, rc.left, rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right + WASABI_API_APP->getScaleX(1),
+ (!header ? rc.top + WASABI_API_APP->getScaleY(18) : rc.top));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ else
+ skipgroup = 1;
+ break;*/
+ case GROUP_SEARCH:
+ if (g_displaysearch && simple != 2)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_CLEARSEARCH);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ else
+ skipgroup = 1;
+ break;
+ case GROUP_STATUS:
+ if (g_displaystatus)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_EJECT);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaystatus;
+ break;
+ case GROUP_HDELIM:
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+ break;
+ case GROUP_REFINE:
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_CLEARREFINE);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ rg.top = divY + divCY;
+ refineHidden = (g_displayrefine) ? ((rg.top + WASABI_API_APP->getScaleY(HIWORD(idealSize))) >= rc.bottom) : TRUE;
+ SetRect(&rg, rc.left, rg.top, rc.right, (refineHidden) ? rg.top : (rg.top + WASABI_API_APP->getScaleY(HIWORD(idealSize))));
+ break;
+ }
+ case GROUP_TRACKS:
+ if (!simple)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_CLEARREFINE);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ (divY + divCY) + ((!refineHidden) ? WASABI_API_APP->getScaleY(HIWORD(idealSize) + 3) : 0),
+ rc.right, rc.bottom);
+ }
+ else
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ case GROUP_FILTER:
+ if (!simple)
+ {
+ if (divX < (rc.left + 15))
+ {
+ divX = rc.left;
+ adiv1_nodraw = 1;
+ }
+ else if (divX > (rc.right - (numFilters == 3 ? 16 * 2 : 24)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_VDELIM), &rw);
+ divX = rc.right - (rw.right - rw.left) - (numFilters == 3 ? 6 : 0) + (numFilters == 2 ? 1 : -1);
+ adiv1_nodraw = 2;
+ }
+ else adiv1_nodraw = 0;
+
+ if (divX2 < (rc.left + 16 * (numFilters == 3 ? 2 : 1)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_VDELIM), &rw);
+ divX2 = rc.left;
+ adiv3_nodraw = 1;
+ }
+ else if (divX2 > (rc.right - 16 * 2))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_VDELIM2), &rw);
+ divX2 = rc.right - ((rw.right - rw.left) - 1) * 2;
+ adiv3_nodraw = 2;
+ }
+ else adiv3_nodraw = 0;
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, divY);
+ }
+ else
+ skipgroup = 1;
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch(pl->id)
+ {
+ // disabled cloud parts
+ /*case IDC_HEADER_DEVICE_TRANSFER:
+ SETLAYOUTPOS(pl, rg.right - 18, rg.top - WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(16), WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (rg.right - rg.left) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(3+16));
+ break;
+ case IDC_HEADER_DEVICE_ICON:
+ SETLAYOUTPOS(pl, rg.left, rg.top - WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(16), WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (rg.right - rg.left + WASABI_API_APP->getScaleX(16)) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ case IDC_HEADER_DEVICE_NAME:
+ {
+ SIZE s = {0};
+ wchar_t buf[128] = {0};
+ HDC dc = GetDC(pl->hwnd);
+ HFONT font = (HFONT)SendMessage(pl->hwnd, WM_GETFONT, 0, 0), oldfont = (HFONT)SelectObject(dc, font);
+ int len = GetWindowText(pl->hwnd, buf, sizeof(buf));
+ GetTextExtentPoint32(dc, buf, len, &s);
+ SelectObject(dc, oldfont);
+ ReleaseDC(pl->hwnd, dc);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(4), rg.top, s.cx + ((s.cx / len)), WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (rg.right - rg.left + WASABI_API_APP->getScaleX(12)) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_HEADER_DEVICE_BAR:
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(4), rg.top + WASABI_API_APP->getScaleY(2),
+ WASABI_API_APP->getScaleX(150), WASABI_API_APP->getScaleY(11));
+ pl->flags |= (!header && (rg.right - rg.left) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(8));
+ break;
+ case IDC_HEADER_DEVICE_SIZE:
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(4), rg.top, rg.right - rg.left, WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (ri.right - ri.left) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ case IDC_HDELIM2:
+ SETLAYOUTPOS(pl, 0, rg.bottom + WASABI_API_APP->getScaleY(1), rg.right + WASABI_API_APP->getScaleX(35), 0);
+ pl->flags |= (!header ? SWP_SHOWWINDOW : SWP_HIDEWINDOW);
+ break;*/
+ case IDC_BUTTON_ARTMODE:
+ case IDC_BUTTON_VIEWMODE:
+ case IDC_BUTTON_COLUMNS:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, (ri.right - ri.left), (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(1));
+ break;
+ case IDC_SEARCH_TEXT:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(6),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_REFINE_TEXT:
+ {
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(2),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_BUTTON_CLEARSEARCH:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_BUTTON_CLEARREFINE:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, rg.bottom - rg.top);
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_QUICKSEARCH:
+ pl->flags |= SWP_SHOWWINDOW;
+ pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_REFINE:
+ pl->flags |= ((rg.right > rg.left) && (rg.top < rg.bottom)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_BUTTON_EJECT:
+ {
+ if (currentViewedDevice->isCloudDevice)
+ {
+ if (currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_REMOVE,0,0,0))
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+ }
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ SETLAYOUTPOS(pl, rg.right - width + WASABI_API_APP->getScaleX(2),
+ rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ width, WASABI_API_APP->getScaleY(HIWORD(idealSize)));
+ pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(3));
+ break;
+ }
+ case IDC_BUTTON_AUTOFILL:
+ if (currentViewedDevice->isCloudDevice)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+ case IDC_BUTTON_SYNC:
+ if (currentViewedDevice->isCloudDevice)
+ {
+ if (!lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+ }
+ case IDC_BUTTON_PLAY:
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_CUSTOM:
+ case IDC_BUTTON_SORT:
+ {
+ if (IDC_BUTTON_CUSTOM != pl->id || customAllowed)
+ {
+ if (groupBtn && (pl->id == IDC_BUTTON_PLAY) && (enqueuedef == 1))
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && (pl->id == IDC_BUTTON_ENQUEUE) && (enqueuedef != 1))
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && (pl->id == IDC_BUTTON_PLAY || pl->id == IDC_BUTTON_ENQUEUE) && customAllowed)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ width, WASABI_API_APP->getScaleY(HIWORD(idealSize)));
+ pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+ case IDC_STATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(18), rg.right - rg.left, WASABI_API_APP->getScaleY(17));
+ if (SWP_SHOWWINDOW & pl->flags) rg.top = (pl->y + pl->cy + WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_HDELIM:
+ divCY = ri.bottom - ri.top;
+ if (divY > (rg.bottom - WASABI_API_APP->getScaleY(70))) { divY = rg.bottom - divCY; m_nodrawtopborders = 2; }
+ else if (divY < (rg.top + WASABI_API_APP->getScaleY(36))) { divY = rg.top; m_nodrawtopborders = 1; }
+ else m_nodrawtopborders = 0;
+ SETLAYOUTPOS(pl, rg.left, divY, rg.right - rg.left + WASABI_API_APP->getScaleX(2), (ri.bottom - ri.top));
+ break;
+ case IDC_LIST_TRACKS:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1),
+ (rg.right - rg.left) + WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+
+ break;
+ case IDC_LIST_ARTIST:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), divX, (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ rg.left += pl->cx;
+ break;
+ case IDC_VDELIM:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (ri.right - ri.left), (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST_ALBUM:
+ if(numFilters == 3)
+ {
+ BOOL hide = ((divX2 - divX - 1) < WASABI_API_APP->getScaleX(15));
+ pl->flags |= hide ? SWP_HIDEWINDOW : SWP_SHOWWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (hide ? 0 : divX2 - divX - WASABI_API_APP->getScaleX(1)),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ }
+ else { SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) + WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1)); }
+ rg.left += pl->cx;
+ break;
+ case IDC_VDELIM2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (numFilters == 2 ? 0 : ri.right - ri.left), (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST_ALBUM2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) + WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for(pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void WINAPI OnDividerMoved(HWND hwnd, INT nPos, LPARAM param)
+{
+ RECT rc;
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ KillTimer(hwndParent,400);
+ SetTimer(hwndParent,400,1000,NULL);
+
+ if (hwndParent)
+ {
+ GetClientRect(hwndParent, &rc);
+ switch((INT)param)
+ {
+ case IDC_VDELIM:
+ {
+ adiv1pos = (nPos * 100000) / (rc.right - rc.left + 10);
+ if(adiv1pos + 500 >= adiv3pos) adiv3pos = adiv1pos + 500;
+ }
+ break;
+ case IDC_HDELIM:
+ adiv2pos = (nPos * 100000) / (rc.bottom - rc.top);
+ break;
+ case IDC_VDELIM2:
+ {
+ adiv3pos = (nPos * 100000) / (rc.right - rc.left + 10);
+ if(adiv3pos - 500 < adiv1pos) adiv1pos = adiv3pos - 500;
+ }
+ break;
+ }
+ LayoutWindows(hwndParent, TRUE, 0);
+ }
+}
+
+static BOOL AttachDivider(HWND hwnd, BOOL fVertical, DIVIDERMOVED callback, LPARAM param)
+{
+ if (!hwnd) return FALSE;
+
+ DIVIDER *pd = (DIVIDER*)calloc(1, sizeof(DIVIDER));
+ if (!pd) return FALSE;
+
+ pd->fUnicode = IsWindowUnicode(hwnd);
+ pd->fnOldProc = (WNDPROC) ((pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc) :
+ SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc));
+ if (!pd->fnOldProc || !SetPropW(hwnd, L"DIVDATA", pd))
+ {
+ free(pd);
+ return FALSE;
+ }
+ pd->fVertical = fVertical;
+ pd->param = param;
+ pd->callback = callback;
+
+ return TRUE;
+}
+
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DIVIDER *pd;
+ pd = GET_DIVIDER(hwnd);
+ if (!pd) return (IsWindowUnicode(hwnd)) ? DefWindowProcW(hwnd, uMsg, wParam, lParam) : DefWindowProcA(hwnd, uMsg, wParam, lParam);
+
+ switch(uMsg)
+ {
+ case WM_DESTROY:
+ RemovePropW(hwnd, L"DIVDATA");
+ (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+ (pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc) : SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc);
+ free(pd);
+ return 0;
+ case WM_LBUTTONDOWN:
+ pd->clickoffs = (pd->fVertical) ? LOWORD(lParam) : HIWORD(lParam);
+ SetCapture(hwnd);
+ break;
+ case WM_LBUTTONUP:
+ ReleaseCapture();
+ break;
+ case WM_SETCURSOR:
+ SetCursor(LoadCursor(NULL, (pd->fVertical) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+ case WM_MOUSEMOVE:
+ {
+ RECT rw;
+ GetWindowRect(hwnd, &rw);
+ GetCursorPos(((LPPOINT)&rw) + 1);
+ (pd->fVertical) ? rw.right -= pd->clickoffs : rw.bottom -= pd->clickoffs;
+
+ if ((pd->fVertical && rw.left != rw.right) || (!pd->fVertical && rw.top != rw.bottom))
+ {
+ MapWindowPoints(HWND_DESKTOP, GetParent(hwnd), ((LPPOINT)&rw) + 1, 1);
+ if (pd->callback) pd->callback(hwnd, (pd->fVertical) ? rw.right : rw.bottom, pd->param);
+ }
+ }
+ break;
+ }
+
+ return (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp b/Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp
new file mode 100644
index 00000000..aa01c0ce
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp
@@ -0,0 +1,1181 @@
+/* almost the same as view_pmp_devices, but only one device*/
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "resource1.h"
+#include "SkinnedListView.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include "./graphics.h"
+#include <strsafe.h>
+#include "../nu/AutoWide.h"
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX, offsetY, customAllowed;
+static DeviceView *device;
+static SkinnedListView * listTransfers=NULL;
+std::map<DeviceView *, bool> device_update_map;
+
+typedef struct TransferView
+{
+ HFONT font;
+ SIZE unitSize;
+ HRGN updateRegion;
+ POINT updateOffset;
+} TransferView;
+
+#define TRANSFERVIEW(_hwnd) ((TransferView*)GetViewData(_hwnd))
+#define TRANSFERVIEW_RET_VOID(_self, _hwnd) { (_self) = TRANSFERVIEW((_hwnd)); if (NULL == (_self)) return; }
+#define TRANSFERVIEW_RET_VAL(_self, _hwnd, _error) { (_self) = TRANSFERVIEW((_hwnd)); if (NULL == (_self)) return (_error); }
+
+#define TRANSFERVIEW_DLU_TO_HORZ_PX(_self, _dlu) MulDiv((_dlu), (_self)->unitSize.cx, 4)
+#define TRANSFERVIEW_DLU_TO_VERT_PX(_self, _dlu) MulDiv((_dlu), (_self)->unitSize.cy, 8)
+
+void handleContextMenuResult(int r, C_ItemList * items=NULL, DeviceView * dev=NULL);
+int showContextMenu(int context,HWND hwndDlg, Device * dev, POINT pt);
+
+class TransferItemShadowShort
+{
+public:
+ CopyInst * c;
+ wchar_t * status, * type, * track, * sourceDevice, * destDevice, * lastChanged, * sourceFile, * clientType;
+ bool changed;
+ TransferItemShadowShort(CopyInst * c)
+ {
+ changed = false;
+ this->c = c;
+ status = _wcsdup(c->statusCaption);
+ type = _wcsdup(c->typeCaption);
+ track = _wcsdup(c->trackCaption);
+ lastChanged = _wcsdup(c->lastChanged);
+ sourceDevice = _wcsdup(c->sourceDevice);
+ destDevice = _wcsdup(c->destDevice);
+ sourceFile = _wcsdup(c->sourceFile);
+ clientType = AutoWideDup(c->dev->GetConnection());
+ }
+ ~TransferItemShadowShort()
+ {
+ if (status) free(status);
+ if (type) free(type);
+ if (track) free(track);
+ if (lastChanged) free(lastChanged);
+ if (sourceDevice) free(sourceDevice);
+ if (destDevice) free(destDevice);
+ if (sourceFile) free(sourceFile);
+ if (clientType) free(clientType);
+ }
+ bool Equals(TransferItemShadowShort * a)
+ {
+ if (!a) return false;
+ return (c == a->c) && !wcscmp(track,a->track) && !wcscmp(status,a->status) && !wcscmp(type,a->type);
+ }
+};
+
+LinkedQueue *getTransferQueue(DeviceView *deviceView)
+{
+ if (deviceView)
+ {
+ return (deviceView->isCloudDevice ? &cloudTransferQueue : &deviceView->transferQueue);
+ }
+ else if (device)
+ {
+ return (device->isCloudDevice ? &cloudTransferQueue : &device->transferQueue);
+ }
+ return NULL;
+}
+
+LinkedQueue *getFinishedTransferQueue(DeviceView *deviceView)
+{
+ if (deviceView)
+ {
+ return (deviceView->isCloudDevice ? &cloudFinishedTransfers : &deviceView->finishedTransfers);
+ }
+ else if (device)
+ {
+ return (device->isCloudDevice ? &cloudFinishedTransfers : &device->finishedTransfers);
+ }
+ return NULL;
+}
+
+int getTransferProgress(DeviceView *deviceView)
+{
+ if(deviceView)
+ {
+ return (deviceView->isCloudDevice ? cloudTransferProgress : deviceView->currentTransferProgress);
+ }
+ else if(device)
+ {
+ return (device->isCloudDevice ? cloudTransferProgress : device->currentTransferProgress);
+ }
+ return 0;
+}
+
+// TODO hook up to a config (would need to work after a reset to be 100% safe...?)
+int sharedQueue = 1;
+static C_ItemList *getTransferListShadow()
+{
+ C_ItemList * list = new C_ItemList;
+ if (!sharedQueue)
+ {
+ LinkedQueue * txQueue = getTransferQueue();
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int j = 0; j < txQueue->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ LinkedQueue * finishedTX = getFinishedTransferQueue();
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ for (int j = 0; j < finishedTX->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+ }
+ else
+ {
+ // TODO should probably review this when we're more intelligent on multi-handling
+ // we do the cloud transfer queue specifically so as not to duplicate by devices
+ LinkedQueue * txQueue = &cloudTransferQueue;
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int j = 0; j < txQueue->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ LinkedQueue * finishedTX = &cloudFinishedTransfers;
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ for (int j = 0; j < finishedTX->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if (!d || d->isCloudDevice) continue;
+
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int j = 0; j < txQueue->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ LinkedQueue * finishedTX = getFinishedTransferQueue(d);
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ for (int j = 0; j < finishedTX->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+ }
+ }
+ return list;
+}
+
+class TransferContents : public ListContents
+{
+public:
+ TransferContents()
+ {
+ oldSize = 0;
+ listShadow = 0;
+ InitializeCriticalSection(&cs);
+ }
+
+ void Init()
+ {
+ lock();
+
+ if (!listShadow)
+ listShadow = getTransferListShadow();
+
+ unlock();
+ }
+
+ virtual ~TransferContents()
+ {
+ DeleteCriticalSection(&cs);
+ delete listShadow;
+ }
+
+ virtual int GetNumColumns()
+ {
+ // TODO check the number of columns are ok, etc
+ return 7;//(device->isCloudDevice ? 7 : 3);
+ }
+
+ virtual int GetNumRows()
+ {
+ return (listShadow ? listShadow->GetSize() : 0);
+ }
+
+ virtual wchar_t * GetColumnTitle(int num)
+ {
+ // TODO need to clean this up as needed
+ switch (num + 1)
+ {
+ case 0: return WASABI_API_LNGSTRINGW(IDS_TYPE);
+ case 1: return WASABI_API_LNGSTRINGW(IDS_TRACK);
+ case 2: return WASABI_API_LNGSTRINGW(IDS_STATUS);
+ case 3: return WASABI_API_LNGSTRINGW(IDS_LAST_CHANGED);
+ case 4: return WASABI_API_LNGSTRINGW(IDS_SOURCE);
+ case 5: return WASABI_API_LNGSTRINGW(IDS_DESTINATION);
+ case 6: return WASABI_API_LNGSTRINGW(IDS_SOURCE_FILE);
+ case 7: return WASABI_API_LNGSTRINGW(IDS_CLIENT_TYPE);
+ }
+ return L"";
+ }
+
+ virtual int GetColumnWidth(int num)
+ {
+ switch (num)
+ {
+ case 0: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col0_width" : L"cloud_col0_width"), 300);
+ case 1: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col1_width" : L"cloud_col1_width"), 150);
+ case 2: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col2_width" : L"cloud_col2_width"), 100);
+ case 3: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col3_width" : L"cloud_col3_width"), 100);
+ case 4: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col4_width" : L"cloud_col4_width"), 100);
+ case 5: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col5_width" : L"cloud_col5_width"), 100);
+ case 6: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col6_width" : L"cloud_col6_width"), 300);
+ case 7: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col7_width" : L"cloud_col7_width"), 100);
+ default: return 0;
+ }
+ }
+
+ virtual void ColumnResize(int col, int newWidth)
+ {
+ if (NULL != global_config &&
+ col >= 0 &&
+ col < GetNumColumns())
+ {
+ wchar_t buffer[64] = {0};
+
+ if (FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), (!device->isCloudDevice ? L"transfers_col%d_width" : L"cloud_col%d_width"), col)))
+ return;
+
+ global_config->WriteInt(buffer, newWidth);
+ }
+ }
+
+ void lock()
+ {
+ EnterCriticalSection(&cs);
+ }
+
+ void unlock()
+ {
+ LeaveCriticalSection(&cs);
+ }
+
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)
+ {
+ if (NULL == buf)
+ return;
+
+ buf[0] = L'\0';
+
+ lock();
+
+ if (row < listShadow->GetSize())
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(row);
+ if (NULL != t)
+ {
+ // TODO need to clean this up as needed
+ switch (col + 1)
+ {
+ case 0: /*StringCchCopy(buf, buflen, t->type);*/ break;
+ case 1: StringCchCopy(buf, buflen, t->track); break;
+ case 2: StringCchCopy(buf, buflen, t->status); break;
+ case 3: StringCchCopy(buf, buflen, t->lastChanged); break;
+ case 4: StringCchCopy(buf, buflen, t->sourceDevice); break;
+ case 5: StringCchCopy(buf, buflen, t->destDevice); break;
+ case 6: StringCchCopy(buf, buflen, t->sourceFile); break;
+ case 7: StringCchCopy(buf, buflen, t->clientType); break;
+ }
+ }
+ }
+ unlock();
+ }
+
+ void PushPopItem(CopyInst *c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ t->changed=true;
+ listShadow->Del(i);
+ listShadow->Add(t);
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd, LVM_REDRAWITEMS, i, size);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+
+ void ItemUpdate(CopyInst * c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ TransferItemShadowShort * n = new TransferItemShadowShort(c);
+ n->changed=true;
+ listShadow->Set(i,n);
+ delete t;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+
+ void FullUpdate()
+ {
+ C_ItemList * newListShadow = getTransferListShadow();
+ if (newListShadow)
+ {
+ int newSize = newListShadow->GetSize();
+ lock();
+ oldSize = listShadow->GetSize();
+ for (int i = 0; i < newSize; i++)
+ {
+ TransferItemShadowShort * newt = (TransferItemShadowShort *)newListShadow->Get(i);
+ TransferItemShadowShort * oldt = i < oldSize ? (TransferItemShadowShort *)listShadow->Get(i) : NULL;
+ newt->changed = !newt->Equals(oldt);
+ }
+
+ C_ItemList * oldListShadow = listShadow;
+ listShadow = newListShadow;
+ for (int i = 0; i < oldListShadow->GetSize(); i++) delete(TransferItemShadowShort *)oldListShadow->Get(i);
+ delete oldListShadow;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ if (newSize != oldSize) PostMessage(hwnd,LVM_SETITEMCOUNT,newSize, 0);
+ for (int i=0; i<newSize; i++)
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(i);
+ if (t->changed) PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ }
+ unlock();
+ }
+ }
+
+ virtual songid_t GetTrack(int pos) { return 0; }
+
+private:
+ CRITICAL_SECTION cs;
+ C_ItemList * listShadow;
+ int oldSize;
+};
+
+static TransferContents transferListContents;
+
+static void updateStatus(HWND hwnd)
+{
+ HWND statusWindow = GetDlgItem(hwnd, IDC_STATUS);
+ if (NULL == statusWindow)
+ return;
+
+ int txProgress = getTransferProgress(device);
+ LinkedQueue * txQueue = getTransferQueue(device);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ if (size > 0)
+ {
+ wchar_t buffer[256] = {0}, format[256] = {0};
+ int pcnum, time, pc, total;
+
+ pcnum = (size * 100) - txProgress;
+ total = size * 100;
+ total += 100 * (finishedTX ? finishedTX->GetSize() : 0);
+
+ time = (int)(device->transferRate * (((double)pcnum)/100.0));
+ pc = ((total-pcnum)*100)/total;
+
+ WASABI_API_LNGSTRINGW_BUF((time > 0 ? IDS_TRANFERS_PERCENT_REMAINING : IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME), format, ARRAYSIZE(format));
+ if (SUCCEEDED(StringCchPrintf(buffer, ARRAYSIZE(buffer), format, size, pc, time/60, time%60)))
+ {
+ if (0 == SendMessage(statusWindow, WM_GETTEXT, (WPARAM)ARRAYSIZE(format), (LPARAM)format) ||
+ CSTR_EQUAL != CompareString(LOCALE_USER_DEFAULT, 0, format, -1, buffer, -1))
+ {
+ SendMessage(statusWindow, WM_SETTEXT, 0, (LPARAM)buffer);
+ }
+ }
+ }
+ else
+ {
+ int length = (int)SendMessage(statusWindow, WM_GETTEXTLENGTH, 0, 0L);
+ if (0 != length)
+ SendMessage(statusWindow, WM_SETTEXT, 0, 0L);
+ }
+}
+
+void TransfersListUpdateItem(CopyInst * item, DeviceView *view)
+{
+ if (view == device)
+ transferListContents.ItemUpdate(item);
+}
+
+void TransfersListPushPopItem(CopyInst * item, DeviceView *view)
+{
+ if (view == device)
+ transferListContents.PushPopItem(item);
+}
+
+static bool AddSelectedItems(C_ItemList *items, W_ListView *listview, LinkedQueue *transfer_queue, int &row, DeviceView *&dev)
+{
+ transfer_queue->lock();
+ int l = transfer_queue->GetSize();
+ for (int j=0; j<l; j++)
+ {
+ if (listview->GetSelected(row++))
+ {
+ CopyInst * c = (CopyInst *)transfer_queue->Get(j);
+ if (c->songid)
+ {
+ if (!dev && c->dev)
+ dev = c->dev;
+ if (dev)
+ {
+ if (c->dev != dev)
+ {
+ transfer_queue->unlock();
+ return false;
+ }
+ else
+ items->Add((void*)c->songid);
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+ return true;
+}
+
+static void RemoveSelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, int &row, bool finished_queue)
+{
+ transfer_queue->lock();
+ int j = transfer_queue->GetSize();
+ while (j-- > 0)
+ {
+ if (listview->GetSelected(--row))
+ {
+ if (j == 0 && !finished_queue)
+ {
+ CopyInst * d = (CopyInst *)transfer_queue->Get(j);
+ if (d && (d->status == STATUS_WAITING || d->status == STATUS_CANCELLED) && device->transferContext.IsPaused())
+ {
+ transfer_queue->Del(j);
+ d->Cancelled();
+ delete d;
+ } // otherwise don't bother
+ }
+ else
+ {
+ CopyInst * d = (CopyInst*)transfer_queue->Del(j);
+ if (d)
+ {
+ if ((d->status == STATUS_WAITING || d->status == STATUS_CANCELLED) && !finished_queue)
+ d->Cancelled();
+ delete d;
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+}
+
+static void CancelSelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, int &row)
+{
+ transfer_queue->lock();
+ int j = transfer_queue->GetSize();
+ int sel = listview->GetSelectedCount();
+
+ for (int i = 0, q = 0; i <= j; i++)
+ {
+ if (listview->GetSelected(i) || !sel)
+ {
+ CopyInst * d = (CopyInst *)transfer_queue->Get(q);
+ if (d && d->status == STATUS_WAITING)
+ {
+ transfer_queue->Del(q);
+ d->Cancelled();
+ delete d;
+ }
+ else if (d && d->status == STATUS_TRANSFERRING)
+ {
+ d->Cancelled();
+ }
+ else
+ {
+ q++;
+ }
+ }
+ }
+
+ transfer_queue->unlock();
+}
+
+static void RetrySelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, LinkedQueue *finished_transfer_queue, int &row)
+{
+ transfer_queue->lock();
+ finished_transfer_queue->lock();
+ int j = finished_transfer_queue->GetSize();
+ int sel = listview->GetSelectedCount();
+
+ LinkedQueue retryTransferQueue;
+ int i = 0;
+ for (int q = 0; i <= j; i++)
+ {
+ if (listview->GetSelected(i) || !sel)
+ {
+ CopyInst * d = (CopyInst *)finished_transfer_queue->Get(q);
+ if (d && (d->status == STATUS_DONE || d->status == STATUS_CANCELLED || d->status == STATUS_ERROR))
+ {
+ // due to STATUS_DONE being applied in most cases, have to look at the
+ // status message and use as the basis on how to proceed with the item
+ if (lstrcmpi(d->statusCaption, WASABI_API_LNGSTRINGW(IDS_UPLOADED)))
+ {
+ retryTransferQueue.Offer(d);
+ finished_transfer_queue->Del(q);
+ }
+ else
+ {
+ q++;
+ }
+ }
+ else
+ {
+ q++;
+ }
+ }
+ }
+
+ i = 0;
+ for (; i <= retryTransferQueue.GetSize(); i++)
+ {
+ CopyInst * d = (CopyInst *)retryTransferQueue.Get(i);
+ if (d)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, d->statusCaption, sizeof(d->statusCaption)/sizeof(wchar_t));
+
+ SYSTEMTIME system_time;
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, d->lastChanged, sizeof(d->lastChanged)/sizeof(wchar_t));
+
+ d->dev->AddTrackToTransferQueue(d);
+ }
+ }
+
+ transfer_queue->unlock();
+ finished_transfer_queue->unlock();
+}
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+/*
+IDC_BUTTON_PAUSETRANSFERS,
+IDC_BUTTONCANCELSELECTED,
+IDC_BUTTON_CLEARFINISHED,
+IDC_BUTTON_REMOVESELECTED,
+IDC_BUTTON_RETRYSELECTED,
+IDC_STATUS,
+IDC_LIST_TRANSFERS,
+*/
+
+static void TransferView_UpdateLayout(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_BUTTON_PAUSETRANSFERS, IDC_BUTTON_CLEARFINISHED, IDC_BUTTON_REMOVESELECTED, IDC_BUTTON_RETRYSELECTED, IDC_STATUS,
+ GROUP_MAIN, IDC_LIST_TRANSFERS
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.bottom == rc.top || rc.right == rc.left) return;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_PAUSETRANSFERS);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ }
+ break;
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_BUTTON_PAUSETRANSFERS:
+ case IDC_BUTTON_CLEARFINISHED:
+ case IDC_BUTTON_REMOVESELECTED:
+ case IDC_BUTTON_RETRYSELECTED:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ width, WASABI_API_APP->getScaleY(HIWORD(idealSize)));
+ pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_STATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > WASABI_API_APP->getScaleX(16)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_LIST_TRANSFERS:
+ SETLAYOUTPOS(pl, rg.left, rg.top + 1, (rg.right - rg.left) - WASABI_API_APP->getScaleX(2), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && !fUpdateAll && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+
+ pl++;
+ }
+ else if (!fUpdateAll && (fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ if (pc->rgn) DeleteObject(pc->rgn);
+ }
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void
+TransferView_UpdateFont(HWND hwnd, BOOL redraw)
+{
+ TransferView *self;
+ HWND elementWindow;
+ HDWP hdwp;
+
+ const int buttonList[] =
+ {
+ IDC_BUTTON_PAUSETRANSFERS,
+ IDC_BUTTONCANCELSELECTED,
+ IDC_BUTTON_CLEARFINISHED,
+ IDC_BUTTON_REMOVESELECTED,
+ IDC_BUTTON_RETRYSELECTED
+ };
+
+ TRANSFERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE == Graphics_GetWindowBaseUnits(hwnd, &self->unitSize.cx, &self->unitSize.cy))
+ {
+ self->unitSize.cx = 6;
+ self->unitSize.cy = 13;
+ }
+
+ elementWindow = GetDlgItem(hwnd, IDC_LIST_TRANSFERS);
+ if (NULL != elementWindow)
+ {
+ elementWindow = (HWND)SendMessage(elementWindow, LVM_GETHEADER, 0, 0L);
+ if (NULL != elementWindow)
+ MLSkinnedHeader_SetHeight(elementWindow, -1);
+ }
+
+ hdwp = BeginDeferWindowPos(ARRAYSIZE(buttonList) + 1);
+ if (NULL != hdwp)
+ {
+ LRESULT idealSize;
+ SIZE buttonSize;
+
+ elementWindow = GetDlgItem(hwnd, IDC_STATUS);
+ if (NULL != elementWindow)
+ {
+ hdwp = DeferWindowPos(hdwp, elementWindow, NULL, 0, 0, 100, self->unitSize.cy,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ }
+
+ for(size_t index = 0; index < ARRAYSIZE(buttonList) && NULL != hdwp; index++)
+ {
+ elementWindow = GetDlgItem(hwnd, buttonList[index]);
+ if (NULL == elementWindow)
+ continue;
+
+ if (IDC_BUTTON_PAUSETRANSFERS == buttonList[index])
+ {
+ wchar_t buffer[128] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_RESUME, buffer, ARRAYSIZE(buffer));
+ idealSize = MLSkinnedButton_GetIdealSize(elementWindow, buffer);
+ buttonSize.cx = LOWORD(idealSize);
+ buttonSize.cy = HIWORD(idealSize);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PAUSE, buffer, ARRAYSIZE(buffer));
+ idealSize = MLSkinnedButton_GetIdealSize(elementWindow, buffer);
+
+ if (buttonSize.cx < LOWORD(idealSize))
+ buttonSize.cx = LOWORD(idealSize);
+
+ if (buttonSize.cy < HIWORD(idealSize))
+ buttonSize.cy = HIWORD(idealSize);
+ }
+ else
+ {
+ idealSize = MLSkinnedButton_GetIdealSize(elementWindow, NULL);
+ buttonSize.cx = LOWORD(idealSize);
+ buttonSize.cy = HIWORD(idealSize);
+ }
+
+ hdwp = DeferWindowPos(hdwp, elementWindow, NULL, 0, 0, buttonSize.cx, buttonSize.cy,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ }
+
+ if (NULL != hdwp)
+ EndDeferWindowPos(hdwp);
+ }
+
+ TransferView_UpdateLayout(hwnd, redraw);
+}
+
+static int
+TransferView_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ HWND controlWindow;
+
+ const int skinList[] =
+ {
+ IDC_BUTTON_PAUSETRANSFERS,
+ IDC_BUTTONCANCELSELECTED,
+ IDC_BUTTON_CLEARFINISHED,
+ IDC_BUTTON_REMOVESELECTED,
+ IDC_BUTTON_RETRYSELECTED,
+ IDC_STATUS,
+ };
+
+ TransferView *self = (TransferView*)calloc(1, sizeof(TransferView));
+ if (NULL != self)
+ {
+ if (FALSE == SetViewData(hwnd, self))
+ {
+ free(self);
+ self = NULL;
+ }
+ }
+
+ if (NULL == self)
+ {
+ DestroyWindow(hwnd);
+ return 0;
+ }
+
+ device = (DeviceView *)param;
+
+ transferListContents.Init();
+ transferListContents.lock();
+
+ transferListContents.FullUpdate();
+
+ listTransfers = new SkinnedListView(&transferListContents,IDC_LIST_TRANSFERS,plugin.hwndLibraryParent, hwnd);
+ listTransfers->DialogProc(hwnd,WM_INITDIALOG, (WPARAM)focusWindow, param);
+
+ transferListContents.unlock();
+ SetDlgItemText(hwnd,IDC_BUTTON_PAUSETRANSFERS,
+ WASABI_API_LNGSTRINGW((device->transferContext.IsPaused()?IDS_RESUME:IDS_PAUSE)));
+
+ MLSkinWindow2(plugin.hwndLibraryParent, hwnd, SKINNEDWND_TYPE_DIALOG,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ for(size_t index = 0; index < ARRAYSIZE(skinList); index++)
+ {
+ controlWindow = GetDlgItem(hwnd, skinList[index]);
+ if (NULL != controlWindow)
+ {
+ MLSkinWindow2(plugin.hwndLibraryParent, controlWindow,
+ (skinList[index] != IDC_STATUS ? SKINNEDWND_TYPE_BUTTON : SKINNEDWND_TYPE_STATIC),
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+ }
+ }
+
+ TransferView_UpdateFont(hwnd, FALSE);
+
+ SetTimer(hwnd,1,500,NULL);
+ updateStatus(hwnd);
+
+ return 0;
+}
+
+static void
+TransferView_OnDestroy(HWND hwnd)
+{
+ TransferView *self;
+
+ self = (TransferView *)RemoveViewData(hwnd);
+ if (NULL == self)
+ return;
+
+ KillTimer(hwnd, 1);
+ device = 0;
+
+ SkinnedListView * lt = listTransfers;
+ if (NULL != lt)
+ {
+ transferListContents.lock();
+ listTransfers=NULL;
+ transferListContents.unlock();
+ delete lt;
+ }
+
+ free(self);
+}
+
+static void
+TransferView_OnWindowPosChanged(HWND hwnd, WINDOWPOS *windowPos)
+{
+ if (NULL == windowPos)
+ return;
+
+ if (SWP_NOSIZE != ((SWP_NOSIZE | SWP_FRAMECHANGED) & windowPos->flags))
+ {
+ TransferView_UpdateLayout(hwnd, 0 == (SWP_NOREDRAW & windowPos->flags));
+ }
+}
+
+static void
+TransferView_OnDisplayChanged(HWND hwnd, INT bpp, INT dpi_x, INT dpi_y)
+{
+ UpdateWindow(hwnd);
+ TransferView_UpdateFont(hwnd, TRUE);
+}
+
+static void
+TransferView_OnSetFont(HWND hwnd, HFONT font, BOOL redraw)
+{
+ TransferView *self;
+ TRANSFERVIEW_RET_VOID(self, hwnd);
+
+ self->font = font;
+}
+
+static HFONT
+TransferView_OnGetFont(HWND hwnd)
+{
+ TransferView *self;
+ TRANSFERVIEW_RET_VAL(self, hwnd, NULL);
+
+ return self->font;
+}
+
+static void
+TransferView_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS regionOffset)
+{
+ TransferView *self;
+ TRANSFERVIEW_RET_VOID(self, hwnd);
+
+ self->updateRegion = updateRegion;
+ self->updateOffset.x = regionOffset.x;
+ self->updateOffset.y = regionOffset.y;
+}
+
+INT_PTR CALLBACK pmp_queue_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if (NULL != listTransfers)
+ {
+ INT_PTR processed = listTransfers->DialogProc(hwndDlg,uMsg,wParam,lParam);
+ if (0 != processed)
+ return processed;
+ }
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG: return TransferView_OnInitDialog(hwndDlg, (HWND)wParam, lParam);
+ case WM_DESTROY: TransferView_OnDestroy(hwndDlg); break;
+ case WM_WINDOWPOSCHANGED: TransferView_OnWindowPosChanged(hwndDlg, (WINDOWPOS*)lParam); return TRUE;
+ case WM_DISPLAYCHANGE: TransferView_OnDisplayChanged(hwndDlg, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); return TRUE;
+ case WM_GETFONT: DIALOG_RESULT(hwndDlg, TransferView_OnGetFont(hwndDlg));
+ case WM_SETFONT: TransferView_OnSetFont(hwndDlg, (HFONT)wParam, LOWORD(lParam)); return TRUE;
+ case WM_TIMER:
+ if (wParam == 1)
+ {
+ updateStatus(hwndDlg);
+ /*if (device_update_map[device] == true)
+ {
+ device_update_map[device] = false;*/
+ transferListContents.FullUpdate();
+ /*}*/
+ }
+ break;
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_LIST_TRANSFERS)
+ {
+ switch (l->code)
+ {
+ case NM_RETURN: // enter!
+ //case NM_RCLICK: // right click!
+ case LVN_KEYDOWN:
+ {
+ int row = 0;
+ C_ItemList items;
+ if (!AddSelectedItems(&items, &listTransfers->listview, getTransferQueue(device), row, device))
+ return 0;
+ if (!AddSelectedItems(&items, &listTransfers->listview, getFinishedTransferQueue(device), row, device))
+ return 0;
+
+ if (items.GetSize())
+ {
+ // TODO need to check the handling of this...
+ /*if (l->code == NM_RCLICK)
+ {
+ LPNMITEMACTIVATE lva=(LPNMITEMACTIVATE)lParam;
+ handleContextMenuResult(showContextMenu(7,l->hwndFrom,device->dev,lva->ptAction),&items,device);
+ }
+ else*/ if (l->code == NM_RETURN)
+ {
+ handleContextMenuResult((!GetAsyncKeyState(VK_SHIFT)?ID_TRACKSLIST_PLAYSELECTION:ID_TRACKSLIST_ENQUEUESELECTION),&items,device);
+ }
+ else if (l->code == LVN_KEYDOWN)
+ {
+ switch (((LPNMLVKEYDOWN)lParam)->wVKey)
+ {
+ case VK_DELETE:
+ {
+ if (!(GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_DELETE,&items,device);
+ }
+ }
+ break;
+ case 0x45: //E
+ if ((GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_EDITSELECTEDITEMS,&items,device);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON_CLEARFINISHED:
+ {
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ int j=finishedTX->GetSize();
+ while (j-- > 0) delete(CopyInst*)finishedTX->Del(j);
+ finishedTX->unlock();
+ }
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_REMOVESELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ RemoveSelectedItems(device, &listTransfers->listview, getTransferQueue(device), row, false);
+ RemoveSelectedItems(device, &listTransfers->listview, getFinishedTransferQueue(device), row, true);
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_PAUSETRANSFERS:
+ {
+ if (false != device->transferContext.IsPaused())
+ device->transferContext.Resume();
+ else
+ device->transferContext.Pause();
+
+ SetDlgItemText(hwndDlg,IDC_BUTTON_PAUSETRANSFERS,WASABI_API_LNGSTRINGW((device->transferContext.IsPaused()?IDS_RESUME:IDS_PAUSE)));
+ TransferView_UpdateLayout(hwndDlg, TRUE);
+ }
+ break;
+ case IDC_BUTTONCANCELSELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ CancelSelectedItems(device, &listTransfers->listview, &cloudTransferQueue, row);
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_RETRYSELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ RetrySelectedItems(device, &listTransfers->listview, &cloudTransferQueue, &cloudFinishedTransfers, row);
+ transferListContents.FullUpdate();
+ }
+ break;
+ }
+ break;
+
+ // gen_ml flickerless drawing
+ case WM_USER + 0x200: DIALOG_RESULT(hwndDlg, 1);
+ case WM_USER + 0x201: TransferView_OnSetUpdateRegion(hwndDlg, (HRGN)lParam, MAKEPOINTS(wParam)); return TRUE;
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/Process.cpp b/Src/Plugins/Library/ml_rg/Process.cpp
new file mode 100644
index 00000000..21cc97e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Process.cpp
@@ -0,0 +1,53 @@
+#include "main.h"
+#include "Process.h"
+
+int ProcessReplayGain::Open(int _mode)
+{
+ mode=_mode;
+ if (mode != RG_INDIVIDUAL_TRACKS
+ && mode != RG_ALBUM)
+ return RG_MODE_NOT_SUPPORTED;
+ context=CreateRG();
+ if (!context)
+ return RG_FAILURE;
+
+ StartRG(context);
+ return RG_SUCCESS;
+}
+
+int ProcessReplayGain::ProcessTrack(const wchar_t *filename)
+{
+ int killSwitch=0;
+ RGWorkFile workFile(filename);
+
+ CalculateRG(context, workFile.filename, workFile.track_gain, workFile.track_peak, 0, &killSwitch, albumPeak);
+ queue.push_back(workFile);
+
+ return RG_SUCCESS;
+}
+
+int ProcessReplayGain::Write()
+{
+ if (mode == RG_ALBUM)
+ {
+ wchar_t album_gain[64]=L"", album_peak[64]=L"";
+ CalculateAlbumRG(context, album_gain, album_peak, albumPeak);
+ CopyAlbumData(queue, album_gain, album_peak);
+ }
+ WriteAlbum(queue);
+
+ return RG_SUCCESS;
+}
+
+void ProcessReplayGain::Close()
+{
+ DestroyRG(context);
+}
+
+#define CBCLASS ProcessReplayGain
+START_DISPATCH;
+CB(OBJ_REPLAYGAIN_OPEN, Open)
+CB(OBJ_REPLAYGAIN_PROCESSTRACK, ProcessTrack)
+CB(OBJ_REPLAYGAIN_WRITE, Write)
+VCB(OBJ_REPLAYGAIN_CLOSE,Close)
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_rg/Process.h b/Src/Plugins/Library/ml_rg/Process.h
new file mode 100644
index 00000000..e3f1d195
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Process.h
@@ -0,0 +1,25 @@
+#ifndef NULLSOFT_ML_RG_PROCESS_H
+#define NULLSOFT_ML_RG_PROCESS_H
+
+#include "obj_replaygain.h"
+
+//this class is meant for use as a service
+
+class ProcessReplayGain : public obj_replaygain
+{
+public:
+ ProcessReplayGain() : context(0), albumPeak(0), mode(RG_INDIVIDUAL_TRACKS) {}
+ int Open(int mode);
+ int ProcessTrack(const wchar_t *filename);
+ int Write();
+ void Close();
+
+protected:
+ RECVS_DISPATCH;
+ void *context;
+ int mode;
+ float albumPeak;
+ WorkQueue::RGWorkQueue queue;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/Progress.cpp b/Src/Plugins/Library/ml_rg/Progress.cpp
new file mode 100644
index 00000000..18c26031
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Progress.cpp
@@ -0,0 +1,261 @@
+#include "main.h"
+#include "resource.h"
+#include"api__ml_rg.h"
+#include <strsafe.h>
+
+struct Progress
+{
+ Progress()
+ {
+ processedFiles = 0;
+ currentBytes = 0;
+ totalBytes = 0;
+ activeHWND = 0;
+ threadHandle = 0;
+ openDialogs = 0;
+ done = false;
+ killSwitch = 0;
+ }
+
+ size_t processedFiles;
+ uint64_t currentBytes;
+ uint32_t totalBytes;
+ WorkQueue activeQueue;
+ HWND activeHWND;
+ HANDLE threadHandle;
+ size_t openDialogs;
+ bool done;
+ int killSwitch;
+};
+
+DWORD WINAPI ThreadProc(void *param)
+{
+ Progress *progress = (Progress *)param;
+ ProgressCallback callback(progress->activeHWND);
+ progress->activeQueue.Calculate(&callback, &progress->killSwitch);
+ PostMessage(progress->activeHWND, WM_USER + 2, 0, 0);
+
+ return 0;
+}
+
+void getViewport(RECT *r, HWND wnd, int full, RECT *sr)
+{
+ POINT *p = NULL;
+
+ if (p || sr || wnd)
+ {
+ HMONITOR hm = NULL;
+
+ if (sr)
+ hm = MonitorFromRect(sr, MONITOR_DEFAULTTONEAREST);
+ else if (wnd)
+ hm = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
+ else if (p)
+ hm = MonitorFromPoint(*p, MONITOR_DEFAULTTONEAREST);
+
+ if (hm)
+ {
+ MONITORINFOEXW mi;
+ memset(&mi, 0, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+
+ if (GetMonitorInfoW(hm, &mi))
+ {
+ if (!full)
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+
+ return ;
+ }
+ }
+ }
+ if (full)
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics(SM_CXSCREEN);
+ r->bottom = GetSystemMetrics(SM_CYSCREEN);
+ }
+ else
+ {
+ SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0);
+ }
+}
+
+BOOL windowOffScreen(HWND hwnd, POINT pt)
+{
+ RECT r = {0}, wnd = {0}, sr = {0};
+ GetWindowRect(hwnd, &wnd);
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + (wnd.right - wnd.left);
+ sr.bottom = sr.top + (wnd.bottom - wnd.top);
+ getViewport(&r, hwnd, 0, &sr);
+ return !PtInRect(&r, pt);
+}
+
+void SaveWindowPos(HWND hwnd)
+{
+ RECT rect = {0};
+ GetWindowRect(hwnd, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_rg", "prog_x", buf, iniFile);
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_rg", "prog_y", buf, iniFile);
+}
+
+INT_PTR WINAPI ReplayGainProgressProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ Progress *progress = (Progress *)lParam;
+ progress->killSwitch = 0;
+ progress->done = false;
+ progress->openDialogs = 0;
+ progress->processedFiles = 0;
+ progress->activeHWND = hwndDlg;
+
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64, WASABI_API_LNGSTRINGW(IDS_1_OF_X_FILES), progress->activeQueue.totalFiles);
+ SetDlgItemTextW(hwndDlg, IDC_PROGRESS_FILES, dummy);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)progress);
+ DWORD threadId;
+ progress->threadHandle = CreateThread(NULL, 0, ThreadProc, (void *)progress, CREATE_SUSPENDED, &threadId);
+ SetThreadPriority(progress->threadHandle, THREAD_PRIORITY_IDLE);
+ ResumeThread(progress->threadHandle);
+
+ POINT pt = {(LONG)GetPrivateProfileIntA("ml_rg", "prog_x", -1, iniFile),
+ (LONG)GetPrivateProfileIntA("ml_rg", "prog_y", -1, iniFile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOW);
+ }
+ break;
+ case WM_DESTROY:
+ {
+ SaveWindowPos(hwndDlg);
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ CloseHandle(progress->threadHandle);
+ progress->activeHWND = 0;
+ delete progress;
+ }
+ break;
+ case WM_USER: // file finished
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->processedFiles++;
+
+ if (progress->processedFiles + 1 > progress->activeQueue.totalFiles)
+ SetDlgItemTextW(hwndDlg, IDC_PROGRESS_FILES, WASABI_API_LNGSTRINGW(IDS_FINISHED));
+ else
+ {
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64,
+ WASABI_API_LNGSTRINGW(IDS_X_OF_X_FILES),
+ progress->processedFiles + 1, progress->activeQueue.totalFiles);
+ SetDlgItemTextW(hwndDlg, IDC_PROGRESS_FILES, dummy);
+ }
+ }
+ break;
+ case WM_USER + 1: // album done
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)lParam;
+ SaveWindowPos(hwndDlg);
+ if (config_ask && config_ask_each_album)
+ {
+ progress->openDialogs++;
+ DoResults(*queue);
+ progress->openDialogs--;
+ if (!progress->openDialogs && progress->done)
+ DestroyWindow(hwndDlg);
+ }
+ else if (config_ask == 0)
+ {
+ WriteAlbum(*queue);
+ }
+ }
+ break;
+ case WM_USER + 2: // all tracks done
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ ShowWindow(hwndDlg, SW_HIDE);
+ SaveWindowPos(hwndDlg);
+ if (config_ask && config_ask_each_album == 0)
+ {
+ DoResults(progress->activeQueue);
+ }
+ progress->killSwitch = 1;
+ WaitForSingleObject(progress->threadHandle, INFINITE);
+ progress->done = true;
+ if (!progress->openDialogs)
+ DestroyWindow(hwndDlg);
+ }
+ break;
+ case WM_USER + 3: // total bytes of current file
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->currentBytes = 0;
+ progress->totalBytes = (uint32_t)lParam;
+ if (progress->totalBytes == 0)
+ {
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, WASABI_API_LNGSTRINGW(IDS_PROCESSING));
+ }
+ else
+ {
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64, L"%u%%", (progress->currentBytes * 100) / progress->totalBytes);
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, dummy);
+ }
+ }
+ break;
+ case WM_USER + 4: // more bytes read
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->currentBytes += lParam;
+ if (progress->totalBytes == 0)
+ {
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, WASABI_API_LNGSTRINGW(IDS_PROCESSING));
+ }
+ else
+ {
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64, L"%u%%", (progress->currentBytes * 100) / progress->totalBytes);
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, dummy);
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCANCEL:
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->killSwitch = 1;
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+void DoProgress(WorkQueue &workQueue)
+{
+ Progress *progress = new Progress;
+ progress->activeQueue = workQueue; // this is a huge slow copy, but I don't care at the moment
+ WASABI_API_CREATEDIALOGPARAMW(IDD_PROGRESS, GetDialogBoxParent(), ReplayGainProgressProc, (LPARAM)progress);
+}
+
+HWND GetDialogBoxParent()
+{
+ HWND parent = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT);
+ if (!parent || parent == (HWND)1)
+ return plugin.hwndWinampParent;
+ return parent;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/RGFactory.cpp b/Src/Plugins/Library/ml_rg/RGFactory.cpp
new file mode 100644
index 00000000..a70daea2
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/RGFactory.cpp
@@ -0,0 +1,67 @@
+#include "main.h"
+#include "RGFactory.h"
+#include "Process.h"
+static const char serviceName[] = "Replay Gain Processor";
+
+FOURCC RGFactory::GetServiceType()
+{
+ return WaSvc::OBJECT;
+}
+
+const char *RGFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID RGFactory::GetGUID()
+{
+ return RGGUID;
+}
+
+void *RGFactory::GetInterface(int global_lock)
+{
+ obj_replaygain *ifc=new ProcessReplayGain;
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return ifc;
+}
+
+int RGFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int RGFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ obj_replaygain *api_ = static_cast<obj_replaygain *>(ifc);
+ ProcessReplayGain *svc_ = static_cast<ProcessReplayGain *>(api_);
+ delete svc_;
+ return 1;
+}
+
+const char *RGFactory::GetTestString()
+{
+ return 0;
+}
+
+int RGFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS RGFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_rg/RGFactory.h b/Src/Plugins/Library/ml_rg/RGFactory.h
new file mode 100644
index 00000000..5deea3aa
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/RGFactory.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_RG_FACTORY_H
+#define NULLSOFT_RG_FACTORY_H
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class RGFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/Results.cpp b/Src/Plugins/Library/ml_rg/Results.cpp
new file mode 100644
index 00000000..592bd47b
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Results.cpp
@@ -0,0 +1,172 @@
+#include "main.h"
+#include "resource.h"
+#include "../nu/listview.h"
+#include "api__ml_rg.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+// this isn't nice but it localises the values for display as they're saved in "C" locale
+enum { GAIN_MODE=0, PEAK_MODE };
+wchar_t* LocaliseNumericText(wchar_t str[64], int mode){
+static wchar_t tmp[64];
+double value;
+ tmp[0]=0;
+ value = _wtof_l(str,WASABI_API_LNG->Get_C_NumericLocale());
+ StringCchPrintfW(tmp,64,(mode==GAIN_MODE?L"%-+.2f dB":L"%-.9f"),value);
+ return tmp;
+}
+
+static void AddQueueToListView(W_ListView *listView, WorkQueue::RGWorkQueue *queue)
+{
+ int i=listView->GetCount();
+ for(WorkQueue::RGWorkQueue::iterator itr = queue->begin(); itr!= queue->end(); itr++)
+ {
+ listView->InsertItem(i, PathFindFileNameW(itr->filename), (int)&*itr);
+ listView->SetItemText(i, 1, LocaliseNumericText(itr->track_gain,GAIN_MODE));
+ listView->SetItemText(i, 2, LocaliseNumericText(itr->track_peak,PEAK_MODE));
+ listView->SetItemText(i, 3, LocaliseNumericText(itr->album_gain,GAIN_MODE));
+ listView->SetItemText(i, 4, LocaliseNumericText(itr->album_peak,PEAK_MODE));
+ i++;
+ }
+}
+
+INT_PTR WINAPI ReplayGainDialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ W_ListView listView(GetDlgItem(hwndDlg, IDC_RGLIST));
+ listView.setwnd(GetDlgItem(hwndDlg, IDC_RGLIST));
+
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_FILENAME), 200);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_PEAK), 80);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_PEAK), 80);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)lParam;
+
+ AddQueueToListView(&listView, queue);
+
+ POINT pt = {(LONG)GetPrivateProfileIntA("ml_rg", "res_x", -1, iniFile),
+ (LONG)GetPrivateProfileIntA("ml_rg", "res_y", -1, iniFile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteAlbum(*queue);
+ }
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_rg", "res_x", buf, iniFile);
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_rg", "res_y", buf, iniFile);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case IDC_SAVETRACK:
+ {
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteTracks(*queue);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+INT_PTR WINAPI ReplayGainDialogProcAll(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ W_ListView listView(GetDlgItem(hwndDlg, IDC_RGLIST));
+ listView.setwnd(GetDlgItem(hwndDlg, IDC_RGLIST));
+
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_FILENAME), 200);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_PEAK), 80);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_PEAK), 80);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ WorkQueue *queue = (WorkQueue *)lParam;
+
+ AddQueueToListView(&listView, &queue->unclassified);
+ for (WorkQueue::AlbumMap::iterator mapItr=queue->albums.begin();mapItr!=queue->albums.end();mapItr++)
+ {
+ AddQueueToListView(&listView, &mapItr->second);
+ }
+
+ POINT pt = {(LONG)GetPrivateProfileIntA("ml_rg", "res_x", -1, iniFile),
+ (LONG)GetPrivateProfileIntA("ml_rg", "res_y", -1, iniFile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ WorkQueue *queue = (WorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteAlbum(queue->unclassified);
+ for (WorkQueue::AlbumMap::iterator mapItr=queue->albums.begin();mapItr!=queue->albums.end();mapItr++)
+ {
+ WriteAlbum(mapItr->second);
+ }
+ }
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_rg", "res_x", buf, iniFile);
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_rg", "res_y", buf, iniFile);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case IDC_SAVETRACK:
+ {
+ WorkQueue *queue = (WorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteTracks(queue->unclassified);
+ for (WorkQueue::AlbumMap::iterator mapItr=queue->albums.begin();mapItr!=queue->albums.end();mapItr++)
+ {
+ WriteTracks(mapItr->second);
+ }
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+void DoResults(WorkQueue::RGWorkQueue &queue)
+{
+ if (!queue.empty())
+ WASABI_API_DIALOGBOXPARAM(IDD_RESULTS, GetDialogBoxParent(), ReplayGainDialogProc, (LPARAM)&queue);
+}
+
+void DoResults(WorkQueue &queue)
+{
+ WASABI_API_DIALOGBOXPARAM(IDD_RESULTS, GetDialogBoxParent(), ReplayGainDialogProcAll, (LPARAM)&queue);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/api__ml_rg.h b/Src/Plugins/Library/ml_rg/api__ml_rg.h
new file mode 100644
index 00000000..f46f1047
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/api__ml_rg.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_ML_RG_API_H
+#define NULLSOFT_ML_RG_API_H
+
+#include "../Winamp/api_decodefile.h"
+extern api_decodefile *decodeFile;
+#define AGAVE_API_DECODE decodeFile
+
+#include "api/application/api_application.h"
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include <api/service/waservicefactory.h>
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/config.cpp b/Src/Plugins/Library/ml_rg/config.cpp
new file mode 100644
index 00000000..3c5ea665
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/config.cpp
@@ -0,0 +1,52 @@
+#include "main.h"
+#include "resource.h"
+
+int config_ask=1;
+int config_ask_each_album=1;
+int config_ignore_gained_album=0;
+
+void DoButtons(HWND hwndDlg)
+{
+ config_ask = IsDlgButtonChecked(hwndDlg, IDC_ASK);
+ config_ask_each_album = IsDlgButtonChecked(hwndDlg, IDC_ALBUM);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ALBUM), config_ask);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ALL), config_ask);
+}
+
+INT_PTR WINAPI RGConfig(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ CheckDlgButton(hwndDlg, IDC_ASK, config_ask);
+ if (config_ask_each_album)
+ CheckDlgButton(hwndDlg, IDC_ALBUM, BST_CHECKED);
+ else
+ CheckDlgButton(hwndDlg, IDC_ALL, BST_CHECKED);
+ DoButtons(hwndDlg);
+ break;
+ case WM_DESTROY:
+ {
+ config_ask = IsDlgButtonChecked(hwndDlg, IDC_ASK);
+ config_ask_each_album = IsDlgButtonChecked(hwndDlg, IDC_ALBUM);
+ WritePrivateProfileStringA("ml_rg", "config_ask", config_ask ? "1" : "0", iniFile);
+ WritePrivateProfileStringA("ml_rg", "config_ask_each_album", config_ask_each_album ? "1" : "0", iniFile);
+ break;
+ }
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDCANCEL:
+ case IDOK:
+ EndDialog(hwndDlg, 0);
+ break;
+ case IDC_ASK:
+ case IDC_ALBUM:
+ case IDC_ALL:
+ DoButtons(hwndDlg);
+ break;
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/main.h b/Src/Plugins/Library/ml_rg/main.h
new file mode 100644
index 00000000..461b4ae7
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/main.h
@@ -0,0 +1,146 @@
+#ifndef NULLSOFT_ML_RG_MAIN_H
+#define NULLSOFT_ML_RG_MAIN_H
+
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include <windowsx.h>
+#include "../winamp/wa_ipc.h"
+#include "../../General/gen_ml/ml.h"
+#include "resource.h"
+#include <string>
+#include <vector>
+#include <map>
+
+extern winampMediaLibraryPlugin plugin;
+extern char *iniFile;
+
+LRESULT SetFileInfo(const wchar_t *filename, const wchar_t *metadata, const wchar_t *data);
+int GetFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len);
+void WriteFileInfo();
+void TagUpdated(const wchar_t *filename);
+
+struct RGWorkFile
+{
+ RGWorkFile(const wchar_t *_filename=0)
+ {
+ if (_filename)
+ lstrcpynW(filename, _filename, MAX_PATH);
+ else
+ filename[0]=0;
+ track_gain[0]=0;
+ track_peak[0]=0;
+ album_gain[0]=0;
+ album_peak[0]=0;
+ }
+
+ wchar_t filename[MAX_PATH];
+ wchar_t track_gain[64];
+ wchar_t track_peak[64];
+ wchar_t album_gain[64];
+ wchar_t album_peak[64];
+};
+
+class ProgressCallback;
+
+class WorkQueue
+{
+public:
+ WorkQueue() : totalFiles(0){}
+ void Add(const wchar_t *filename);
+ void Calculate(ProgressCallback *callback, int *killSwitch);
+ typedef std::vector<RGWorkFile> RGWorkQueue;
+ typedef std::map<std::wstring, RGWorkQueue> AlbumMap;
+ AlbumMap albums;
+ RGWorkQueue unclassified;
+ size_t totalFiles;
+};
+
+constexpr auto TIME_SPAN_MS = 10;
+
+class ProgressCallback
+{
+public:
+ ProgressCallback(HWND _c)
+ : callback(_c),
+ totalReceived(0)
+ {
+ ticks = GetTickCount64();
+ }
+
+ void InformSize(size_t bytes)
+ {
+ if (!PostMessage(callback, WM_USER + 3, 0, bytes))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+ }
+ /// <summary>
+ /// This function may fire an "ERROR_NOT_ENOUGH_QUOTA" 1816 (0x718) error when the limit is hit!
+ /// Put some throttle here, post message every 10 ms, not each time we receive a progress.
+ /// </summary>
+ /// <param name="bytes"></param>
+ void Progress(size_t bytes)
+ {
+ totalReceived += bytes;
+ ULONGLONG currentTicks = GetTickCount64();
+ if (currentTicks - ticks >= TIME_SPAN_MS)
+ {
+ ticks = currentTicks;
+ if (!PostMessage(callback, WM_USER + 4, 0, totalReceived))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+
+ totalReceived = 0;
+ }
+ }
+ void FileFinished()
+ {
+ // notify remaining bytes
+ if (totalReceived)
+ {
+ PostMessage(callback, WM_USER + 4, 0, totalReceived);
+ totalReceived = 0;
+ }
+
+ if(!PostMessage(callback, WM_USER, 0, 0))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+ }
+ void AlbumFinished(WorkQueue::RGWorkQueue *album)
+ {
+ if(!PostMessage(callback, WM_USER + 1, 0, (LPARAM)album))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+ }
+
+ HWND callback;
+ ULONGLONG ticks;
+ size_t totalReceived;
+};
+
+void CopyAlbumData(WorkQueue::RGWorkQueue &workQueue, const wchar_t *album_gain, const wchar_t *album_peak);
+void WriteAlbum(WorkQueue::RGWorkQueue &workQueue);
+void WriteTracks(WorkQueue::RGWorkQueue &workQueue);
+void DoResults(WorkQueue::RGWorkQueue &queue);
+void DoResults(WorkQueue &queue);
+void DoProgress(WorkQueue &workQueue);
+
+void DestroyRG(void *context);
+void *CreateRG();
+void CalculateAlbumRG(void *context, wchar_t album_gain[64], wchar_t album_peak[64], float &albumPeak);
+void StartRG(void *context);
+void CalculateRG(void *context, const wchar_t *filename, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak);
+
+HWND GetDialogBoxParent();
+BOOL windowOffScreen(HWND hwnd, POINT pt);
+
+extern int config_ask, config_ask_each_album, config_ignore_gained_album;
+INT_PTR WINAPI RGConfig(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/metadata.cpp b/Src/Plugins/Library/ml_rg/metadata.cpp
new file mode 100644
index 00000000..63d4107f
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/metadata.cpp
@@ -0,0 +1,28 @@
+#include "main.h"
+
+LRESULT SetFileInfo(const wchar_t *filename, const wchar_t *metadata, const wchar_t *data)
+{
+ extendedFileInfoStructW efis = {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? (size_t)lstrlenW(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+void WriteFileInfo()
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+}
+
+int GetFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len)
+{
+ extendedFileInfoStructW efis = { filename, metadata, dest, (size_t)len, };
+ return (int)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE); //will return 1 if wa2 supports this IPC call
+}
+
+void TagUpdated(const wchar_t *filename)
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)filename, IPC_FILE_TAG_MAY_HAVE_UPDATEDW);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.cpp b/Src/Plugins/Library/ml_rg/ml_rg.cpp
new file mode 100644
index 00000000..6d6dcb34
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.cpp
@@ -0,0 +1,409 @@
+#include "Main.h"
+#include "../nu/AutoWideFn.h"
+#include "api__ml_rg.h"
+//#include <api/service/waservicefactory.h>
+#include "RGFactory.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+#include <strsafe.h>
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+int uninstalling = 0;
+RGFactory rgFactory;
+static DWORD ml_rg_config_ipc;
+static BOOL ml_rg_open_prefs;
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+static int Init();
+static void Quit();
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+#define PLUG_VER L"1.29"
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_rg.dll)",
+ Init,
+ Quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+api_decodefile *AGAVE_API_DECODE = 0;
+api_application *WASABI_API_APP = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_stats *AGAVE_API_STATS = 0;
+
+char *iniFile = 0;
+int Init()
+{
+ waServiceFactory *sf = 0;
+
+
+ ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceBuild( AGAVE_API_DECODE, decodeFileGUID );
+ ServiceBuild( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceBuild( AGAVE_API_STATS, AnonymousStatsGUID );
+
+ plugin.service->service_register( &rgFactory );
+
+ // loader so that we can get the localisation service api for use
+ ServiceBuild( WASABI_API_LNG, languageApiGUID );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlReplayGainLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_REPLAY_GAIN_ANALYZER ), PLUG_VER );
+
+ plugin.description = (char *)szDescription;
+
+ ml_rg_config_ipc = (DWORD)SendMessageA( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "ml_rg_config", IPC_REGISTER_WINAMP_IPCMESSAGE );
+
+ iniFile = (char *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIFILE );
+ config_ask = GetPrivateProfileIntA( "ml_rg", "config_ask", config_ask, iniFile );
+ config_ask_each_album = GetPrivateProfileIntA( "ml_rg", "config_ask_each_album", config_ask_each_album, iniFile );
+
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ ServiceRelease(decodeFile, decodeFileGUID);
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID);
+ plugin.service->service_deregister(&rgFactory);
+}
+
+void WorkQueue::Add(const wchar_t *filename)
+{
+ wchar_t album[512] = L"";
+
+ // if the user wants to ignore tracks already scanned, check and skip appropriately
+ if (config_ignore_gained_album && GetFileInfo(filename, L"replaygain_album_gain", album, 256) && album[0] != 0)
+ return;
+
+
+ GetFileInfo(filename, L"album", album, 256);
+ if (album[0])
+ {
+ RGWorkFile workFile;
+ lstrcpynW(workFile.filename, filename, MAX_PATH);
+ albums[album].push_back(workFile);
+ }
+ else
+ {
+ RGWorkFile workFile;
+ lstrcpynW(workFile.filename, filename, MAX_PATH);
+ unclassified.push_back(workFile);
+ }
+ totalFiles++;
+}
+
+void WriteAlbum(WorkQueue::RGWorkQueue &workQueue)
+{
+ for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
+ {
+ if (itr->track_gain[0])
+ SetFileInfo(itr->filename, L"replaygain_track_gain", itr->track_gain);
+
+ if (itr->track_peak[0])
+ SetFileInfo(itr->filename, L"replaygain_track_peak", itr->track_peak);
+
+ if (itr->album_gain[0])
+ SetFileInfo(itr->filename, L"replaygain_album_gain", itr->album_gain);
+
+ if (itr->album_peak[0])
+ SetFileInfo(itr->filename, L"replaygain_album_peak", itr->album_peak);
+
+ WriteFileInfo();
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::REPLAYGAIN_COUNT);
+
+ TagUpdated(itr->filename);
+ }
+}
+
+void WriteTracks(WorkQueue::RGWorkQueue &workQueue)
+{
+ for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
+ {
+ if (itr->track_gain[0])
+ SetFileInfo(itr->filename, L"replaygain_track_gain", itr->track_gain);
+
+ if (itr->track_peak[0])
+ SetFileInfo(itr->filename, L"replaygain_track_peak", itr->track_peak);
+
+ WriteFileInfo();
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::REPLAYGAIN_COUNT);
+
+ TagUpdated(itr->filename);
+ }
+}
+
+void CopyAlbumData(WorkQueue::RGWorkQueue &workQueue, const wchar_t *album_gain, const wchar_t *album_peak)
+{
+ for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
+ {
+ if (itr->track_gain && itr->track_gain[0]) // if there's no track gain, it's because there was an error!
+ {
+ if (album_gain && album_gain[0])
+ lstrcpynW(itr->album_gain, album_gain, 64);
+
+ if (album_peak && album_peak[0])
+ lstrcpynW(itr->album_peak, album_peak, 64);
+ }
+ }
+}
+
+void WorkQueue::Calculate(ProgressCallback *callback, int *killSwitch)
+{
+ void *context = CreateRG();
+ StartRG(context);
+
+ float albumPeak = 0;
+ for (RGWorkQueue::iterator itr = unclassified.begin();itr != unclassified.end();itr++)
+ {
+ if (*killSwitch) {DestroyRG(context); return ;}
+ CalculateRG(context, itr->filename, itr->track_gain, itr->track_peak, callback, killSwitch, albumPeak);
+ callback->FileFinished();
+ }
+ if (*killSwitch) {DestroyRG(context); return ;}
+ callback->AlbumFinished(&unclassified);
+
+ for (AlbumMap::iterator mapItr = albums.begin();mapItr != albums.end();mapItr++)
+ {
+ albumPeak = 0;
+ StartRG(context);
+ for (RGWorkQueue::iterator itr = mapItr->second.begin();itr != mapItr->second.end();itr++)
+ {
+ if (*killSwitch) {DestroyRG(context); return ;}
+ CalculateRG(context, itr->filename, itr->track_gain, itr->track_peak, callback, killSwitch, albumPeak);
+ callback->FileFinished();
+ }
+ wchar_t album_gain[64] = L"", album_peak[64] = L"";
+ CalculateAlbumRG(context, album_gain, album_peak, albumPeak);
+ CopyAlbumData(mapItr->second, album_gain, album_peak);
+ if (*killSwitch) {DestroyRG(context); return ;}
+ callback->AlbumFinished(&(mapItr->second));
+ }
+ DestroyRG(context);
+}
+
+class WorkQueuePlaylistLoader : public ifc_playlistloadercallback
+{
+public:
+ WorkQueuePlaylistLoader(WorkQueue *_queue);
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info);
+
+protected:
+ WorkQueue *queue;
+ RECVS_DISPATCH;
+};
+
+WorkQueuePlaylistLoader::WorkQueuePlaylistLoader(WorkQueue *_queue)
+{
+ queue = _queue;
+}
+
+void WorkQueuePlaylistLoader::OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+{
+ queue->Add(filename);
+}
+
+#define CBCLASS WorkQueuePlaylistLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_ITEMRECORDLIST ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_STREAMNAMES ||
+ param1 == ML_TYPE_FILENAMESW || param1 == ML_TYPE_STREAMNAMESW ||
+ (AGAVE_API_PLAYLISTMANAGER && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)))
+ {
+ wchar_t description[512] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_CALCULATE_REPLAY_GAIN, description, 512);
+
+ mlAddToSendToStructW s = {0};
+ s.context = param2;
+ s.desc = description;
+ s.user32 = (INT_PTR)PluginMessageProc;
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&s, ML_IPC_ADDTOSENDTOW);
+ }
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)PluginMessageProc) return 0;
+
+ INT_PTR type = param1;
+ INT_PTR data = param2;
+
+ if (data)
+ {
+ WorkQueue workQueue;
+ if (type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordList *p = (itemRecordList*)data;
+ for (int x = 0; x < p->Size; x ++)
+ {
+ workQueue.Add(AutoWideFn(p->Items[x].filename));
+ }
+
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *p = (itemRecordListW*)data;
+ for (int x = 0; x < p->Size; x ++)
+ {
+ workQueue.Add(p->Items[x].filename);
+ }
+
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES)
+ {
+ char *p = (char*)data;
+ while (p && *p)
+ {
+ workQueue.Add(AutoWideFn(p));
+ p += strlen(p) + 1;
+ }
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW)
+ {
+ wchar_t *p = (wchar_t*)data;
+ while (p && *p)
+ {
+ workQueue.Add(p);
+ p += wcslen(p) + 1;
+ }
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist *playlist = (mlPlaylist *)param2;
+ WorkQueuePlaylistLoader loader(&workQueue);
+ AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)param2;
+ WorkQueuePlaylistLoader loader(&workQueue);
+ while (playlists && *playlists)
+ {
+ mlPlaylist *playlist = *playlists;
+ AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
+ playlists++;
+ }
+ DoProgress(workQueue);
+ return 1;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_CONFIG)
+ {
+ ml_rg_open_prefs = TRUE;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 42, IPC_OPENPREFSTOPAGE);
+ ml_rg_open_prefs = FALSE;
+ return 1;
+ }
+ // this will be sent if we've opened the playback->replay gain prefs page
+ // so that we can enable the embedded controls and return the RGConfig proc
+ else if (message_type == ml_rg_config_ipc)
+ {
+ // sanity check by winamp.exe to make sure that we're valid
+ if(!param1)
+ {
+ return 1;
+ }
+ // return the config dialog proceedure
+ else if(param1 == 1)
+ {
+ return (INT_PTR)RGConfig;
+ }
+ // queried when the playback prefs page is opened to see if we [ml_rg] caused it
+ else if(param1 == 2)
+ {
+ if(ml_rg_open_prefs)
+ {
+ return TRUE;
+ }
+ }
+ }
+ return 0;
+}
+
+extern "C" {
+ __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ uninstalling = 1;
+
+ // prompt to remove our settings with default as no (just incase)
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ (wchar_t*)plugin.description,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ WritePrivateProfileStringA("ml_rg", 0, 0, iniFile);
+ }
+
+ // also attempt to remove the ReplayGainAnalysis.dll so everything is kept cleaner
+ wchar_t path[MAX_PATH] = {0};
+ PathCombineW(path, (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETSHAREDDLLDIRECTORYW), L"ReplayGainAnalysis.dll");
+
+ // if we get a handle then try to lower the handle count so we can delete
+ HINSTANCE rgLib = GetModuleHandleW(path);
+ if(rgLib) {
+ FreeLibrary(rgLib);
+ }
+ DeleteFileW(path);
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.rc b/Src/Plugins/Library/ml_rg/ml_rg.rc
new file mode 100644
index 00000000..fe1ce9c8
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.rc
@@ -0,0 +1,152 @@
+// 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_RESULTS DIALOGEX 0, 0, 356, 170
+STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT | WS_EX_APPWINDOW
+CAPTION "Replay Gain Results"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_RGLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,4,7,348,141
+ DEFPUSHBUTTON "Save as Album",IDOK,116,153,76,13
+ PUSHBUTTON "Save Track data",IDC_SAVETRACK,196,153,76,13
+ PUSHBUTTON "Cancel",IDCANCEL,276,153,76,13
+END
+
+IDD_PROGRESS DIALOGEX 0, 0, 186, 63
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "Calculating Replay Gain"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "File Progress:",IDC_STATIC,7,7,44,8
+ LTEXT "Calculating...",IDC_FILE_PROGRESS,63,7,116,8
+ LTEXT "Total Progress:",IDC_STATIC,7,23,50,8
+ LTEXT "Calculating...",IDC_PROGRESS_FILES,63,23,116,8
+ PUSHBUTTON "Abort",IDCANCEL,129,41,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_RESULTS, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 352
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 166
+ END
+
+ IDD_PROGRESS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 56
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_REPLAY_GAIN_ANALYZER "Nullsoft Replay Gain Analyzer v%s"
+ 65535 "{5F633543-148D-48cc-B683-DA82F592CF28}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CALCULATE_REPLAY_GAIN "Calculate Replay Gain"
+ IDS_1_OF_X_FILES "1 of %u files"
+ IDS_FINISHED "Finished."
+ IDS_X_OF_X_FILES "%u of %u files"
+ IDS_PROCESSING "Processing..."
+ IDS_TOO_MANY_CHANNELS "Too many channels, can't deal with this yet"
+ IDS_REPLAYGAIN "ReplayGain"
+ IDS_NOT_ABLE_TO_OPEN_RG_DLL "Not able to open ReplayGainAnalysis.dll"
+ IDS_COL_FILENAME "Filename"
+ IDS_COL_TRACK_GAIN "Track Gain"
+ IDS_COL_TRACK_PEAK "Track Peak"
+ IDS_COL_ALBUM_GAIN "Album Gain"
+ IDS_COL_ALBUM_PEAK "Album Peak"
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings for this plug-in?"
+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_rg/ml_rg.sln b/Src/Plugins/Library/ml_rg/ml_rg.sln
new file mode 100644
index 00000000..5beb288b
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.sln
@@ -0,0 +1,57 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29424.173
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_rg", "ml_rg.vcxproj", "{F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}"
+ ProjectSection(ProjectDependencies) = postProject
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D} = {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReplayGainAnalysis", "..\ReplayGainAnalysis\ReplayGainAnalysis.vcxproj", "{8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}"
+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
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|Win32.Build.0 = Debug|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|x64.ActiveCfg = Debug|x64
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|x64.Build.0 = Debug|x64
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|Win32.ActiveCfg = Release|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|Win32.Build.0 = Release|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|x64.ActiveCfg = Release|x64
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|x64.Build.0 = Release|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|Win32.Build.0 = Debug|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|x64.ActiveCfg = Debug|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|x64.Build.0 = Debug|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|Win32.ActiveCfg = Release|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|Win32.Build.0 = Release|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|x64.ActiveCfg = Release|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|x64.Build.0 = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.vcxproj b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj
new file mode 100644
index 00000000..309e9194
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj
@@ -0,0 +1,310 @@
+<?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>{F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}</ProjectGuid>
+ <RootNamespace>ml_rg</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)'=='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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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)'=='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|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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4311;4302;%(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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <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>
+ <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>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4311;4302;%(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>
+ </Link>
+ <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>
+ <ClInclude Include="api__ml_rg.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="obj_replaygain.h" />
+ <ClInclude Include="Process.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="RGFactory.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="metadata.cpp" />
+ <ClCompile Include="ml_rg.cpp" />
+ <ClCompile Include="Process.cpp" />
+ <ClCompile Include="Progress.cpp" />
+ <ClCompile Include="replaygain.cpp" />
+ <ClCompile Include="Results.cpp" />
+ <ClCompile Include="RGFactory.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_rg.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_rg/ml_rg.vcxproj.filters b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters
new file mode 100644
index 00000000..c0ec5bcf
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="metadata.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Process.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Progress.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="replaygain.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Results.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RGFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_rg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_rg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="obj_replaygain.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Process.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RGFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{20ba2df7-10e3-42e6-896b-4c0a795e542f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{018c7bf8-fe6c-4705-aa4d-c641ffd71994}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{916716df-cad8-4fa2-9a6f-2203f1fbade7}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_rg.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/obj_replaygain.h b/Src/Plugins/Library/ml_rg/obj_replaygain.h
new file mode 100644
index 00000000..a594c246
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/obj_replaygain.h
@@ -0,0 +1,62 @@
+#ifndef NULLSOFT_ML_RG_OBJ_REPLAYGAIN_H
+#define NULLSOFT_ML_RG_OBJ_REPLAYGAIN_H
+
+#include <bfc/dispatch.h>
+
+enum
+{
+ RG_SUCCESS = 0,
+ RG_FAILURE = 1,
+ RG_MODE_NOT_SUPPORTED=2,
+
+ RG_INDIVIDUAL_TRACKS = 0, // use this mode to calculate each track sent individually
+ RG_ALBUM = 1, // use this mode to treat all tracks sent as belonging to the same album
+ RG_AUTO = 2, // retrieve tags from the files to determine album info
+};
+
+class obj_replaygain : public Dispatchable
+{
+protected:
+ obj_replaygain() {}
+ ~obj_replaygain() {}
+public:
+ int Open(int mode);
+ int ProcessTrack(const wchar_t *filename);
+ int Write();
+ void Close();
+
+ DISPATCH_CODES
+ {
+ OBJ_REPLAYGAIN_OPEN = 10,
+ OBJ_REPLAYGAIN_PROCESSTRACK = 20,
+ OBJ_REPLAYGAIN_WRITE = 30,
+ OBJ_REPLAYGAIN_CLOSE = 40,
+ };
+};
+
+inline int obj_replaygain::Open(int mode)
+{
+ return _call(OBJ_REPLAYGAIN_OPEN, (int)RG_FAILURE, mode);
+}
+
+inline int obj_replaygain::ProcessTrack(const wchar_t *filename)
+{
+ return _call(OBJ_REPLAYGAIN_PROCESSTRACK, (int)RG_FAILURE, filename);
+}
+
+inline int obj_replaygain::Write()
+{
+ return _call(OBJ_REPLAYGAIN_WRITE, (int)RG_FAILURE);
+}
+
+inline void obj_replaygain::Close()
+{
+ _voidcall(OBJ_REPLAYGAIN_CLOSE);
+}
+
+// {3A398A1B-D316-4094-993E-27EAEA553D19}
+static const GUID RGGUID =
+{ 0x3a398a1b, 0xd316, 0x4094, { 0x99, 0x3e, 0x27, 0xea, 0xea, 0x55, 0x3d, 0x19 } };
+
+
+#endif
diff --git a/Src/Plugins/Library/ml_rg/replaygain.cpp b/Src/Plugins/Library/ml_rg/replaygain.cpp
new file mode 100644
index 00000000..beda7f8b
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/replaygain.cpp
@@ -0,0 +1,370 @@
+#include "main.h"
+#include <math.h>
+#include "../ReplayGainAnalysis/gain_analysis.h"
+#include "api__ml_rg.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <locale.h>
+
+#pragma intrinsic(fabs)
+
+static inline float fastmax(float x, float a)
+{
+ x -= a;
+ x += (float)fabs(x);
+ x *= 0.5f;
+ x += a;
+ return (x);
+}
+
+static HMODULE rgLib = 0;
+typedef int(*INITGAINANALYSIS)(void *context, long samplefreq);
+static INITGAINANALYSIS InitGainAnalysis = 0;
+typedef int(*ANALYZESAMPLES)(void *context, const float * left_samples, const float * right_samples, size_t num_samples, int num_channels);
+static ANALYZESAMPLES AnalyzeSamples = 0;
+typedef int(*RESETSAMPLEFREQUENCY)(void *context, long samplefreq);
+static RESETSAMPLEFREQUENCY ResetSampleFrequency = 0;
+typedef float(*GETTITLEGAIN)(void *context);
+static GETTITLEGAIN GetTitleGain = 0;
+typedef float(*GETALBUMGAIN)(void *context);
+static GETALBUMGAIN GetAlbumGain = 0;
+typedef void *(* CREATERGCONTEXT)();
+static CREATERGCONTEXT CreateRGContext = 0;
+typedef void(*FREERGCONTEXT)(void *context);
+static FREERGCONTEXT FreeRGContext = 0;
+
+void LoadRG()
+{
+ if (rgLib)
+ return ;
+
+ wchar_t path[MAX_PATH] = {0};
+ PathCombineW(path, (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETSHAREDDLLDIRECTORYW), L"ReplayGainAnalysis.dll");
+ rgLib = LoadLibraryW(path);
+
+ if (rgLib)
+ {
+ InitGainAnalysis = (INITGAINANALYSIS)GetProcAddress(rgLib, "WAInitGainAnalysis");
+ AnalyzeSamples = (ANALYZESAMPLES)GetProcAddress(rgLib, "WAAnalyzeSamples");
+ GetTitleGain = (GETTITLEGAIN)GetProcAddress(rgLib, "WAGetTitleGain");
+ ResetSampleFrequency = (RESETSAMPLEFREQUENCY)GetProcAddress(rgLib, "WAResetSampleFrequency");
+ GetAlbumGain = (GETALBUMGAIN)GetProcAddress(rgLib, "WAGetAlbumGain");
+ CreateRGContext = (CREATERGCONTEXT)GetProcAddress(rgLib, "WACreateRGContext");
+ FreeRGContext = (FREERGCONTEXT)GetProcAddress(rgLib, "WAFreeRGContext");
+ }
+}
+
+void *CreateRG()
+{
+ LoadRG();
+
+ return CreateRGContext();
+}
+
+void DestroyRG(void *context)
+{
+ FreeRGContext(context);
+}
+
+#define CHUNKSIZE 16384
+static void CalculateRG_float(void *context, ifc_audiostream *decoder, AudioParameters *parameters, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak)
+{
+ float data[2*CHUNKSIZE] = {0};
+ float right[CHUNKSIZE] = {0};
+ float peak = 0;
+
+ if (parameters->channels > 2)
+ {
+ char titleStr[32] = {0};
+ MessageBoxA(GetDialogBoxParent(),
+ WASABI_API_LNGSTRING(IDS_TOO_MANY_CHANNELS),
+ WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
+ MB_OK);
+ decodeFile->CloseAudio(decoder);
+ return ;
+ }
+ ResetSampleFrequency(context, parameters->sampleRate);
+ if (callback) callback->InformSize((parameters->sizeBytes == -1) ? 0 : parameters->sizeBytes);
+ while (1)
+ {
+ if (*killSwitch)
+ {
+ decodeFile->CloseAudio(decoder);
+ return ;
+ }
+ int error=0;
+ size_t bytesRead = decoder->ReadAudio((void *)data, sizeof(data), killSwitch, &error);
+ if (*killSwitch)
+ {
+ decodeFile->CloseAudio(decoder);
+ return ;
+ }
+ else if (error)
+ {
+ break;
+ }
+ if (callback) callback->Progress(bytesRead);
+
+ size_t samples = bytesRead / sizeof(*data);
+
+ if (!samples)
+ break;
+
+ for (size_t i = 0;i != samples;i++)
+ {
+ peak = fastmax(peak, (float)fabs(data[i]));
+ data[i] *= 32768.0f;
+ }
+ albumPeak = fastmax(peak, albumPeak);
+
+ if (parameters->channels == 1)
+ AnalyzeSamples(context, data, NULL, samples, 1);
+ else
+ {
+ size_t samples2 = samples / 2;
+ for (size_t i = 0;i != samples2;i++)
+ {
+ data[i] = data[i * 2];
+ right[i] = data[i * 2 + 1];
+ }
+ AnalyzeSamples(context, data, right, samples2, 2);
+ }
+ }
+ decodeFile->CloseAudio(decoder);
+ float gain = GetTitleGain(context);
+ if (gain != GAIN_NOT_ENOUGH_SAMPLES)
+ {
+ _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
+ _snwprintf_l(track_gain, 64, L"%-+.2f dB", C_locale, gain);
+ _snwprintf_l(track_peak, 64, L"%-.9f", C_locale, peak);
+ }
+}
+
+static void FillFloat(float *left, float *right, void *samples, size_t bps, size_t numSamples, size_t numChannels, float &peak, float &albumPeak, float gain)
+{
+ switch (bps)
+ {
+ case 8:
+ {
+ unsigned __int8 *samples8 = (unsigned __int8 *)samples;
+ size_t t = 0;
+ for (size_t x = 0; x != numSamples; x ++)
+ {
+ left[x] = (float)(samples8[t++] - 128) * 256.0f * gain;
+
+ if (numChannels == 2)
+ {
+ right[x] = (float)(samples8[t++] - 128) * 256.0f* gain;
+ }
+ else
+ right[x] = left[x];
+ peak = fastmax(peak, (float)fabs(left[x]));
+ peak = fastmax(peak, (float)fabs(right[x]));
+ albumPeak=fastmax(albumPeak, peak);
+ }
+ }
+ break;
+ case 16:
+ {
+ short *samples16 = (short *)samples;
+ size_t t = 0;
+ if (numChannels == 1)
+ {
+ for (size_t x = 0; x != numSamples; x ++)
+ {
+ left[x] = (float)samples16[t++] * gain;
+ right[x] = left[x];
+ peak = fastmax(peak, (float)fabs(left[x]));
+ albumPeak=fastmax(albumPeak, peak);
+
+ }
+ }
+ else if (numChannels == 2)
+ {
+ for (size_t x = 0; x != numSamples; x ++)
+ {
+ left[x] = (float)samples16[t++] * gain ;
+ right[x] = (float)samples16[t++] * gain;
+
+ peak = fastmax(peak, (float)fabs(left[x]));
+ peak = fastmax(peak, (float)fabs(right[x]));
+ albumPeak=fastmax(albumPeak, peak);
+ }
+ }
+ }
+ break;
+ case 24:
+ {
+ unsigned __int8 *samples8 = (unsigned __int8 *)samples;
+ for (size_t x = 0; x != numSamples; x ++)
+ {
+ long temp = (((long)samples8[0]) << 8);
+ temp = temp | (((long)samples8[1]) << 16);
+ temp = temp | (((long)samples8[2]) << 24);
+ left[x] = (float)temp* gain / 65536.0f;
+ samples8 += 3;
+ if (numChannels == 2)
+ {
+ temp = (((long)samples8[0]) << 8);
+ temp = temp | (((long)samples8[1]) << 16);
+ temp = temp | (((long)samples8[2]) << 24);
+ right[x] = (float)temp* gain / 65536.0f;
+ samples8 += 3;
+ }
+ else
+ right[x] = left[x];
+ peak = fastmax(peak, (float)fabs(left[x]));
+ peak = fastmax(peak, (float)fabs(right[x]));
+ albumPeak=fastmax(albumPeak, peak);
+
+ }
+ }
+ break;
+ }
+
+}
+#undef CHUNKSIZE
+#define CHUNKSIZE 4096
+static void CalculateRG_pcm(void *context, ifc_audiostream *decoder, AudioParameters *parameters, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak)
+{
+ char data[4*2*CHUNKSIZE] = {0};
+ float left[CHUNKSIZE] = {0};
+ float right[CHUNKSIZE] = {0};
+ float peak = 0;
+ if (parameters->channels > 2)
+ {
+ char titleStr[32];
+ MessageBoxA(GetDialogBoxParent(),
+ WASABI_API_LNGSTRING(IDS_TOO_MANY_CHANNELS),
+ WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
+ MB_OK);
+ decodeFile->CloseAudio(decoder);
+ return ;
+ }
+
+ int padded_bits = (parameters->bitsPerSample + 7) & (~7);
+ albumPeak *= 32768.0f;
+ ResetSampleFrequency(context, parameters->sampleRate);
+ if (callback) callback->InformSize((parameters->sizeBytes == -1) ? 0 : parameters->sizeBytes);
+ while (1)
+ {
+ if (*killSwitch)
+ {
+ decodeFile->CloseAudio(decoder);
+ return ;
+ }
+ int error=0;
+ size_t bytesRead = decoder->ReadAudio((void *)data, 4096 * parameters->channels * (padded_bits / 8), killSwitch, &error);
+ if (*killSwitch)
+ {
+ decodeFile->CloseAudio(decoder);
+ return ;
+ }
+ else if (error)
+ {
+ break;
+ }
+
+ if (callback) callback->Progress(bytesRead);
+
+ size_t samples = bytesRead / (padded_bits / 8);
+
+ if (!samples)
+ break;
+
+ FillFloat(left, right, data, padded_bits, samples / parameters->channels, parameters->channels, peak, albumPeak, (float)pow(2., (double)(padded_bits - parameters->bitsPerSample)));
+
+ size_t samples2 = samples / 2;
+ AnalyzeSamples(context, left, right, samples2, 2);
+ }
+ decodeFile->CloseAudio(decoder);
+ float gain = GetTitleGain(context);
+ if (gain != GAIN_NOT_ENOUGH_SAMPLES)
+ {
+ StringCchPrintfW(track_gain, 64, L"%-+.2f dB", gain);
+ StringCchPrintfW(track_peak, 64, L"%-.9f", peak / 32768.0f);
+ }
+
+ albumPeak /= 32768.0f;
+}
+
+void CalculateRG(void *context, const wchar_t *filename, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak)
+{
+ LoadRG();
+ if (!rgLib)
+ {
+ char titleStr[32] = {0};
+ MessageBoxA(GetDialogBoxParent(),
+ WASABI_API_LNGSTRING(IDS_NOT_ABLE_TO_OPEN_RG_DLL),
+ WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
+ MB_OK);
+ return ;
+ }
+
+ wchar_t dummy[64] = {0};
+ if (!GetFileInfo(filename, L"replaygain_track_gain", dummy, 64)) // check if the plugin even supports replaygain
+ return ;
+
+ /*
+ TODO: want to do something like this, but have to do it on the main thread (ugh)
+ if (!_wcsnicmp(dummy, "-24601", 6))
+ {
+ SetFileInfo(itr->filename, L"replaygain_track_gain", L"");
+ SetFileInfo(itr->filename, L"replaygain_track_peak", L"");
+ SetFileInfo(itr->filename, L"replaygain_album_gain", L"");
+ SetFileInfo(itr->filename, L"replaygain_album_peak", L"");
+ WriteFileInfo();
+ }
+ */
+
+ AudioParameters parameters;
+ parameters.flags = AUDIOPARAMETERS_FLOAT | AUDIOPARAMETERS_MAXCHANNELS | AUDIOPARAMETERS_MAXSAMPLERATE;
+ parameters.channels = 2;
+ parameters.sampleRate = 192000;
+
+ ifc_audiostream *decoder = decodeFile->OpenAudioBackground(filename, &parameters);
+ if (decoder)
+ CalculateRG_float(context, decoder, &parameters, track_gain, track_peak, callback, killSwitch, albumPeak);
+ else
+ {
+ // try PCM
+ memset(&parameters, 0, sizeof(AudioParameters));
+ parameters.flags = AUDIOPARAMETERS_MAXCHANNELS | AUDIOPARAMETERS_MAXSAMPLERATE;
+ parameters.channels = 2;
+ parameters.sampleRate = 192000;
+
+ ifc_audiostream *decoder = decodeFile->OpenAudioBackground(filename, &parameters);
+ if (decoder)
+ CalculateRG_pcm(context, decoder, &parameters, track_gain, track_peak, callback, killSwitch, albumPeak);
+ }
+
+}
+
+void CalculateAlbumRG(void *context, wchar_t album_gain[64], wchar_t album_peak[64], float &albumPeak)
+{
+ float gain = GetAlbumGain(context);
+ if (gain != GAIN_NOT_ENOUGH_SAMPLES)
+ {
+ /*StringCchPrintfW(album_gain, 64, L"%-+.2f dB", gain);
+ StringCchPrintfW(album_peak, 64, L"%-.9f", albumPeak);*/
+ _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
+ _snwprintf_l(album_gain, 64, L"%-+.2f dB", C_locale, gain);
+ _snwprintf_l(album_peak, 64, L"%-.9f", C_locale, albumPeak);
+ }
+}
+
+void StartRG(void *context)
+{
+ LoadRG();
+ if (!rgLib)
+ {
+ char titleStr[32] = {0};
+ MessageBoxA(GetDialogBoxParent(),
+ WASABI_API_LNGSTRING(IDS_NOT_ABLE_TO_OPEN_RG_DLL),
+ WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
+ MB_OK);
+ return ;
+ }
+
+ InitGainAnalysis(context, 44100); // since this is most common. We'll reset it before doing a real calculation anyway
+}
diff --git a/Src/Plugins/Library/ml_rg/resource.h b/Src/Plugins/Library/ml_rg/resource.h
new file mode 100644
index 00000000..88f4f058
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/resource.h
@@ -0,0 +1,40 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_rg.rc
+//
+#define IDS_CALCULATE_REPLAY_GAIN 1
+#define IDS_1_OF_X_FILES 2
+#define IDS_FINISHED 3
+#define IDS_X_OF_X_FILES 4
+#define IDS_PROCESSING 5
+#define IDS_TOO_MANY_CHANNELS 6
+#define IDS_REPLAYGAIN 7
+#define IDS_NOT_ABLE_TO_OPEN_RG_DLL 8
+#define IDS_COL_FILENAME 9
+#define IDS_COL_TRACK_GAIN 10
+#define IDS_COL_TRACK_PEAK 11
+#define IDS_COL_ALBUM_GAIN 12
+#define IDS_COL_ALBUM_PEAK 13
+#define IDS_STRING14 14
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 14
+#define IDD_RESULTS 101
+#define IDD_PROGRESS 102
+#define IDC_RGLIST 1001
+#define IDC_SAVETRACK 1002
+#define IDC_PROGRESS_FILES 1003
+#define IDC_FILE_PROGRESS 1004
+#define IDC_ASK 1005
+#define IDC_ALBUM 1006
+#define IDC_ALL 1007
+#define IDS_NULLSOFT_REPLAY_GAIN_ANALYZER 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 107
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1008
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_rg/version.rc2 b/Src/Plugins/Library/ml_rg/version.rc2
new file mode 100644
index 00000000..c016ebfe
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,29,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,29,0,0"
+ VALUE "InternalName", "Nullsoft Replay Gain Analyzer"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_rg.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_transcode/LinkedQueue.cpp b/Src/Plugins/Library/ml_transcode/LinkedQueue.cpp
new file mode 100644
index 00000000..7896c7bb
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/LinkedQueue.cpp
@@ -0,0 +1,139 @@
+#include "LinkedQueue.h"
+
+LinkedQueue::LinkedQueue()
+{
+ size=0;
+ head=NULL;
+ tail=NULL;
+ bm=NULL;
+ bmpos=0;
+ InitializeCriticalSection(&cs);
+}
+
+void LinkedQueue::lock()
+{
+ EnterCriticalSection(&cs);
+ //wchar_t buf[100]; wsprintf(buf,L"Lock taken by %x",GetCurrentThreadId()); OutputDebugString(buf);
+}
+void LinkedQueue::unlock()
+{
+ LeaveCriticalSection(&cs);
+ //wchar_t buf[100]; wsprintf(buf,L"Lock released by %x",GetCurrentThreadId()); OutputDebugString(buf);
+}
+
+LinkedQueue::~LinkedQueue()
+{
+ lock();
+ QueueElement * q=head;
+ while(q) { QueueElement *p=q; q=q->next; delete p; }
+ unlock();
+ DeleteCriticalSection(&cs);
+}
+
+void LinkedQueue::Offer(void * e)
+{
+ lock();
+ if(size==0) { size++; head=tail=new QueueElement(e); unlock(); return; }
+ tail->next=new QueueElement(e);
+ tail->next->prev=tail;
+ tail=tail->next;
+ size++;
+ bm=NULL;
+ unlock();
+}
+
+void * LinkedQueue::Poll()
+{
+ lock();
+ if(size == 0) { unlock(); return NULL; }
+ size--;
+ void * r = head->elem;
+ QueueElement * q = head;
+ head=head->next;
+ if(head!=NULL) head->prev=NULL;
+ else tail=NULL;
+ delete q;
+ bm=NULL;
+ unlock();
+ return r;
+}
+
+void * LinkedQueue::Peek()
+{
+ lock();
+ void * ret=head?head->elem:NULL;
+ unlock();
+ return ret;
+}
+
+QueueElement * LinkedQueue::Find(int x)
+{
+ if(x>=size || x<0) return NULL;
+ if(x == 0) return head;
+ if(x == size-1) return tail;
+ if(!bm) { bm=head; bmpos=0; }
+ int diffh = x;
+ int difft = (size-1) - x;
+ int diffbm = x - bmpos;
+ diffbm>0?diffbm:-diffbm;
+ if(diffh < difft && diffh < diffbm) { bm=head; bmpos=0; }
+ else if(diffh >= difft && diffbm >= difft) { bm=tail; bmpos=size-1; }
+ while(bmpos > x && bm) { bm=bm->prev; bmpos--; }
+ while(bmpos < x && bm) { bm=bm->next; bmpos++; }
+ return bm;
+}
+
+void * LinkedQueue::Get(int pos)
+{
+ lock();
+ QueueElement * e = Find(pos);
+ unlock();
+ return e?e->elem:NULL;
+}
+
+void LinkedQueue::Set(int pos, void * val)
+{
+ lock();
+ QueueElement * e = Find(pos);
+ if(e) e->elem=val;
+ unlock();
+}
+
+void* LinkedQueue::Del(int pos)
+{
+ lock();
+ QueueElement * e = Find(pos);
+ if(!e) { unlock(); return NULL; }
+ else if(size == 1) head=tail=NULL;
+ else if(e==head)
+ {
+ head=head->next;
+ head->prev=NULL;
+ }
+ else if(e==tail)
+ {
+ tail=tail->prev;
+ tail->next=NULL;
+ }
+ else
+ {
+ e->prev->next = e->next;
+ e->next->prev = e->prev;
+ }
+ size--;
+ bm=NULL;
+ unlock();
+ void * ret = e->elem;
+ delete e;
+ return ret;
+}
+
+int LinkedQueue::GetSize()
+{
+ return size;
+ /*
+ lock();
+ int s = size;
+ unlock();
+ return s;*/
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/LinkedQueue.h b/Src/Plugins/Library/ml_transcode/LinkedQueue.h
new file mode 100644
index 00000000..84310efc
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/LinkedQueue.h
@@ -0,0 +1,41 @@
+#ifndef _LINKEDQUEUE_H_
+#define _LINKEDQUEUE_H_
+
+#include <windows.h>
+
+class LinkedQueue;
+class QueueElement;
+
+class QueueElement {
+public:
+ QueueElement * next;
+ QueueElement * prev;
+ void * elem;
+ QueueElement(void * e) { next=NULL; prev=NULL; elem=e; }
+};
+
+
+class LinkedQueue {
+protected:
+ QueueElement * head;
+ QueueElement * tail;
+ QueueElement * bm;
+ int bmpos;
+ int size;
+ QueueElement * Find(int pos);
+ CRITICAL_SECTION cs;
+public:
+ LinkedQueue();
+ ~LinkedQueue();
+ int GetSize();
+ void Offer(void * e);
+ void *Poll();
+ void *Peek();
+ void *Get(int pos);
+ void Set(int pos, void * val);
+ void *Del(int pos);
+ void lock();
+ void unlock();
+};
+
+#endif //_LINKEDQUEUE_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/api__ml_transcode.h b/Src/Plugins/Library/ml_transcode/api__ml_transcode.h
new file mode 100644
index 00000000..07a944bc
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/api__ml_transcode.h
@@ -0,0 +1,26 @@
+#ifndef NULLSOFT_ML_TRANSCODE_API_H
+#define NULLSOFT_ML_TRANSCODE_API_H
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Metadata/api_metadata.h"
+extern api_metadata *metadataApi;
+#define AGAVE_API_METADATA metadataApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../Agave/AlbumArt/api_albumart.h"
+extern api_albumart *albumArtApi;
+#define AGAVE_API_ALBUMART albumArtApi
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/main.cpp b/Src/Plugins/Library/ml_transcode/main.cpp
new file mode 100644
index 00000000..e0f0c457
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/main.cpp
@@ -0,0 +1,1100 @@
+#define PLUGIN_VERSION L"2.79"
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlwapi.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../winamp/wa_ipc.h"
+#include "../nu/AutoWide.h"
+#include "../nu/ns_wc.h"
+#include "../nu/AutoChar.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+#include "LinkedQueue.h"
+#include "resource.h"
+#include <vector>
+#include <api/service/waServiceFactory.h>
+
+#include "api__ml_transcode.h"
+
+#include <strsafe.h>
+#define WM_TRANSCODE_START WM_APP+1
+#define WM_TRANSCODE_ADD WM_APP+10
+#define WM_TRANSCODE_UPDATEUI WM_APP+11
+#define WM_TRANSCODE_ABORT WM_APP+12
+
+static int init();
+static void quit();
+static HWND GetDialogBoxParent();
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_transcode.dll)",
+ init,
+ quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+typedef std::vector<const wchar_t*> PtrListWCharPtr;
+LinkedQueue transcodeQueue;
+
+int transcodeConfig(HWND parent); // returns fourcc
+
+void transcode(const itemRecordListW *ice, HWND parent);
+void transcode(PtrListWCharPtr &filenames, HWND parent);
+
+void addTrackToTranscodeQueue(itemRecordW *track, unsigned int format, const wchar_t* dest, const wchar_t* folder);
+void addTrackToTranscodeQueue(const wchar_t *track, unsigned int format, const wchar_t* dest, const wchar_t* folder);
+
+void filenameToItemRecord(const wchar_t *file, itemRecordW *ice);
+void copyTags(const itemRecordW *in, const wchar_t *out);
+
+extern void RecursiveCreateDirectory(wchar_t *buf1);
+extern wchar_t *FixReplacementVars(wchar_t *str, size_t str_size, itemRecordW *song);
+void FixFileLength(wchar_t *str);
+
+HWND transcoderWnd = NULL;
+
+wchar_t inifile[MAX_PATH] = {0};
+char inifileA[MAX_PATH] = {0};
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+api_metadata *AGAVE_API_METADATA=0;
+api_application *WASABI_API_APP=0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_albumart *AGAVE_API_ALBUMART = 0;
+api_stats *AGAVE_API_STATS = 0;
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+int init()
+{
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ ServiceBuild(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceBuild(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceBuild(AGAVE_API_STATS, AnonymousStatsGUID);
+
+ const wchar_t *iniDirectory = WASABI_API_APP->path_getUserSettingsPath();
+ PathCombine(inifile, iniDirectory, L"Plugins\\ml\\ml_transcode.ini");
+ lstrcpynA(inifileA, AutoChar(inifile), MAX_PATH);
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlTranscodeLangGUID);
+
+ static wchar_t szDescription[256];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_FORMAT_CONVERTER ), PLUGIN_VERSION );
+
+ plugin.description = (char*)szDescription;
+
+ return ML_INIT_SUCCESS;
+}
+
+void quit()
+{
+ if(IsWindow(transcoderWnd))
+ SendMessage(transcoderWnd,WM_TRANSCODE_ABORT,0,0);
+
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ ServiceRelease(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID);
+}
+
+class TranscodePlaylistLoader : public ifc_playlistloadercallback
+{
+public:
+ TranscodePlaylistLoader(PtrListWCharPtr*_fileList);
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info);
+
+protected:
+ PtrListWCharPtr*fileList;
+ RECVS_DISPATCH;
+};
+
+TranscodePlaylistLoader::TranscodePlaylistLoader(PtrListWCharPtr*_fileList)
+{
+ fileList = _fileList;
+}
+
+void TranscodePlaylistLoader::OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+{
+ fileList->push_back(_wcsdup(filename));
+}
+
+#define CBCLASS TranscodePlaylistLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+
+INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_FILENAMES ||
+ param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_FILENAMESW
+ || (AGAVE_API_PLAYLISTMANAGER && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)))
+ {
+ mlAddToSendToStructW s;
+ s.context=param2;
+ s.desc=WASABI_API_LNGSTRINGW(IDS_FORMAT_CONVERTER);
+ s.user32=(intptr_t)PluginMessageProc;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&s,ML_IPC_ADDTOSENDTOW);
+ }
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param2 && param3 == (INT_PTR)PluginMessageProc)
+ {
+ if(param1 == ML_TYPE_FILENAMES)
+ {
+ PtrListWCharPtr fileList;
+ TranscodePlaylistLoader loader(&fileList);
+ const char * filenames = (const char *)param2;
+ while(filenames && *filenames)
+ {
+ // try to load as playlist first
+ if (AGAVE_API_PLAYLISTMANAGER->Load(AutoWide(filenames), &loader) != PLAYLISTMANAGER_SUCCESS)
+ {
+ // not a playlist.. just add it directly
+ fileList.push_back(AutoWideDup(filenames));
+ }
+ filenames+=strlen(filenames)+1;
+ }
+ transcode(fileList,GetDialogBoxParent());
+
+ //fileList.freeAll();
+ for (auto file : fileList)
+ {
+ free((void*)file);
+ }
+ fileList.clear();
+
+ return 1;
+ }
+ else if(param1 == ML_TYPE_FILENAMESW)
+ {
+ PtrListWCharPtr fileList;
+ TranscodePlaylistLoader loader(&fileList);
+ const wchar_t * filenames = (const wchar_t *)param2;
+ while(filenames && *filenames)
+ {
+ // try to load as playlist first
+ if (AGAVE_API_PLAYLISTMANAGER->Load(filenames, &loader) != PLAYLISTMANAGER_SUCCESS)
+ {
+ // not a playlist.. just add it directly
+ fileList.push_back(filenames);
+ }
+ filenames+=wcslen(filenames)+1;
+ }
+ transcode(fileList,GetDialogBoxParent());
+ return 1;
+ }
+ else if(param1 == ML_TYPE_ITEMRECORDLIST)
+ {
+ const itemRecordList *ico=(const itemRecordList*)param2;
+ itemRecordListW list = {0,};
+ convertRecordList(&list, ico);
+ transcode(&list, GetDialogBoxParent());
+ freeRecordList(&list);
+ return 1;
+ }
+ else if(param1 == ML_TYPE_ITEMRECORDLISTW)
+ {
+ const itemRecordListW *list =(const itemRecordListW*)param2;
+ transcode(list,GetDialogBoxParent());
+ return 1;
+ }
+ else if (param1 == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist *playlist = (mlPlaylist *)param2;
+ PtrListWCharPtr fileList;
+ fileList.reserve(playlist->numItems);
+ TranscodePlaylistLoader loader(&fileList);
+ AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
+ transcode(fileList,GetDialogBoxParent());
+
+ //fileList.freeAll();
+ for (auto file : fileList)
+ {
+ free((void*)file);
+ }
+ fileList.clear();
+
+ return 1;
+ }
+ else if (param1 == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)param2;
+ PtrListWCharPtr fileList;
+ while (playlists && *playlists)
+ {
+ mlPlaylist *playlist = *playlists;
+ fileList.reserve(fileList.size() + playlist->numItems);
+ TranscodePlaylistLoader loader(&fileList);
+ AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
+ playlists++;
+ }
+ transcode(fileList,GetDialogBoxParent());
+
+ //fileList.freeAll();
+ for (auto file : fileList)
+ {
+ free((void*)file);
+ }
+ fileList.clear();
+
+ return 1;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_CONFIG)
+ {
+ transcodeConfig((HWND)param1);
+ return 1;
+ }
+ return 0;
+}
+
+class TranscodeItem
+{
+ public:
+ itemRecordW ice;
+ unsigned int fourcc;
+ wchar_t *outfile;
+ TranscodeItem( unsigned int fourcc, const wchar_t *folder, const wchar_t *outfile, const itemRecordW *i ) : fourcc( fourcc )
+ {
+ ZeroMemory( &ice, sizeof( ice ) );
+ copyRecord( &ice, const_cast<itemRecordW *>( i ) ); // TODO: remove for 5.53. this just works around some 5.52 build weirdness
+ makefn( folder, outfile );
+ }
+ TranscodeItem( unsigned int fourcc, const wchar_t *folder, const wchar_t *outfile, const wchar_t *i ) : fourcc( fourcc )
+ {
+ ZeroMemory( &ice, sizeof( ice ) );
+ filenameToItemRecord( i, &ice );
+ makefn( folder, outfile );
+ }
+ void makefn( const wchar_t *folder, const wchar_t *outfile )
+ {
+ if ( GetPrivateProfileInt( L"transcoder", L"usefilename", 0, inifile ) )
+ {
+ size_t len = wcslen( ice.filename ) + 10;
+ this->outfile = (wchar_t *)calloc( len, sizeof( this->outfile[ 0 ] ) );
+ StringCchCopyW( this->outfile, len, ice.filename );
+ const wchar_t *extn = wcsrchr( outfile, L'.' );
+ wchar_t *exto = wcsrchr( this->outfile, L'.' );
+ if ( extn && exto && wcslen( extn ) < 10 )
+ StringCchCopy( exto, len, extn );
+ }
+ else
+ {
+ wchar_t filename[ 2048 ] = { 0 };
+ StringCchCopy( filename, 2048, outfile );
+ FixReplacementVars( filename, 2048, &ice );
+ FixFileLength( filename );
+ PathCombine( filename, folder, filename );
+ this->outfile = _wcsdup( filename );
+ }
+ }
+ ~TranscodeItem()
+ {
+ free( outfile ); freeRecord( &ice );
+ }
+};
+
+void getViewport( RECT *r, HWND wnd, int full, RECT *sr )
+{
+ POINT *p = NULL;
+ if ( p || sr || wnd )
+ {
+ HMONITOR hm = NULL;
+
+ if ( sr )
+ hm = MonitorFromRect( sr, MONITOR_DEFAULTTONEAREST );
+ else if ( wnd )
+ hm = MonitorFromWindow( wnd, MONITOR_DEFAULTTONEAREST );
+ else if ( p )
+ hm = MonitorFromPoint( *p, MONITOR_DEFAULTTONEAREST );
+
+ if ( hm )
+ {
+ MONITORINFOEX mi;
+ memset( &mi, 0, sizeof( mi ) );
+ mi.cbSize = sizeof( mi );
+
+ if ( GetMonitorInfoW( hm, &mi ) )
+ {
+ if ( !full )
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+
+ return;
+ }
+ }
+ }
+
+ if ( full )
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics( SM_CXSCREEN );
+ r->bottom = GetSystemMetrics( SM_CYSCREEN );
+ }
+ else
+ {
+ SystemParametersInfoW( SPI_GETWORKAREA, 0, r, 0 );
+ }
+}
+
+BOOL windowOffScreen( HWND hwnd, POINT pt )
+{
+ RECT r = { 0 }, wnd = { 0 }, sr = { 0 };
+
+ GetWindowRect( hwnd, &wnd );
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + ( wnd.right - wnd.left );
+ sr.bottom = sr.top + ( wnd.bottom - wnd.top );
+
+ getViewport( &r, hwnd, 0, &sr );
+
+ return !PtInRect( &r, pt );
+}
+
+bool transcoding = false;
+bool transcoderIdle = false;
+int totalItems = 0;
+int itemsDone = 0;
+int itemsFailed = 0;
+
+static BOOL CALLBACK transcode_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static convertFileStructW * cfs;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ transcoderWnd = hwndDlg;
+ cfs = (convertFileStructW *)calloc(1, sizeof(convertFileStructW));
+ SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETRANGE32,0,100);
+ SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETRANGE32,0,totalItems*100);
+ SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,0,0);
+ SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETPOS,0,0);
+ PostMessage(hwndDlg,WM_TRANSCODE_START,0,0);
+
+ // show edit info window and restore last position as applicable
+ POINT pt = {GetPrivateProfileInt(L"transcoder", L"convert_x", -1, inifile),
+ GetPrivateProfileInt(L"transcoder", L"convert_y", -1, inifile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOWNA);
+ }
+ break;
+ case WM_TRANSCODE_START:
+ for(;;)
+ {
+ TranscodeItem * t = (TranscodeItem *)transcodeQueue.Peek();
+ if(!t)
+ {
+ SetDlgItemText(hwndDlg,IDC_ABORT,WASABI_API_LNGSTRINGW(IDS_DONE));
+ SendMessage(transcoderWnd,WM_TRANSCODE_UPDATEUI,0,0);
+ transcoderIdle=true;
+ return 0;
+ }
+ transcoderIdle=false;
+ RecursiveCreateDirectory(t->outfile);
+ ZeroMemory(cfs,sizeof(*cfs));
+ cfs->callbackhwnd = hwndDlg;
+ cfs->sourcefile = t->ice.filename;
+ cfs->destfile = t->outfile;
+ cfs->destformat[0] = t->fourcc;
+ //cfs->destformat[1] = 44100;
+ //cfs->destformat[2] = 16;
+ //cfs->destformat[3] = 2;
+ cfs->destformat[6] = mmioFOURCC('I','N','I',' ');
+ cfs->destformat[7] = (intptr_t)inifileA;
+ cfs->error = L"";
+
+ SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,0,0);
+ wchar_t buf[1024] = {0};
+ StringCchPrintf(buf, 1024, L"%s - %s", t->ice.artist?t->ice.artist:L"", t->ice.title?t->ice.title:L"");
+ SetDlgItemText(hwndDlg,IDC_CURRENTTRACK,buf);
+
+ if(SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)cfs,IPC_CONVERTFILEW)) break;
+ else
+ {
+ if (cfs->error && *cfs->error)
+ {
+ MessageBox(hwndDlg, cfs->error, WASABI_API_LNGSTRINGW(IDS_TRANSCODING_FAILED), MB_ICONWARNING | MB_OK);
+ }
+ delete (TranscodeItem*)transcodeQueue.Poll();
+ itemsDone++; itemsFailed++;
+ }
+ }
+ case WM_TRANSCODE_ADD: // update totals, stuff added to queue
+ SetWindowPos(hwndDlg,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
+ if(transcoderIdle)
+ {
+ SetDlgItemText(hwndDlg,IDC_ABORT,WASABI_API_LNGSTRINGW(IDS_ABORT));
+ SendMessage(hwndDlg,WM_TRANSCODE_START,0,0);
+ return 0;
+ } // else update UI
+ case WM_TRANSCODE_UPDATEUI:
+ {
+ SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETRANGE32,0,totalItems*100);
+ SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETPOS,itemsDone*100,0);
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, 100, WASABI_API_LNGSTRINGW(IDS_TRACKS_DONE_REMAINING_FAILED),
+ itemsDone-itemsFailed, totalItems-itemsDone, itemsFailed);
+ SetDlgItemText(hwndDlg,IDC_TOTALCAPTION,buf);
+ }
+ break;
+ case WM_WA_IPC:
+ switch(lParam)
+ {
+ case IPC_CB_CONVERT_STATUS:
+ if(wParam >= 0 && wParam <= 100)
+ {
+ SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETPOS,(int)wParam + itemsDone*100,0);
+ SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,(int)wParam,0);
+ }
+ break;
+ case IPC_CB_CONVERT_DONE:
+ {
+ SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,100,0);
+ TranscodeItem * t = (TranscodeItem*)transcodeQueue.Poll();
+ itemsDone++;
+ cfs->callbackhwnd=NULL;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)cfs,IPC_CONVERTFILEW_END);
+ copyTags(&t->ice,t->outfile);
+ if (AGAVE_API_STATS)
+ {
+ AGAVE_API_STATS->IncrementStat(api_stats::TRANSCODE_COUNT);
+ AGAVE_API_STATS->SetStat(api_stats::TRANSCODE_FORMAT, t->fourcc);
+ }
+ delete t;
+ PostMessage(hwndDlg,WM_TRANSCODE_START,0,0);
+ }
+ break;
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_ABORT:
+ transcode_dlgproc(hwndDlg,WM_TRANSCODE_ABORT,0,0);
+ break;
+ }
+ break;
+ case WM_CLOSE:
+ case WM_TRANSCODE_ABORT:
+ {
+ transcoding = false;
+ cfs->callbackhwnd = NULL;
+ if(!transcoderIdle) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)cfs,IPC_CONVERTFILEW_END);
+ TranscodeItem * t;
+ while((t = (TranscodeItem*)transcodeQueue.Poll()) != NULL)
+ {
+ // prompt to delete incomplete conversions
+ if(PathFileExists(t->outfile))
+ {
+ wchar_t title[64] = {0}, prompt[512] = {0}, file[MAX_PATH] = {0};
+ lstrcpyn(file, t->outfile, MAX_PATH);
+ PathStripPath(file);
+ StringCchPrintf(prompt, 512, WASABI_API_LNGSTRINGW(IDS_REMOVE_PARTIAL_FILE), file);
+ if(MessageBox(hwndDlg, prompt,
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSCODING_ABORTED, title, 64), MB_YESNO) == IDYES)
+ {
+ DeleteFile(t->outfile);
+ }
+ }
+ delete t;
+ }
+
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ wchar_t buf[16] = {0};
+ StringCchPrintf(buf, 16, L"%d", rect.left);
+ WritePrivateProfileString(L"transcoder", L"convert_x", buf, inifile);
+ StringCchPrintf(buf, 16, L"%d", rect.top);
+ WritePrivateProfileString(L"transcoder", L"convert_y", buf, inifile);
+
+ EndDialog(hwndDlg,0);
+ itemsDone = 0;
+ totalItems = 0;
+ itemsFailed = 0;
+ transcoderWnd = NULL;
+ transcoding = false;
+ transcoderIdle = false;
+ free(cfs);
+ }
+ break;
+ }
+ return 0;
+}
+
+void startTranscoding()
+{
+ if(transcoding)
+ {
+ totalItems++;
+ SendMessage(transcoderWnd,WM_TRANSCODE_ADD,0,0);
+ }
+ else
+ {
+ transcoding = true;
+ totalItems=1;
+ WASABI_API_CREATEDIALOGW(IDD_TRANSCODE, NULL, transcode_dlgproc);
+ }
+}
+
+void addTrackToTranscodeQueue(const wchar_t * track, unsigned int format, const wchar_t* filepart, const wchar_t* folder)
+{
+ transcodeQueue.Offer(new TranscodeItem(format, folder, filepart, track));
+ startTranscoding();
+}
+
+void addTrackToTranscodeQueue(itemRecordW * track, unsigned int format, const wchar_t* filepart, const wchar_t* folder)
+{
+ transcodeQueue.Offer(new TranscodeItem(format, folder, filepart, track));
+ startTranscoding();
+}
+
+static void fourccToString(unsigned int f, wchar_t * str, int str_len)
+{
+ char s[4] = {(char)(f&0xFF),(char)((f>>8)&0xFF),(char)((f>>16)&0xFF),0};
+ StringCchCopy(str, str_len, AutoWide(s));
+ CharLower(str);
+}
+
+wchar_t* GetDefaultSaveToFolder(wchar_t* path_to_store)
+{
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, path_to_store)))
+ {
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path_to_store)))
+ {
+ // and if that all fails then do a reasonable default
+ lstrcpyn(path_to_store, L"C:\\My Music", MAX_PATH);
+ }
+ // if there's no valid My Music folder (typically win2k) then default to %my_documents%\my music
+ else
+ {
+ PathCombine(path_to_store, path_to_store, L"My Music");
+ }
+ }
+ return path_to_store;
+}
+
+DWORD GetPrivateProfileStringUTF8(LPCSTR lpAppName, LPCSTR lpKeyName, LPCSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCSTR lpFileName)
+{
+ char utf8_text[2048] = {0};
+ GetPrivateProfileStringA(lpAppName,lpKeyName,lpDefault,utf8_text, 2048, lpFileName);
+
+ return MultiByteToWideCharSZ(CP_UTF8, 0, utf8_text, -1, lpReturnedString, nSize);
+}
+
+BOOL WritePrivateProfileStringUTF8(LPCSTR lpAppName, LPCSTR lpKeyName, LPCTSTR lpString, LPCSTR lpFileName)
+{
+ return WritePrivateProfileStringA(lpAppName, lpKeyName, AutoChar(lpString, CP_UTF8), lpFileName);
+}
+
+class EncodableFormat
+{
+public:
+ unsigned int fourcc;
+ wchar_t *desc;
+ EncodableFormat(unsigned int fourcc,wchar_t *desc) :
+ fourcc(fourcc) {this->desc = _wcsdup(desc);}
+ ~EncodableFormat() {free(desc);}
+};
+
+static void enumProc(intptr_t user_data, const char *desc, int fourcc) {
+ ((C_ItemList*)user_data)->Add(new EncodableFormat((unsigned int)fourcc,AutoWide(desc)));
+}
+
+static void BuildEncodableFormatsList(C_ItemList * list, HWND winampWindow,wchar_t * inifile) {
+ converterEnumFmtStruct e = {enumProc,(intptr_t)list};
+ SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS);
+}
+
+unsigned int transcodeGatherSettings(wchar_t *format, wchar_t *folder, int format_len, HWND parent)
+{
+ if(GetPrivateProfileInt(L"transcoder",L"showconf",1,inifile))
+ if(!transcodeConfig(parent))
+ return 0;
+
+ wchar_t tmp[MAX_PATH] = {0};
+ GetPrivateProfileStringUTF8("transcoder", "fileformat","<Artist> - <Album>\\## - <Title>",format,1024,inifileA);
+ GetPrivateProfileStringUTF8("transcoder", "fileroot", AutoChar(GetDefaultSaveToFolder(tmp), CP_UTF8), folder, MAX_PATH, inifileA);
+
+ unsigned int fourcc = GetPrivateProfileInt(L"transcoder",L"format",mmioFOURCC('A','A','C','f'),inifile);
+
+ char extA[8]=".";
+ convertConfigItem c = {fourcc,"extension",&extA[1],7,inifileA};
+ if(!SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM))
+ {
+ // if there was an error, see if it's from an invalid fourcc and try to fallback
+ C_ItemList * formats = new C_ItemList;
+ BuildEncodableFormatsList(formats,plugin.hwndWinampParent,inifile);
+ bool doFail = false;
+
+ for(int i=0; i < formats->GetSize(); i++) {
+ EncodableFormat * f = (EncodableFormat *)formats->Get(i);
+ // if it exists then abort and fail as prior behaviour
+ if(f->fourcc == fourcc)
+ {
+ doFail = true;
+ break;
+ }
+ }
+ if(!doFail)
+ {
+ fourcc = mmioFOURCC('A','A','C','f');
+ c.format = fourcc;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM);
+ }
+ }
+ if(extA[1]) StringCchCat(format, format_len, AutoWide(extA));
+ else
+ {
+ wchar_t ext[8]=L".";
+ fourccToString(fourcc,&ext[1], 8);
+ StringCchCat(format, format_len, ext);
+ }
+ return fourcc;
+}
+
+void transcode( PtrListWCharPtr &filenames, HWND parent )
+{
+ wchar_t format[ 2048 ] = { 0 }, folder[ MAX_PATH ] = { 0 };
+ unsigned int fourcc = transcodeGatherSettings( format, folder, 2048, parent );
+
+ if ( !fourcc )
+ return;
+
+ for ( const wchar_t *l_filename : filenames )
+ addTrackToTranscodeQueue( l_filename, fourcc, format, folder );
+}
+
+void transcode(const itemRecordListW *ice, HWND parent)
+{
+ wchar_t format[2048] = {0}, folder[MAX_PATH] = {0};
+ unsigned int fourcc = transcodeGatherSettings(format, folder, 2048, parent);
+ if(!fourcc) return;
+
+ for(int i=0; i < ice->Size; i++) {
+ addTrackToTranscodeQueue(&ice->Items[i],fourcc,format,folder);
+ }
+}
+
+static void FreeEncodableFormatsList(C_ItemList * list) {
+ int l = list->GetSize();
+ while(--l >= 0) {
+ delete ((EncodableFormat*)list->Get(l));
+ list->Del(l);
+ }
+}
+
+static void doConfigResizeChild(HWND parent, HWND child) {
+ if (child) {
+ RECT r;
+ GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r);
+ ScreenToClient(parent, (LPPOINT)&r);
+ SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ ShowWindow(child, SW_SHOW);
+ }
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ wchar_t buf[4096]=L"";
+ GetDlgItemText((HWND)lpData,IDC_ROOTDIR,buf,sizeof(buf)/sizeof(wchar_t));
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)buf);
+ SetWindowText(hwnd,WASABI_API_LNGSTRINGW(IDS_SELECT_WHERE_TO_SAVE));
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+static BOOL CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static C_ItemList * formats;
+ static convertConfigStruct * ccs;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ bool usefn = !!GetPrivateProfileInt(L"transcoder", L"usefilename",0,inifile);
+ if(GetPrivateProfileInt(L"transcoder", L"showconf", 1, inifile)) CheckDlgButton(hwndDlg,IDC_SHOWEVERY,BST_CHECKED);
+ if(usefn) CheckDlgButton(hwndDlg,IDC_USE_FILENAME,BST_CHECKED);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ROOTDIR),!usefn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE),!usefn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_NAMING),!usefn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORMATHELP),!usefn);
+
+ wchar_t buf[4096]=L"", tmp[MAX_PATH]=L"";
+ GetPrivateProfileStringUTF8("transcoder", "fileformat","<Artist> - <Album>\\## - <Title>",buf,4096,inifileA);
+ SetDlgItemText(hwndDlg,IDC_NAMING,buf);
+
+ GetPrivateProfileStringUTF8("transcoder", "fileroot", AutoChar(GetDefaultSaveToFolder(tmp), CP_UTF8), buf, 4096, inifileA);
+ SetDlgItemText(hwndDlg,IDC_ROOTDIR,buf);
+ formats = new C_ItemList;
+ BuildEncodableFormatsList(formats,plugin.hwndWinampParent,inifile);
+
+ ccs = (convertConfigStruct *)calloc(sizeof(convertConfigStruct),1);
+ ccs->extra_data[6] = mmioFOURCC('I','N','I',' ');
+ ccs->extra_data[7] = (int)inifileA;
+ ccs->hwndParent = hwndDlg;
+ ccs->format = GetPrivateProfileInt(L"transcoder",L"format",mmioFOURCC('A','A','C','f'),inifile);
+
+ for(int i=0; i < formats->GetSize(); i++) {
+ EncodableFormat * f = (EncodableFormat *)formats->Get(i);
+ int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc);
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f);
+
+ if(f->fourcc == ccs->format)
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0);
+ }
+
+ // if there is no selection then force things to the correct default
+ if(SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, (WPARAM)0, 0) == CB_ERR) {
+ for(int i=0; i < SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCOUNT, 0, 0); i++) {
+ EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, (WPARAM)i, 0);
+ if(f->fourcc == mmioFOURCC('A','A','C','f')) {
+ ccs->format = f->fourcc;
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)i, 0);
+ break;
+ }
+ }
+ }
+
+ HWND h = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+
+ // show config window and restore last position as applicable
+ POINT pt = {GetPrivateProfileInt(L"transcoder", L"showconf_x", -1, inifile),
+ GetPrivateProfileInt(L"transcoder", L"showconf_y", -1, inifile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG_END);
+ free(ccs); ccs=0;
+ FreeEncodableFormatsList(formats);
+ delete formats; formats=0;
+ }
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_USE_FILENAME:
+ {
+ bool usefn = IsDlgButtonChecked(hwndDlg,IDC_USE_FILENAME)!=0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ROOTDIR),!usefn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE),!usefn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_NAMING),!usefn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORMATHELP),!usefn);
+ }
+ break;
+
+ case IDC_ENCFORMAT:
+ if (HIWORD(wParam) != CBN_SELCHANGE) return 0;
+ {
+ int sel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
+ if (sel != CB_ERR)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG_END);
+ EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0);
+ ccs->format = f->fourcc;
+ HWND h = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ }
+ }
+ break;
+
+ case IDC_FORMATHELP:
+ {
+ wchar_t titleStr[64] = {0};
+ MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_FILENAME_FORMAT_HELP),
+ WASABI_API_LNGSTRINGW_BUF(IDS_FILENAME_FORMAT_HELP_TITLE,titleStr,64), MB_OK);
+ break;
+ }
+
+ case IDC_BROWSE:
+ {
+ BROWSEINFO bi = {0};
+ LPMALLOC lpm = 0;
+ wchar_t bffFileName[MAX_PATH] = {0};
+ bi.hwndOwner = hwndDlg;
+ bi.pszDisplayName = bffFileName;
+ bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_FOLDER);
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ bi.lParam = (LPARAM)hwndDlg;
+ LPITEMIDLIST iil = SHBrowseForFolder(&bi);
+ if(iil)
+ {
+ SHGetPathFromIDList(iil,bffFileName);
+ SHGetMalloc(&lpm);
+ lpm->Free(iil);
+ SetDlgItemText(hwndDlg, IDC_ROOTDIR, bffFileName);
+ }
+ }
+ break;
+
+ case IDOK:
+ case IDCANCEL:
+ {
+ DWORD fourcc=0;
+ if (LOWORD(wParam) == IDOK)
+ {
+ wchar_t buf[4096]=L"";
+ GetDlgItemText(hwndDlg,IDC_NAMING,buf,sizeof(buf)/sizeof(wchar_t));
+ WritePrivateProfileStringUTF8("transcoder","fileformat",buf,inifileA);
+ GetDlgItemText(hwndDlg,IDC_ROOTDIR,buf,sizeof(buf)/sizeof(wchar_t));
+ WritePrivateProfileStringUTF8("transcoder","fileroot",buf,inifileA);
+ WritePrivateProfileString(L"transcoder",L"showconf",IsDlgButtonChecked(hwndDlg,IDC_SHOWEVERY)?L"1":L"0",inifile);
+ WritePrivateProfileString(L"transcoder",L"usefilename",IsDlgButtonChecked(hwndDlg,IDC_USE_FILENAME)?L"1":L"0",inifile);
+
+ LRESULT esel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
+ if (esel!=CB_ERR)
+ {
+ LRESULT data = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, esel, 0);
+ if (data != CB_ERR)
+ {
+ EncodableFormat * f = (EncodableFormat *)data;
+ StringCchPrintf(buf, 4096, L"%d", f->fourcc);
+ WritePrivateProfileString(L"transcoder",L"format",buf,inifile);
+ fourcc=f->fourcc;
+ }
+ }
+ }
+
+ EndDialog(hwndDlg, (LOWORD(wParam) ? fourcc : 0));
+
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ wchar_t buf[16] = {0};
+ StringCchPrintf(buf, 16, L"%d", rect.left);
+ WritePrivateProfileString(L"transcoder", L"showconf_x", buf, inifile);
+ StringCchPrintf(buf, 16, L"%d", rect.top);
+ WritePrivateProfileString(L"transcoder", L"showconf_y", buf, inifile);
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+int transcodeConfig(HWND parent) { // returns fourcc
+ return WASABI_API_DIALOGBOXW(IDD_TRANSCODE_CONFIG, parent, config_dlgproc);
+}
+// metadata shit
+
+extern wchar_t *guessTitles(const wchar_t *filename, int *tracknum,wchar_t **artist, wchar_t **album,wchar_t **title);
+
+#define atoi_NULLOK(s) ((s)?_wtoi(s):0)
+
+void filenameToItemRecord(const wchar_t *file, itemRecordW * ice)
+{
+ int gtrack=0;
+ wchar_t *gartist=NULL,*galbum=NULL,*gtitle=NULL;
+ wchar_t *guessbuf = guessTitles(file,&gtrack,&gartist,&galbum,&gtitle);
+ if(!gartist) gartist=L"";
+ if(!galbum) galbum=L"";
+ if(!gtitle) gtitle=L"";
+
+ wchar_t buf[512] = {0};
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"title", buf, 512);
+ if(buf[0]) { ice->title=_wcsdup(buf); gartist=L""; galbum=L""; gtrack=-1;}
+ else ice->title=_wcsdup(gtitle);
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"album", buf, 512);
+ if(buf[0]) ice->album=_wcsdup(buf);
+ else ice->album=_wcsdup(galbum);
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"artist", buf, 512);
+ if(buf[0]) ice->artist=_wcsdup(buf);
+ else ice->artist=_wcsdup(gartist);
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"albumartist", buf, 512);
+ if(buf[0]) ice->albumartist=_wcsdup(buf);
+ else ice->albumartist=_wcsdup(ice->artist);
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"track", buf, 512);
+ if(buf[0]) ice->track=atoi_NULLOK(buf);
+ else ice->track=gtrack;
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"genre", buf, 512);
+ ice->genre=_wcsdup(buf);
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"comment", buf, 512);
+ ice->comment=_wcsdup(buf);
+
+ buf[0]=0;
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"year", buf, 512);
+ ice->year=atoi_NULLOK(buf);
+
+ basicFileInfoStructW b={0};
+ b.filename=const_cast<wchar_t *>(file); //benski> changed extendedFileInfoStruct but not basicFileInfoStruct, i'll have to do that later so we can get rid of this cast
+ b.quickCheck=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
+ ice->length=b.length;
+
+ ice->filename = _wcsdup(file);
+ free(guessbuf);
+}
+
+void copyTags(const itemRecordW *in, const wchar_t *out)
+{
+ // check if the old file still exists - if it does, we will let Winamp copy metadata for us
+ if (wcscmp(in->filename, out) && PathFileExists(in->filename))
+ {
+ copyFileInfoStructW copy;
+ copy.dest = out;
+ copy.source = in->filename;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&copy, IPC_COPY_EXTENDED_FILE_INFOW) == 0) // 0 indicates success here
+ {
+ AGAVE_API_ALBUMART->CopyAlbumArt(in->filename, out);
+ return;
+ }
+ }
+
+ wchar_t buf[32] = {0}, file[MAX_PATH] = {0};
+ StringCchCopy(file, MAX_PATH, out);
+ extendedFileInfoStructW e = {0};
+ e.filename=file;
+
+ e.metadata=L"album";
+ e.ret=in->album;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ e.metadata=L"artist";
+ e.ret=in->artist;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ e.metadata=L"albumartist";
+ e.ret=in->albumartist;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ e.metadata=L"title";
+ e.ret=in->title;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ e.metadata=L"track";
+ StringCchPrintf(buf, 32, L"%d", in->track);
+ e.ret=buf;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ e.metadata=L"genre";
+ e.ret=in->genre;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ e.metadata=L"comment";
+ e.ret=in->comment;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+
+ if(in->year > 0)
+ {
+ e.metadata=L"year";
+ StringCchPrintf(buf, 32, L"%d", in->year);
+ e.ret=buf;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW);
+ }
+
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_WRITE_EXTENDED_FILE_INFO);
+}
+
+extern "C" {
+ __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin() {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // prompt to remove our settings with default as no (just incase)
+ wchar_t title[256] = {0};
+ StringCchPrintf(title, 256, WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FORMAT_CONVERTER), PLUGIN_VERSION);
+ if(MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ title,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ DeleteFile(inifile);
+ }
+ // if not transcoding then can remove on the fly (5.37+)
+ if(!IsWindow(transcoderWnd)){
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+ // otherwise allow for any prompting/full restart removal (default)
+ return ML_PLUGIN_UNINSTALL_REBOOT;
+ }
+};
+
+static HWND GetDialogBoxParent()
+{
+ HWND parent = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT);
+ if (!parent || parent == (HWND)1)
+ return plugin.hwndWinampParent;
+ return parent;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/ml_transcode.rc b/Src/Plugins/Library/ml_transcode/ml_transcode.rc
new file mode 100644
index 00000000..f18bd888
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/ml_transcode.rc
@@ -0,0 +1,162 @@
+// 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_TRANSCODE DIALOGEX 0, 0, 186, 90
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | DS_CENTERMOUSE | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Converting..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "",IDC_CURRENTTRACK,7,7,172,8
+ CONTROL "",IDC_TRACKPROGRESS,"msctls_progress32",WS_BORDER,7,18,172,14
+ LTEXT "",IDC_TOTALCAPTION,7,38,172,8
+ CONTROL "",IDC_TOTALPROGRESS,"msctls_progress32",WS_BORDER,7,49,172,14
+ PUSHBUTTON "Abort",IDC_ABORT,67,70,50,13
+END
+
+IDD_TRANSCODE_CONFIG DIALOGEX 0, 0, 271, 319
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Format Converter Configuration"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Output File Naming Scheme",IDC_STATIC,7,4,256,80
+ LTEXT "Specify the destination folder to store your converted music:",IDC_STATIC,13,15,231,8,0,WS_EX_TRANSPARENT
+ EDITTEXT IDC_ROOTDIR,14,26,192,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BROWSE,207,26,50,13
+ LTEXT "Specify the naming scheme:",IDC_STATIC,13,42,198,8,0,WS_EX_TRANSPARENT
+ EDITTEXT IDC_NAMING,14,52,192,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Format Help",IDC_FORMATHELP,207,52,50,13
+ CONTROL "Use old file name and path (just change the extension)",IDC_USE_FILENAME,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,70,219,10
+ GROUPBOX "Encoding Format",IDC_STATIC,7,87,256,40
+ LTEXT "Select the format you wish to convert to:",IDC_STATIC,13,97,135,8
+ COMBOBOX IDC_ENCFORMAT,14,108,243,116,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "",IDC_ENC_CONFIG,7,129,256,169,NOT WS_VISIBLE
+ CONTROL "Show this before each convert",IDC_SHOWEVERY,"Button",BS_AUTOCHECKBOX | BS_VCENTER | WS_TABSTOP,7,304,140,9
+ DEFPUSHBUTTON "Ok",IDOK,159,302,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,213,302,50,13
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_TRANSCODE, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 83
+ END
+
+ IDD_TRANSCODE_CONFIG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 263
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 315
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_FORMAT_CONVERTER "Nullsoft Format Converter v%s"
+ 65535 "{699B8BA5-B292-4aba-8047-D46B0DF4E1D6}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_FORMAT_CONVERTER "Format Converter"
+ IDS_DONE "Done"
+ IDS_TRANSCODING_FAILED "Transcoding Failed"
+ IDS_ABORT "Abort"
+ IDS_TRACKS_DONE_REMAINING_FAILED
+ "%d tracks done, %d tracks remaining, %d failed"
+ IDS_FILENAME_FORMAT_HELP
+ "You may enter a filename format string for your copied files.\nIt can contain \\ or / to delimit a path, and the following keywords:\n\n<Artist> - inserts the artist with the default capitalization\n<ARTIST> - inserts the artist in all uppercase\n<artist> - inserts the artist in all lowercase\n<Albumartist>/<ALBUMARTIST>/<albumartist> - inserts the album artist\n<Album>/<ALBUM>/<album> - inserts the album\n<year> - inserts the album year\n<Genre>/<GENRE>/<genre> - inserts the album genre\n<Title>/<TITLE>/<title> - inserts the track title\n#, ##, ### - inserts the track number, with leading 0s if ## or ###\n<filename> - uses the old filename of the file (not the full path)\n<disc> - inserts the disc number\n<discs> - inserts the total discs number (e.g. CD<disc> of <discs>)"
+ IDS_FILENAME_FORMAT_HELP_TITLE "Filename format help"
+ IDS_CHOOSE_FOLDER "Choose a folder or create a new one"
+ IDS_SELECT_WHERE_TO_SAVE "Choose where to save converted files"
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings file for this plug-in?"
+ IDS_TRANSCODING_ABORTED "Transcoding Aborted"
+ IDS_REMOVE_PARTIAL_FILE "Do you want to remove the partially transcoded file\n '%s' or keep it?\n\n(Note: it is likely to be invalid and not play correctly)"
+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_transcode/ml_transcode.sln b/Src/Plugins/Library/ml_transcode/ml_transcode.sln
new file mode 100644
index 00000000..6559eb11
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/ml_transcode.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29709.97
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_transcode", "ml_transcode.vcxproj", "{B9933E53-426C-4603-BE06-672059143A27}"
+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
+ {B9933E53-426C-4603-BE06-672059143A27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {B9933E53-426C-4603-BE06-672059143A27}.Debug|Win32.Build.0 = Debug|Win32
+ {B9933E53-426C-4603-BE06-672059143A27}.Debug|x64.ActiveCfg = Debug|x64
+ {B9933E53-426C-4603-BE06-672059143A27}.Debug|x64.Build.0 = Debug|x64
+ {B9933E53-426C-4603-BE06-672059143A27}.Release|Win32.ActiveCfg = Release|Win32
+ {B9933E53-426C-4603-BE06-672059143A27}.Release|Win32.Build.0 = Release|Win32
+ {B9933E53-426C-4603-BE06-672059143A27}.Release|x64.ActiveCfg = Release|x64
+ {B9933E53-426C-4603-BE06-672059143A27}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B5FE8422-686F-47D5-A186-D3E42F8236F4}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj b/Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj
new file mode 100644
index 00000000..e41d528d
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj
@@ -0,0 +1,333 @@
+<?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>{B9933E53-426C-4603-BE06-672059143A27}</ProjectGuid>
+ <RootNamespace>ml_transcode</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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <SubSystem>Windows</SubSystem>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4302;4311;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <SubSystem>Windows</SubSystem>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4302;4311;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\itemlist.h" />
+ <ClInclude Include="..\..\General\gen_ml\ml.h" />
+ <ClInclude Include="..\..\..\nu\AutoChar.h" />
+ <ClInclude Include="..\..\..\nu\AutoWide.h" />
+ <ClInclude Include="..\..\..\Winamp\wa_ipc.h" />
+ <ClInclude Include="api__ml_transcode.h" />
+ <ClInclude Include="LinkedQueue.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\itemlist.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\ml_local\guess.cpp" />
+ <ClCompile Include="LinkedQueue.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="replaceVars.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_transcode.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_transcode/ml_transcode.vcxproj.filters b/Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj.filters
new file mode 100644
index 00000000..a8481226
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/ml_transcode.vcxproj.filters
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="..\ml_local\guess.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LinkedQueue.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="replaceVars.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\itemlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_transcode.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LinkedQueue.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\AutoChar.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\AutoWide.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\itemlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\ml.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\wa_ipc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{dad8753a-63ad-4940-b004-ee080d154ba8}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{9eded93f-4358-4590-b654-608fc1d13764}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{56b6e7a2-f5f3-4b8d-a042-b3bf2cd8b7dd}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_transcode.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/replaceVars.cpp b/Src/Plugins/Library/ml_transcode/replaceVars.cpp
new file mode 100644
index 00000000..6a6ce53b
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/replaceVars.cpp
@@ -0,0 +1,317 @@
+#include <windows.h>
+#include <ctype.h>
+#include <string.h>
+#include "..\..\General\gen_ml/ml.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+static void removebadchars(wchar_t *s, wchar_t *&end, size_t &str_size)
+{
+ const wchar_t *start = s;
+ while (s && *s)
+ {
+ if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|')
+ *s = L'_';
+ s = CharNextW(s);
+ }
+
+ // cut off trailing periods
+ if (s != start)
+ {
+ do
+ {
+ s--;
+ if (*s == '.')
+ {
+ *s = '_';
+ end--;
+ str_size++;
+ }
+ else
+ break;
+ }
+ while (s != start);
+ }
+}
+
+void RecursiveCreateDirectory(wchar_t *str)
+{
+ wchar_t *p = str;
+ if ((p[0] ==L'\\' || p[0] ==L'/') && (p[1] ==L'\\' || p[1] ==L'/'))
+ {
+ p += 2;
+ while (p &&*p && *p !=L'\\' && *p !=L'/') p++;
+ if (!p || !*p) return ;
+ p++;
+ while (p && *p && *p !=L'\\' && *p !=L'/') p++;
+ }
+ else
+ {
+ while (p && *p && *p !=L'\\' && *p !=L'/') p++;
+ }
+
+ while (p && *p)
+ {
+ while (p && *p && *p !=L'\\' && *p !=L'/') p = CharNextW(p);
+ if (p && *p)
+ {
+ wchar_t lp = *p;
+ *p = 0;
+ CreateDirectoryW(str, NULL);
+ *p++ = lp;
+ }
+ }
+}
+
+void FixFileLength(wchar_t *str)
+{
+ size_t len = wcslen(str);
+ if (len >= MAX_PATH) // no good
+ {
+ size_t extensionPosition=len;
+ while (extensionPosition--)
+ {
+ if (str[extensionPosition]=='.' || str[extensionPosition] == '/' || str[extensionPosition] == '\\')
+ break;
+ }
+ if (str[extensionPosition]=='.') // found an extension? good, let's relocate it
+ {
+ ptrdiff_t diff = len - extensionPosition;
+ diff++;
+ while (diff)
+ {
+ str[MAX_PATH-diff]=str[len-diff+1];
+ diff--;
+ }
+ }
+ str[MAX_PATH-1]=0;
+ }
+}
+
+static void ADD_STR(const wchar_t *x, wchar_t *&outp, size_t &str_size)
+{
+ wchar_t *end = outp;
+ StringCchCopyExW(outp, str_size, (x) ? (x) : L"", &end, &str_size, 0);
+ removebadchars(outp, end, str_size);
+ outp = end;
+}
+
+static void ADD_STR_U(const wchar_t *x, wchar_t *&outp, size_t &str_size)
+{
+ wchar_t *end = outp;
+ StringCchCopyExW(outp, str_size, (x) ? (x) : L"", &end, &str_size, 0);
+ removebadchars(outp, end, str_size);
+ CharUpperW(outp);
+ outp = end;
+}
+
+static void ADD_STR_L(const wchar_t *x, wchar_t *&outp, size_t &str_size)
+{
+ wchar_t *end = outp;
+ StringCchCopyExW(outp, str_size, (x)?(x):L"", &end, &str_size,0);
+ removebadchars(outp, end, str_size);
+ CharLowerW(outp);
+ outp=end;
+}
+
+// FixReplacementVars: replaces <Artist>, <Title>, <Album>, and #, ##, ##, with appropriate data
+wchar_t *FixReplacementVars(wchar_t *str, size_t str_size, itemRecordW * song)
+{
+ wchar_t tmpsrc[4096] = {0};
+ lstrcpyn(tmpsrc,str,sizeof(tmpsrc)/sizeof(wchar_t)); //lstrcpyn is nice enough to make sure it's null terminated.
+
+ wchar_t *inp = tmpsrc;
+ wchar_t *outp = str;
+
+ while (inp && *inp && str_size)
+ {
+ if (*inp == L'<')
+ {
+ if (!wcsncmp(inp,L"<TITLE>",7))
+ {
+ ADD_STR_U(song->title, outp, str_size);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<title>",7))
+ {
+ ADD_STR_L(song->title, outp, str_size);
+ inp+=7;
+ }
+ else if (!_wcsnicmp(inp,L"<Title>",7))
+ {
+ ADD_STR(song->title, outp, str_size);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<ALBUM>",7))
+ {
+ ADD_STR_U(song->album, outp, str_size);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<album>",7))
+ {
+ ADD_STR_L(song->album, outp, str_size);
+ inp+=7;
+ }
+ else if (!_wcsnicmp(inp,L"<Album>",7))
+ {
+ ADD_STR(song->album, outp, str_size);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<GENRE>",7))
+ {
+ ADD_STR_U(song->genre, outp, str_size);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<genre>",7))
+ {
+ ADD_STR_L(song->genre, outp, str_size);
+ inp+=7;
+ }
+ else if (!_wcsnicmp(inp,L"<Genre>",7))
+ {
+ ADD_STR(song->genre, outp, str_size);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<ARTIST>",8))
+ {
+ ADD_STR_U(song->artist, outp, str_size);
+ inp+=8;
+ }
+ else if (!wcsncmp(inp,L"<artist>",8))
+ {
+ ADD_STR_L(song->artist, outp, str_size);
+ inp+=8;
+ }
+ else if (!_wcsnicmp(inp,L"<Artist>",8))
+ {
+ ADD_STR(song->artist, outp, str_size);
+ inp+=8;
+ }
+ else if (!wcsncmp(inp,L"<ALBUMARTIST>",13))
+ {
+ if (song->albumartist && song->albumartist[0])
+ ADD_STR_U(song->albumartist, outp, str_size);
+ else
+ ADD_STR_U(song->artist, outp, str_size);
+ inp+=13;
+ }
+ else if (!wcsncmp(inp,L"<albumartist>",13))
+ {
+ if (song->albumartist && song->albumartist[0])
+ ADD_STR_L(song->albumartist, outp, str_size);
+ else
+ ADD_STR_L(song->artist, outp, str_size);
+ inp+=13;
+ }
+ else if (!_wcsnicmp(inp,L"<Albumartist>",13))
+ {
+ if (song->albumartist && song->albumartist[0])
+ ADD_STR(song->albumartist, outp, str_size);
+ else
+ ADD_STR(song->artist, outp, str_size);
+ inp+=13;
+ }
+ else if (!_wcsnicmp(inp,L"<year>",6))
+ {
+ wchar_t year[64] = {0};
+ if(song->year==0) year[0]=0;
+ else StringCchPrintf(year, 64, L"%d", song->year);
+ ADD_STR(year, outp, str_size);
+ inp+=6;
+ }
+ else if (!_wcsnicmp(inp,L"<disc>",6))
+ {
+ wchar_t disc[16] = {0};
+ if(song->disc==0) disc[0]=0;
+ else StringCchPrintf(disc, 16, L"%d", song->disc);
+ ADD_STR(disc, outp, str_size);
+ inp+=6;
+ }
+ else if (!_wcsnicmp(inp,L"<discs>",7))
+ {
+ wchar_t discs[32] = {0};
+ if(song->disc==0) discs[0]=0;
+ else StringCchPrintf(discs, 32, L"%d", song->discs);
+ ADD_STR(discs, outp, str_size);
+ inp+=7;
+ }
+ else if(!_wcsnicmp(inp,L"<filename>",10))
+ {
+ wchar_t *fn = _wcsdup(PathFindFileName(song->filename));
+ const wchar_t *insert;
+
+ if (NULL != fn &&
+ FALSE != PathIsFileSpec(fn))
+ {
+ PathRemoveExtension(fn);
+ insert = fn;
+ }
+ else
+ insert = NULL;
+
+ if (NULL == insert || L'\0' == *insert)
+ insert = L"<filename>";
+
+ ADD_STR(insert, outp, str_size);
+
+ free(fn);
+ inp+=10;
+ }
+ else *outp++=*inp++;
+ }
+ else if (*inp == '#')
+ {
+ int nd = 0;
+ wchar_t tmp[64] = {0};
+ while (inp && *inp =='#') nd++,inp++;
+
+ if (!song->track)
+ {
+ tmp[0] = 0;
+ while (inp && *inp == ' ') inp++;
+ if (inp && (*inp == '-' || *inp == '.' || *inp == '_')) // separator
+ {
+ inp++;
+ while (inp && *inp == ' ') inp++;
+ }
+ }
+ else
+ {
+ if (nd > 1)
+ {
+ wchar_t tmp2[32] = {0};
+ if (nd > 5) nd=5;
+ StringCchPrintf(tmp2, 32, L"%%%02dd",nd);
+ StringCchPrintf(tmp, 64, tmp2,song->track);
+ }
+ else StringCchPrintf(tmp, 64, L"%d", song->track);
+ }
+ ADD_STR(tmp, outp, str_size);
+ }
+ else *outp++ = *inp++;
+ }
+ if (outp) *outp = 0;
+
+ inp = str;
+ outp = str;
+ wchar_t lastc=0;
+ while (inp && *inp)
+ {
+ wchar_t c=*inp++;
+ if (c == '\t') c=' ';
+
+ if (c == ' ' && (lastc == ' ' || lastc == '\\' || lastc == '/')) continue; // ignore space after slash, or another space
+
+ if ((c == '\\' || c == '/') && lastc == ' ') outp--; // if we have a space then slash, back up to write the slash where the space was
+ *outp++ = c;
+ lastc = c;
+ }
+ if (outp) *outp=0;
+
+ #undef ADD_STR
+ #undef ADD_STR_L
+ #undef ADD_STR_U
+
+ return(str);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/resource.h b/Src/Plugins/Library/ml_transcode/resource.h
new file mode 100644
index 00000000..1fac1dd8
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/resource.h
@@ -0,0 +1,45 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_transcode.rc
+//
+#define IDS_FORMAT_CONVERTER 0
+#define IDS_DONE 1
+#define IDS_TRANSCODING_FAILED 2
+#define IDS_ABORT 3
+#define IDCANCEL2 4
+#define IDS_TRACKS_DONE_REMAINING_FAILED 4
+#define IDS_FILENAME_FORMAT_HELP 5
+#define IDS_FILENAME_FORMAT_HELP_TITLE 6
+#define IDS_CHOOSE_FOLDER 7
+#define IDS_SELECT_WHERE_TO_SAVE 9
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 10
+#define IDS_TRANSCODING_ABORTED 11
+#define IDS_STRING12 12
+#define IDS_REMOVE_PARTIAL_FILE 12
+#define IDD_TRANSCODE 101
+#define IDD_TRANSCODE_CONFIG 102
+#define IDC_TRACKPROGRESS 1001
+#define IDC_CURRENTTRACK 1002
+#define IDC_TOTALPROGRESS 1003
+#define IDC_TOTALCAPTION 1004
+#define IDC_ABORT 1005
+#define IDC_USE_FILENAME 1011
+#define IDC_ROOTDIR 1014
+#define IDC_BROWSE 1015
+#define IDC_NAMING 1016
+#define IDC_FORMATHELP 1017
+#define IDC_ENC_CONFIG 1018
+#define IDC_SHOWEVERY 1019
+#define IDC_ENCFORMAT 1020
+#define IDS_NULLSOFT_FORMAT_CONVERTER 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 106
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1006
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_transcode/reversesync.h b/Src/Plugins/Library/ml_transcode/reversesync.h
new file mode 100644
index 00000000..b1363658
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/reversesync.h
@@ -0,0 +1,9 @@
+#ifndef _REVERSESYNC_H
+#define _REVERSESYNC_H
+
+WIN32_FIND_DATA *File_Exists(char *buf);
+char *Skip_Root(char *path);
+BOOL RecursiveCreateDirectory(char* buf1);
+char *FixReplacementVars(char *str, int str_size, itemRecord * song);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_transcode/version.rc2 b/Src/Plugins/Library/ml_transcode/version.rc2
new file mode 100644
index 00000000..e0d322b9
--- /dev/null
+++ b/Src/Plugins/Library/ml_transcode/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,79,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", "2,79,0,0"
+ VALUE "InternalName", "Nullsoft Format Convertor"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_transcode.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_webdev/commands.cpp b/Src/Plugins/Library/ml_webdev/commands.cpp
new file mode 100644
index 00000000..241017c9
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/commands.cpp
@@ -0,0 +1,328 @@
+#include "main.h"
+#include "./commands.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./navigation.h"
+#include "./import.h"
+#include "./serviceHelper.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#include <ifc_omservice.h>
+#include <browserView.h>
+#include <strsafe.h>
+
+HRESULT Command_NavigateService(ifc_omservice *service, LPCWSTR pszUrl, BOOL fActiveOnly)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ ifc_omservice *activeService;
+ HWND hView = navigation->GetActiveView(&activeService);
+ if (NULL == hView || activeService->GetId() != service->GetId())
+ hView = NULL;
+
+ if (NULL != activeService)
+ activeService->Release();
+
+ HRESULT hr = S_OK;
+
+ if (NULL != hView)
+ {
+ if (FALSE == BrowserView_Navigate(hView, pszUrl, TRUE))
+ hr = E_FAIL;
+ }
+ else
+ {
+ hr = (FALSE == fActiveOnly) ?
+ navigation->ShowService(service->GetId(), pszUrl) : E_NOTIMPL;
+ }
+
+ navigation->Release();
+ return hr;
+}
+static void CALLBACK ThreadCallback_NavigateService(Dispatchable *object, ULONG_PTR param1, ULONG_PTR param2)
+{
+ ifc_omservice *service = (ifc_omservice*)object;
+ LPWSTR pszUrl = (LPWSTR)param1;
+
+ Command_NavigateService(service, pszUrl, (BOOL)param2);
+
+ Plugin_FreeResString(pszUrl);
+}
+
+HRESULT Command_PostNavigateSvc(ifc_omservice *service, LPCWSTR pszUrl, BOOL fActiveOnly)
+{
+ if (NULL == OMUTILITY)
+ return E_FAIL;
+
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ LPWSTR pszUrlCopy = Plugin_DuplicateResString(pszUrl);
+ HRESULT hr = OMUTILITY->PostMainThreadCallback2(ThreadCallback_NavigateService, service, (ULONG_PTR)pszUrlCopy, fActiveOnly);
+ if (FAILED(hr))
+ {
+ Plugin_FreeResString(pszUrlCopy);
+ }
+
+ return hr;
+
+}
+
+HRESULT Command_EditService(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ WCHAR szBuffer[2048];
+
+ HRESULT hr = Plugin_MakeResourcePath(szBuffer, ARRAYSIZE(szBuffer), RT_HTML, MAKEINTRESOURCE(IDR_HTML_EDITOR), RESPATH_TARGETIE | RESPATH_COMPACT);
+ if (FAILED(hr)) return hr;
+
+ INT cchUrl = lstrlen(szBuffer);
+ LPWSTR pszParam = szBuffer + cchUrl;
+ INT cchParamMax = ARRAYSIZE(szBuffer) - cchUrl;
+ hr = StringCchPrintf(pszParam, cchParamMax, L"?serviceId=%u", service->GetId());
+ if (FAILED(hr)) return hr;
+
+ return Command_NavigateService(service, szBuffer, FALSE);
+}
+
+HRESULT Command_ReloadService(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ HRESULT hr = ServiceHelper_Reload(service);
+ return hr;
+}
+
+HRESULT Command_ResetPermissions(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == AGAVE_API_JSAPI2_SECURITY)
+ return E_UNEXPECTED;
+
+ WCHAR szBuffer[64] = {0};
+ if (FAILED(StringCchPrintfW(szBuffer, ARRAYSIZE(szBuffer), L"%u", service->GetId())))
+ return E_FAIL;
+
+ AGAVE_API_JSAPI2_SECURITY->ResetAuthorization(szBuffer);
+ return S_OK;
+}
+
+HRESULT Command_LocateService(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ WCHAR szPath[512];
+
+ HRESULT hr = service->GetAddress(szPath, ARRAYSIZE(szPath));
+ if (FAILED(hr)) return hr;
+
+ if (L'\0' == szPath[0])
+ return E_FAIL;
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ navigation->Release();
+
+ if (WASABI_API_EXPLORERFINDFILE)
+ {
+ WASABI_API_EXPLORERFINDFILE->AddFile(szPath);
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+ return E_UNEXPECTED;
+}
+
+HRESULT Command_EditServiceExternal(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ WCHAR szPath[512] = {0};
+
+ HRESULT hr = service->GetAddress(szPath, ARRAYSIZE(szPath));
+ if (FAILED(hr)) return hr;
+
+ if (L'\0' == szPath[0])
+ return E_FAIL;
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_UNEXPECTED;
+
+ HWND hOwner = navigation->GetActiveView(NULL);
+ navigation->Release();
+
+ if (NULL == hOwner)
+ hOwner = Plugin_GetLibrary();
+
+ HINSTANCE hInst = ShellExecute(hOwner, L"open", szPath, NULL, NULL, SW_SHOWNORMAL);
+ hr = ((INT_PTR)hInst > 32) ? S_OK : E_FAIL;
+ return hr;
+}
+
+HRESULT Command_DeleteItem(HNAVITEM hItem)
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ hr = navigation->DeleteItem(hItem);
+ navigation->Release();
+ }
+
+ return hr;
+}
+
+HRESULT Command_DeleteAll()
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ hr = navigation->DeleteAll();
+ navigation->Release();
+ }
+
+ return hr;
+}
+
+HRESULT Command_CreateService()
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (FAILED(hr)) return hr;
+
+ HNAVITEM hItem;
+ hr = navigation->CreateUserService(&hItem);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ hr= navigation->GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ Command_EditService(service);
+ service->Release();
+ }
+ }
+ navigation->Release();
+ return hr;
+}
+
+HRESULT Command_NewWindow(HNAVITEM hItem)
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ HWND hWindow;
+ HRESULT hr = navigation->CreatePopup(hItem, &hWindow);
+ if (SUCCEEDED(hr))
+ {
+ ShowWindow(hWindow, SW_SHOWNORMAL);
+ }
+ navigation->Release();
+ }
+ return hr;
+}
+
+HRESULT Command_OpenView(HNAVITEM hItem)
+{
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ hr = navigation->SelectItem(hItem, NULL);
+ navigation->Release();
+ }
+
+ return hr;
+}
+
+void Command_ImportFiles()
+{
+ HWND hOwner = Plugin_GetDialogOwner();
+ ImportService_FromFile(hOwner);
+}
+
+void Command_ImportUrl()
+{
+ HWND hOwner = Plugin_GetDialogOwner();
+ ImportService_FromUrl(hOwner);
+}
+
+static void CALLBACK BrowserOptions_Callback(HWND hOptions, UINT type, ULONG_PTR user)
+{
+ HWND hLibrary = (HWND)user;
+ switch(type)
+ {
+ case BOCALLBACK_INIT:
+ {
+ HWND hView = (HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL != hView)
+ {
+ RECT viewRect, optionsRect;
+ if (GetWindowRect(hView, &viewRect) && GetWindowRect(hOptions, &optionsRect))
+ {
+ INT x = viewRect.left + ((viewRect.right - viewRect.left) - (optionsRect.right - optionsRect.left))/2;
+ INT y = viewRect.top + ((viewRect.bottom - viewRect.top) - (optionsRect.bottom - optionsRect.top))/2;
+ SetWindowPos(hOptions, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
+ SendMessage(hOptions, DM_REPOSITION, 0, 0L);
+ }
+ }
+ }
+ break;
+ }
+}
+
+HRESULT Command_ShowBrowserOptions()
+{
+ HWND hWinamp = Plugin_GetWinamp();
+ if (NULL == hWinamp || NULL == OMBROWSERMNGR)
+ return E_UNEXPECTED;
+
+ HRESULT hr = OMBROWSERMNGR->Initialize(NULL, hWinamp);
+ if (SUCCEEDED(hr))
+ {
+ HWND hOwner = Plugin_GetDialogOwner();
+ hr = OMBROWSERMNGR->ShowOptions(hOwner, BOSTYLE_NORMAL | BOSTYLE_SHOWDEBUG,
+ BrowserOptions_Callback, (ULONG_PTR)hOwner);
+ }
+ return hr;
+}
+
+BOOL CommandManager_Process(HNAVITEM hItem, ifc_omservice *service, UINT commandId)
+{
+ switch(commandId)
+ {
+ case ID_VIEW_OPEN: Command_OpenView(hItem); return TRUE;
+ case ID_VIEW_NEWWINDOW: Command_NewWindow(hItem); return TRUE;
+ case ID_SERVICE_NEW: Command_CreateService(); return TRUE;
+ case ID_SERVICE_DELETE: Command_DeleteItem(hItem); return TRUE;
+ case ID_SERVICE_DELETEALL: Command_DeleteAll(); return TRUE;
+ case ID_SERVICE_EDIT: Command_EditService(service); return TRUE;
+ case ID_SERVICE_LOCATE: Command_LocateService(service); return TRUE;
+ case ID_SERVICE_EDITEXTERNAL: Command_EditServiceExternal(service); return TRUE;
+ case ID_SERVICE_RELOAD: Command_ReloadService(service); return TRUE;
+ case ID_SERVICE_RESETPERMISSIONS: Command_ResetPermissions(service); return TRUE;
+ case ID_NAVIGATION_BACK: Command_NavigateService(service, NAVIGATE_BACK, TRUE); return TRUE;
+ case ID_NAVIGATION_FORWARD: Command_NavigateService(service, NAVIGATE_FORWARD, TRUE); return TRUE;
+ case ID_NAVIGATION_HOME: Command_NavigateService(service, NAVIGATE_HOME, TRUE); return TRUE;
+ case ID_NAVIGATION_STOP: Command_NavigateService(service, NAVIGATE_STOP, TRUE); return TRUE;
+ case ID_NAVIGATION_REFRESH: Command_NavigateService(service, NAVIGATE_REFRESH, TRUE); return TRUE;
+ case ID_OMBROWSER_OPTIONS: Command_ShowBrowserOptions(); return TRUE;
+ case ID_SERVICE_IMPORT_FILE: Command_ImportFiles(); return TRUE;
+ case ID_SERVICE_IMPORT_URL: Command_ImportUrl(); return TRUE;
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/commands.h b/Src/Plugins/Library/ml_webdev/commands.h
new file mode 100644
index 00000000..e5e93ae7
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/commands.h
@@ -0,0 +1,29 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_COMMANDS_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_COMMANDS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef LPVOID HNAVITEM;
+class ifc_omservice;
+
+HRESULT Command_NavigateService(ifc_omservice *service, LPCWSTR pszUrl, BOOL fActiveOnly);
+HRESULT Command_PostNavigateSvc(ifc_omservice *service, LPCWSTR pszUrl, BOOL fActiveOnly);
+HRESULT Command_EditService(ifc_omservice *service);
+HRESULT Command_ReloadService(ifc_omservice *service);
+HRESULT Command_ResetPermissions(ifc_omservice *service);
+HRESULT Command_LocateService(ifc_omservice *service);
+HRESULT Command_EditServiceExternal(ifc_omservice *service);
+HRESULT Command_DeleteItem(HNAVITEM hItem);
+HRESULT Command_DeleteAll();
+HRESULT Command_CreateService(void);
+HRESULT Command_OpenView(HNAVITEM hItem);
+HRESULT Command_NewWindow(HNAVITEM hItem);
+HRESULT Command_ShowBrowserOptions(void);
+
+BOOL CommandManager_Process(HNAVITEM hItem, ifc_omservice *service, UINT commandId);
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_COMMANDS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/common.cpp b/Src/Plugins/Library/ml_webdev/common.cpp
new file mode 100644
index 00000000..5d7aabf5
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/common.cpp
@@ -0,0 +1,238 @@
+#include "./common.h"
+#include "./wasabi.h"
+#include "./main.h"
+
+#include "../winamp/wa_ipc.h"
+
+#include <strsafe.h>
+
+LPWSTR Plugin_MallocString(size_t cchLen)
+{
+ return (LPWSTR)malloc(sizeof(WCHAR) * cchLen);
+}
+
+void Plugin_FreeString(LPWSTR pszString)
+{
+ if (NULL != pszString)
+ {
+ free(pszString);
+ }
+}
+
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen)
+{
+ return (LPWSTR)realloc(pszString, sizeof(WCHAR) * cchLen);
+}
+
+LPWSTR Plugin_CopyString(LPCWSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenW(pszSource) + 1;
+
+ LPWSTR copy = Plugin_MallocString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(WCHAR) * cchSource);
+ }
+ return copy;
+}
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen)
+{
+ return (LPSTR)malloc(sizeof(CHAR) * cchLen);
+}
+
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenA(pszSource) + 1;
+
+ LPSTR copy = Plugin_MallocAnsiString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(CHAR) * cchSource);
+ }
+ return copy;
+
+}
+void Plugin_FreeAnsiString(LPSTR pszString)
+{
+ Plugin_FreeString((LPWSTR)pszString);
+}
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
+{
+ INT cchBuffer = WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, lpDefaultChar, lpUsedDefaultChar);
+ if (0 == cchBuffer) return NULL;
+
+ LPSTR buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, buffer, cchBuffer, lpDefaultChar, lpUsedDefaultChar))
+ {
+ Plugin_FreeAnsiString(buffer);
+ return NULL;
+ }
+ return buffer;
+}
+
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte)
+{
+ if (NULL == lpMultiByteStr) return NULL;
+ INT cchBuffer = MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0);
+ if (NULL == cchBuffer) return NULL;
+
+ if (cbMultiByte > 0) cchBuffer++;
+
+ LPWSTR buffer = Plugin_MallocString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, buffer, cchBuffer))
+ {
+ Plugin_FreeString(buffer);
+ return NULL;
+ }
+
+ if (cbMultiByte > 0)
+ {
+ buffer[cchBuffer - 1] = L'\0';
+ }
+ return buffer;
+}
+
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource)
+{
+ return (IS_INTRESOURCE(pszResource)) ?
+ (LPWSTR)pszResource :
+ Plugin_CopyString(pszResource);
+}
+
+void Plugin_FreeResString(LPWSTR pszResource)
+{
+ if (!IS_INTRESOURCE(pszResource))
+ Plugin_FreeString(pszResource);
+}
+
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString)
+{
+ if (NULL == pszBuffer)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ if (NULL == pszString)
+ {
+ pszBuffer[0] = L'\0';
+ }
+ else if (IS_INTRESOURCE(pszString))
+ {
+ if (NULL == WASABI_API_LNG)
+ hr = E_FAIL;
+ else
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszString, pszBuffer, cchBufferMax);
+ }
+ else
+ {
+ hr = StringCchCopy(pszBuffer, cchBufferMax, pszString);
+ }
+ return hr;
+}
+
+void Plugin_SafeRelease(IUnknown *pUnk)
+{
+ if (NULL != pUnk)
+ pUnk->Release();
+}
+
+HRESULT Plugin_MakeResourcePath(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszType, LPCWSTR pszName, UINT flags)
+{
+ HINSTANCE hInstance = WASABI_API_LNG_HINST;
+ if (NULL == hInstance || NULL == FindResource(hInstance, pszName, pszType))
+ hInstance = Plugin_GetInstance();
+
+ if (NULL == OMUTILITY)
+ return E_UNEXPECTED;
+
+ return OMUTILITY->MakeResourcePath(pszBuffer, cchBufferMax, hInstance, pszType, pszName, flags);
+}
+
+HWND Plugin_GetDialogOwner(void)
+{
+ HWND hOwner= Plugin_GetLibrary();
+ if (NULL == hOwner || FALSE == IsWindowVisible(hOwner) ||
+ FALSE == IsWindowEnabled(hOwner))
+ {
+ hOwner = Plugin_GetWinamp();
+ if (NULL != hOwner)
+ {
+ HWND hDlgParent = (HWND)SENDWAIPC(hOwner, IPC_GETDIALOGBOXPARENT, 0L);
+ if (NULL != hDlgParent)
+ hOwner = hDlgParent;
+ }
+ }
+ return hOwner;
+}
+
+HRESULT Plugin_AppendFileFilter(LPTSTR pszBuffer, size_t cchBufferMax, LPCTSTR pName, LPCTSTR pFilter, LPTSTR *ppBufferOut, size_t *pRemaining, BOOL bShowFilter)
+{
+ HRESULT hr;
+
+ LPTSTR pCursor = pszBuffer;
+
+ if (NULL != ppBufferOut)
+ *ppBufferOut = pszBuffer;
+
+ if (NULL != pRemaining)
+ *pRemaining = cchBufferMax;
+
+ if (NULL == pszBuffer || NULL == pName || NULL == pFilter)
+ return E_INVALIDARG;
+
+ pszBuffer[0] = TEXT('\0');
+
+ hr = StringCchCopyEx(pCursor, cchBufferMax, pName, &pCursor, &cchBufferMax,
+ STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE);
+ if (bShowFilter && SUCCEEDED(hr))
+ {
+ LPTSTR p = pCursor;
+ hr = StringCchPrintfEx(pCursor, cchBufferMax, &pCursor, &cchBufferMax,
+ STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE, TEXT(" (%s)"), pFilter);
+ if (SUCCEEDED(hr) && p != pCursor)
+ CharLowerBuff(p, (INT)(INT_PTR)(pCursor - p));
+
+ }
+ if (SUCCEEDED(hr))
+ {
+ pCursor++;
+ cchBufferMax--;
+ hr = StringCchCopyEx(pCursor, cchBufferMax, pFilter, &pCursor, &cchBufferMax,
+ STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE);
+ }
+
+ if (cchBufferMax < 1)
+ hr = STRSAFE_E_INSUFFICIENT_BUFFER;
+
+ pCursor++;
+ cchBufferMax--;
+
+ if (SUCCEEDED(hr))
+ {
+ pCursor[0] = TEXT('\0');
+ if (NULL != ppBufferOut)
+ *ppBufferOut = pCursor;
+ if (NULL != pRemaining)
+ *pRemaining = cchBufferMax;
+ }
+ else
+ {
+ pszBuffer[0] = TEXT('\0');
+ pszBuffer[1] = TEXT('\0');
+ }
+
+ return hr;
+}
diff --git a/Src/Plugins/Library/ml_webdev/common.h b/Src/Plugins/Library/ml_webdev/common.h
new file mode 100644
index 00000000..7b52d156
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/common.h
@@ -0,0 +1,68 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_COMMON_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_COMMON_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/trace.h"
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#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 MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+#ifndef GetWindowStyle
+#define GetWindowStyle(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_STYLE))
+#endif //GetWindowStyle
+
+#ifndef GetWindowStyleEx
+#define GetWindowStyleEx(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_EXSTYLE))
+#endif // GetWindowStyleEx
+
+LPWSTR Plugin_MallocString(size_t cchLen);
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
+void Plugin_FreeString(LPWSTR pszString);
+LPWSTR Plugin_CopyString(LPCWSTR pszSource);
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen);
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
+void Plugin_FreeAnsiString(LPSTR pszString);
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
+void Plugin_FreeResString(LPWSTR pszResource);
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
+
+void Plugin_SafeRelease(IUnknown *pUnk);
+HRESULT Plugin_MakeResourcePath(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszType, LPCWSTR pszName, UINT uFlags);
+
+HWND Plugin_GetDialogOwner(void);
+HRESULT Plugin_AppendFileFilter(LPTSTR pszBuffer, size_t cchBufferMax, LPCTSTR pName, LPCTSTR pFilter, LPTSTR *ppBufferOut, size_t *pRemaining, BOOL bShowFilter);
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_COMMON_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/config.cpp b/Src/Plugins/Library/ml_webdev/config.cpp
new file mode 100644
index 00000000..6a81bb6c
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/config.cpp
@@ -0,0 +1,74 @@
+#include "main.h"
+#include "./config.h"
+#include "./wasabi.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define CONFIG_SUFFIX L"Plugins\\webDev"
+
+static LPCSTR Config_GetPath()
+{
+ static LPSTR configPath = NULL;
+ if (NULL == configPath)
+ {
+ LPCWSTR p = (NULL != WASABI_API_APP) ? WASABI_API_APP->path_getUserSettingsPath() : NULL;
+ if (NULL != p)
+ {
+ WCHAR szBuffer[MAX_PATH * 2] = {0};
+ if (0 != PathCombine(szBuffer, p, CONFIG_SUFFIX))
+ {
+ OMUTILITY->EnsurePathExist(szBuffer);
+ PathAppend(szBuffer, L"config.ini");
+ configPath = Plugin_WideCharToMultiByte(CP_UTF8, 0, szBuffer, -1, NULL, NULL);
+ }
+ }
+ }
+
+ return configPath;
+}
+
+DWORD Config_ReadStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpDefault, LPSTR lpReturnedString, DWORD nSize)
+{
+ return GetPrivateProfileStringA(lpSectionName, lpKeyName, lpDefault, lpReturnedString, nSize, Config_GetPath());
+}
+
+UINT Config_ReadInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nDefault)
+{
+ return GetPrivateProfileIntA(lpSectionName, lpKeyName, nDefault, Config_GetPath());
+}
+
+HRESULT Config_WriteStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpString)
+{
+ LPCSTR configPath = Config_GetPath();
+ if (NULL == configPath || '\0' == *configPath)
+ return E_UNEXPECTED;
+
+ if (0 != WritePrivateProfileStringA(lpSectionName, lpKeyName, lpString, configPath))
+ return S_OK;
+
+ DWORD errorCode = GetLastError();
+ return HRESULT_FROM_WIN32(errorCode);
+}
+
+HRESULT Config_WriteInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nValue)
+{
+ char szBuffer[32];
+ HRESULT hr = StringCchPrintfA(szBuffer, ARRAYSIZE(szBuffer), "%d", nValue);
+ if (FAILED(hr)) return hr;
+
+ return Config_WriteStr(lpSectionName, lpKeyName, szBuffer);
+}
+
+HRESULT Config_WriteSection(LPCSTR lpSectionName, LPCSTR lpData)
+{
+ LPCSTR configPath = Config_GetPath();
+ if (NULL == configPath || '\0' == *configPath)
+ return E_UNEXPECTED;
+
+ if (0 == WritePrivateProfileSectionA(lpSectionName, lpData, configPath))
+ return S_OK;
+
+ DWORD errorCode = GetLastError();
+ return HRESULT_FROM_WIN32(errorCode);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/config.h b/Src/Plugins/Library/ml_webdev/config.h
new file mode 100644
index 00000000..24f5816c
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/config.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_CONFIG_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_CONFIG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+DWORD Config_ReadStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpDefault, LPSTR lpReturnedString, DWORD nSize);
+UINT Config_ReadInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nDefault);
+HRESULT Config_WriteStr(LPCSTR lpSectionName, LPCSTR lpKeyName, LPCSTR lpString);
+HRESULT Config_WriteInt(LPCSTR lpSectionName, LPCSTR lpKeyName, INT nValue);
+HRESULT Config_WriteSection(LPCSTR lpSectionName, LPCSTR lpData);
+
+
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_CONFIG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/external.cpp b/Src/Plugins/Library/ml_webdev/external.cpp
new file mode 100644
index 00000000..30837747
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/external.cpp
@@ -0,0 +1,272 @@
+#include "main.h"
+#include "./resource.h"
+#include "./external.h"
+#include "./navigation.h"
+#include "./commands.h"
+
+#include "../winamp/jsapi.h"
+#include "../winamp/jsapi_CallbackParameters.h"
+
+#include "./serviceHelper.h"
+
+#include <browserView.h>
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+
+#define DISPTABLE_CLASS ExternalDispatch
+
+DISPTABLE_BEGIN()
+ DISPENTRY_ADD(DISPATCH_SERVICE_OPEN, L"serviceOpen", OnServiceOpen)
+ DISPENTRY_ADD(DISPATCH_SERVICE_CREATE, L"serviceCreate", OnServiceCreate)
+ DISPENTRY_ADD(DISPATCH_SERVICE_GETINFO, L"serviceGetInfo", OnServiceGetInfo)
+ DISPENTRY_ADD(DISPATCH_SERVICE_SETINFO, L"serviceSetInfo", OnServiceSetInfo)
+DISPTABLE_END
+
+#undef DISPTABLE_CLASS
+
+
+static BOOL DispParam_GetStringOpt(LPCWSTR *str, DISPPARAMS *paramInfo, UINT paramNumber, UINT *argErr)
+{
+ if (paramInfo->cArgs < paramNumber ||
+ VT_NULL == paramInfo->rgvarg[paramInfo->cArgs - paramNumber].vt)
+ {
+ *str = NULL;
+ return FALSE;
+ }
+
+ JSAPI_GETSTRING((*str), paramInfo, paramNumber, argErr);
+ return TRUE;
+}
+
+ExternalDispatch::ExternalDispatch()
+ : ref(1)
+{
+}
+
+ExternalDispatch::~ExternalDispatch()
+{
+}
+
+HRESULT ExternalDispatch::CreateInstance(ExternalDispatch **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ *instance = new ExternalDispatch();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+LPCWSTR ExternalDispatch::GetName()
+{
+ return L"WebDev";
+}
+
+ULONG ExternalDispatch::AddRef(void)
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+
+ULONG ExternalDispatch::Release(void)
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+STDMETHODIMP ExternalDispatch::QueryInterface(REFIID riid, void **ppvObject)
+{
+ if (NULL == ppvObject) return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = static_cast<IDispatch*>(this);
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = static_cast<IUnknown*>(this);
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceOpen(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 1, 2);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+
+ UINT serviceId;
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 1, puArgErr);
+
+ LPCWSTR forceUrl = NULL;
+ if (pdispparams->cArgs > 1)
+ {
+ switch(pdispparams->rgvarg[0].vt)
+ {
+ case VT_BSTR: forceUrl = pdispparams->rgvarg[0].bstrVal; break;
+ case VT_I4: forceUrl = MAKEINTRESOURCE(pdispparams->rgvarg[0].lVal); break;
+ }
+ }
+
+ if (FALSE == DispParam_GetStringOpt(&forceUrl, pdispparams,2, puArgErr))
+ forceUrl = NULL;
+
+ HRESULT hr;
+
+ Navigation *navigation;
+ hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ if (NULL != navigation->FindService(serviceId, &service))
+ {
+ hr = Command_NavigateService(service, forceUrl, FALSE);
+ service->Release();
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ navigation->Release();
+ }
+
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceCreate(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ HRESULT hr = Command_CreateService();
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceGetInfo(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_DISPATCH);
+
+ UINT serviceId;
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 1, puArgErr);
+
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ if (NULL != navigation->FindService(serviceId, &service))
+ {
+ WCHAR szBuffer[2048];
+ JSAPI::CallbackParameters *params = new JSAPI::CallbackParameters;
+ params->AddLong(L"id", service->GetId());
+
+ if (FAILED(service->GetName(szBuffer, ARRAYSIZE(szBuffer)))) szBuffer[0] = L'\0';
+ params->AddString(L"name", szBuffer);
+
+ if (FAILED(service->GetUrl(szBuffer, ARRAYSIZE(szBuffer)))) szBuffer[0] = L'\0';
+ params->AddString(L"url", szBuffer);
+
+ if (FAILED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer)))) szBuffer[0] = L'\0';
+ params->AddString(L"icon", szBuffer);
+
+ params->AddBoolean(L"preauthorized", (S_OK == ServiceHelper_IsPreAuthorized(service)));
+
+ service->Release();
+ V_DISPATCH(pvarResult) = params;
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ navigation->Release();
+ }
+
+ if (FAILED(hr))
+ {
+ V_DISPATCH(pvarResult) = 0;
+ }
+
+ return S_OK;
+}
+
+HRESULT ExternalDispatch::OnServiceSetInfo(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 2, 5);
+
+ UINT serviceId;
+ JSAPI_GETUNSIGNED_AS_NUMBER(serviceId, pdispparams, 1, puArgErr);
+
+ Navigation *navigation;
+ HRESULT hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ HNAVITEM hItem = navigation->FindService(serviceId, &service);
+ if (NULL != hItem)
+ {
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ LPCWSTR value = NULL;
+
+ editor->BeginUpdate();
+
+ if (FALSE != DispParam_GetStringOpt(&value, pdispparams, 2, puArgErr) && FAILED(editor->SetName(value, FALSE)))
+ hr = E_FAIL;
+
+ if (FALSE != DispParam_GetStringOpt(&value, pdispparams, 3, puArgErr) && FAILED(ServiceHelper_UpdateIcon(editor, value)))
+ hr = E_FAIL;
+
+ if (FALSE != DispParam_GetStringOpt(&value, pdispparams, 4, puArgErr) && FAILED(editor->SetUrl(value, FALSE)))
+ hr = E_FAIL;
+
+ VARIANT_BOOL authorized = JSAPI_PARAM_OPTIONAL(pdispparams, 5, boolVal, VARIANT_FALSE);
+ if (S_OK == editor->SetFlags((VARIANT_TRUE == authorized) ? WDSVCF_PREAUTHORIZED : 0, WDSVCF_PREAUTHORIZED))
+ {
+ if (VARIANT_TRUE == authorized)
+ Command_ResetPermissions(service);
+ }
+
+ editor->EndUpdate();
+ editor->Release();
+ }
+
+ if(SUCCEEDED(hr))
+ hr = ServiceHelper_Save(service);
+
+ service->Release();
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ navigation->Release();
+ }
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE));
+
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/external.h b/Src/Plugins/Library/ml_webdev/external.h
new file mode 100644
index 00000000..e3dfe6ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/external.h
@@ -0,0 +1,49 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_EXTERNAL_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_EXTERNAL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/dispatchTable.h"
+
+class ExternalDispatch : public IDispatch
+{
+
+public:
+ typedef enum
+ {
+ DISPATCH_SERVICE_OPEN = 700,
+ DISPATCH_SERVICE_CREATE = 701,
+ DISPATCH_SERVICE_GETINFO = 702,
+ DISPATCH_SERVICE_SETINFO = 703,
+ } DispatchCodes;
+
+protected:
+ ExternalDispatch();
+ ~ExternalDispatch();
+
+public:
+ static HRESULT CreateInstance(ExternalDispatch **instance);
+ static LPCWSTR GetName();
+
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+protected:
+ DISPTABLE_INCLUDE();
+ DISPHANDLER_REGISTER(OnServiceOpen);
+ DISPHANDLER_REGISTER(OnServiceCreate);
+ DISPHANDLER_REGISTER(OnServiceGetInfo);
+ DISPHANDLER_REGISTER(OnServiceSetInfo);
+
+protected:
+ ULONG ref;
+
+};
+
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_EXTERNAL_HEADER
diff --git a/Src/Plugins/Library/ml_webdev/forceUrl.cpp b/Src/Plugins/Library/ml_webdev/forceUrl.cpp
new file mode 100644
index 00000000..90b55742
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/forceUrl.cpp
@@ -0,0 +1,77 @@
+#include "main.h"
+#include "./forceUrl.h"
+
+typedef struct __FORCEURLDATA
+{
+ UINT serviceId;
+ LPWSTR url;
+} FORCEURLDATA;
+
+#define FORCEURLPROP L"MLWEBDEV_FORCEURL"
+
+void ForceUrl_Remove()
+{
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return;
+
+ FORCEURLDATA *data = (FORCEURLDATA*)GetProp(hLibrary, FORCEURLPROP);
+ RemoveProp(hLibrary, FORCEURLPROP);
+ if (NULL != data)
+ {
+ Plugin_FreeString(data->url);
+ free(data);
+ }
+}
+
+HRESULT ForceUrl_Set(UINT serviceId, LPCWSTR pszUrl)
+{
+ if (NULL == pszUrl) return E_INVALIDARG;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_FAIL;
+
+ FORCEURLDATA *data = (FORCEURLDATA*)GetProp(hLibrary, FORCEURLPROP);
+
+ if (NULL != data)
+ {
+ Plugin_FreeString(data->url);
+ if (data->serviceId != serviceId)
+ {
+ free(data);
+ data = NULL;
+ }
+ }
+
+ if (NULL == data)
+ {
+ data = (FORCEURLDATA*)malloc(sizeof(FORCEURLDATA));
+ if (NULL == data) return E_OUTOFMEMORY;
+ data->serviceId = serviceId;
+ }
+
+ data->url = Plugin_CopyString(pszUrl);
+ if (NULL == data->url || FALSE == SetProp(hLibrary, FORCEURLPROP, data))
+ {
+ ForceUrl_Remove();
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+HRESULT ForceUrl_Get(UINT serviceId, const wchar_t **ppszUrl)
+{
+ if (NULL == ppszUrl) return E_POINTER;
+ *ppszUrl = NULL;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_FAIL;
+
+ FORCEURLDATA *data = (FORCEURLDATA*)GetProp(hLibrary, FORCEURLPROP);
+
+ if (NULL == data || data->serviceId != serviceId)
+ return E_NOINTERFACE;
+
+ *ppszUrl = data->url;
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/forceUrl.h b/Src/Plugins/Library/ml_webdev/forceUrl.h
new file mode 100644
index 00000000..75b25aaf
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/forceUrl.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_FORCE_URL_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_FORCE_URL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+
+#include <wtypes.h>
+
+HRESULT ForceUrl_Set(UINT serviceId, LPCWSTR pszUrl);
+HRESULT ForceUrl_Get(UINT serviceId, const wchar_t **ppszUrl);
+void ForceUrl_Remove();
+
+#endif // NULLSOFT_WEBDEV_PLUGIN_FORCE_URL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/import.h b/Src/Plugins/Library/ml_webdev/import.h
new file mode 100644
index 00000000..b1c079c7
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/import.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_IMPORT_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_IMPORT_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+HRESULT ImportService_GetFileSupported();
+HRESULT ImportService_GetUrlSupported();
+
+HRESULT ImportService_FromFile(HWND hOwner);
+HRESULT ImportService_FromUrl(HWND hOwner);
+
+void ImportService_SaveRecentUrl();
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_IMPORT_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/importFile.cpp b/Src/Plugins/Library/ml_webdev/importFile.cpp
new file mode 100644
index 00000000..a6e1d654
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/importFile.cpp
@@ -0,0 +1,266 @@
+#include "main.h"
+#include "./import.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+#include "./navigation.h"
+
+#include <ifc_omstorage.h>
+#include <ifc_omfilestorage.h>
+#include <ifc_omstorageenum.h>
+#include <ifc_omservice.h>
+#include <ifc_omserviceenum.h>
+
+#include <strsafe.h>
+
+static HRESULT ImportFile_GetEnumerator(ifc_omstorageenumerator **enumerator)
+{
+ if (NULL == OMSERVICEMNGR) return E_UNEXPECTED;
+ return OMSERVICEMNGR->EnumStorage(&STID_OmFileStorage, ifc_omstorage::capPublic | ifc_omstorage::capLoad, enumerator);
+}
+
+HRESULT ImportService_GetFileSupported()
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportFile_GetEnumerator(&enumerator);
+
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage;
+ if(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ storage->Release();
+ hr = S_OK;
+ }
+ else
+ {
+ hr = S_FALSE;
+ }
+
+ enumerator->Release();
+ }
+ return hr;
+}
+
+static HRESULT ImportFile_GetFilter(LPWSTR pszBuffer, UINT cchBufferMax, DWORD *defaultIndex)
+{
+ if (NULL != defaultIndex)
+ *defaultIndex = 0;
+
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ HRESULT hr;
+ WCHAR szName[128] = {0}, szList[512] = {0};
+
+ LPWSTR cursor = pszBuffer;
+ size_t remaining = cchBufferMax;
+
+ LPWSTR listC = szList;
+ size_t listR = ARRAYSIZE(szList);
+
+ DWORD counter = 0;
+
+ szList[0] = L'\0';
+ pszBuffer[0] = L'\0';
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_FILEFILTER_ALL, szName, ARRAYSIZE(szName));
+ hr = Plugin_AppendFileFilter(cursor, remaining, szName, L"*.*", &cursor, &remaining, TRUE);
+ if (FAILED(hr)) return hr;
+ counter++;
+
+
+ ifc_omstorageenumerator *enumerator;
+ hr = ImportFile_GetEnumerator(&enumerator);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage;
+ ifc_omfilestorage *fileStorage;
+ while(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ if (SUCCEEDED(storage->QueryInterface(IFC_OmFileStorage, (void**)&fileStorage)))
+ {
+ WCHAR szFilter[64] = {0};
+ if (SUCCEEDED(fileStorage->GetFilter(szFilter, ARRAYSIZE(szFilter))) &&
+ L'\0' != szFilter[0] &&
+ SUCCEEDED(storage->GetDescription(szName, ARRAYSIZE(szName))))
+ {
+ hr = Plugin_AppendFileFilter(cursor, remaining, szName, szFilter, &cursor, &remaining, TRUE);
+ if (FAILED(hr)) break;
+
+ counter++;
+
+ if (listC == szList || SUCCEEDED(StringCchCopyEx(listC, listR, L";", &listC, &listR, 0)))
+ StringCchCopyEx(listC, listR, szFilter, &listC, &listR, 0);
+ }
+ fileStorage->Release();
+ }
+ storage->Release();
+ }
+ enumerator->Release();
+ }
+
+ if (SUCCEEDED(hr) && L'\0' != szList[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_FILEFILTER_ALLKNOWN, szName, ARRAYSIZE(szName));
+ hr = Plugin_AppendFileFilter(cursor, remaining, szName, szList, &cursor, &remaining, TRUE);
+ if (FAILED(hr)) return hr;
+
+ counter++;
+
+ if (NULL != defaultIndex)
+ *defaultIndex = counter;
+ }
+
+ return hr;
+}
+static HRESULT ImportFile_ProcessFile(HWND hOwner, ifc_omstorageenumerator *enumerator,
+ ifc_omservicehost *serviceHost, ifc_omstorage *serviceStorage, LPCWSTR pszFile, ULONG *converted)
+{
+ ifc_omstorage *storage;
+ enumerator->Reset();
+
+ Navigation *navigation;
+ if (FAILED(Plugin_GetNavigation(&navigation)))
+ return E_FAIL;
+
+ ULONG loaded(0), saved(0);
+ while(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ ifc_omserviceenum *serviceEnum;
+ HRESULT hr = storage->Load(pszFile, serviceHost, &serviceEnum);
+ if(SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ while(S_OK == serviceEnum->Next(1, &service, NULL))
+ {
+ loaded++;
+ if (SUCCEEDED(service->SetAddress(NULL)))
+ {
+ ULONG savedOk;
+ if (SUCCEEDED(serviceStorage->Save(&service, 1, ifc_omstorage::saveClearModified, &savedOk)))
+ {
+ navigation->CreateItem(service);
+ saved += savedOk;
+ }
+ }
+ service->Release();
+ }
+ serviceEnum->Release();
+ break;
+ }
+ else if (OMSTORAGE_E_UNKNOWN_FORMAT != hr)
+ {
+ break;
+ }
+
+ storage->Release();
+ }
+
+ if (NULL != converted)
+ *converted = saved;
+
+ navigation->Release();
+
+ return S_OK;
+}
+
+
+static HRESULT ImportFile_ProcessList(HWND hOwner, LPCWSTR pszList)
+{
+ if (NULL == pszList)
+ return E_INVALIDARG;
+
+ LPCWSTR base, block, c;
+ base = pszList;
+ c = base;
+ block = NULL;
+ ULONG converted;
+
+ HRESULT hr;
+ ifc_omstorageenumerator *enumerator;
+ hr = ImportFile_GetEnumerator(&enumerator);
+ if (FAILED(hr)) return hr;
+
+ WebDevServiceHost *serviceHost;
+ hr = WebDevServiceHost::GetCachedInstance(&serviceHost);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *serviceStorage;
+ hr = ServiceHelper_QueryStorage(&serviceStorage);
+ if (SUCCEEDED(hr))
+ {
+ ULONG scanned(0);
+ while(L'\0' != *c)
+ {
+ block = c;
+ while (L'\0' != *c) c++;
+ if (c != block && block != base)
+ {
+ WCHAR szBuffer[MAX_PATH * 2] = {0};
+ scanned++;
+ if (SUCCEEDED(StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), L"%s\\%s", base, block)) &&
+ SUCCEEDED(ImportFile_ProcessFile(hOwner, enumerator, serviceHost, serviceStorage, szBuffer, &converted)))
+ {
+ }
+ }
+ c++;
+ }
+
+ if (pszList == block && c != pszList &&
+ SUCCEEDED(ImportFile_ProcessFile(hOwner, enumerator, serviceHost, serviceStorage, pszList, &converted)))
+ {
+ }
+ serviceStorage->Release();
+ }
+ serviceHost->Release();
+ }
+
+ enumerator->Release();
+ return hr;
+}
+
+HRESULT ImportService_FromFile(HWND hOwner)
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ HRESULT hr;
+ OPENFILENAME of;
+ ZeroMemory(&of, sizeof(of));
+ of.lStructSize = sizeof(of);
+
+ WCHAR szFilter[1024];
+ hr = ImportFile_GetFilter(szFilter, ARRAYSIZE(szFilter), &of.nFilterIndex);
+ if (FAILED(hr)) return hr;
+
+ UINT cchResultMax = 16384;
+ LPWSTR pszResult = Plugin_MallocString(cchResultMax);
+ if (NULL == pszResult) return E_OUTOFMEMORY;
+ *pszResult = L'\0';
+
+ of.hwndOwner = hOwner;
+ of.lpstrFilter = szFilter;
+ of.lpstrFile = pszResult;
+ of.nMaxFile = cchResultMax;
+ of.lpstrInitialDir = WASABI_API_APP->path_getUserSettingsPath();
+ of.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_IMPORT_FILES);
+ of.Flags = OFN_ENABLESIZING | OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_ALLOWMULTISELECT;
+
+ if (0 == GetOpenFileName(&of))
+ {
+ INT err = CommDlgExtendedError();
+ hr = (0 == err) ? S_FALSE : E_FAIL;
+ }
+ else
+ {
+ hr = ImportFile_ProcessList(hOwner, pszResult);
+ }
+
+ Plugin_FreeString(pszResult);
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/importUrl.cpp b/Src/Plugins/Library/ml_webdev/importUrl.cpp
new file mode 100644
index 00000000..2ab584e8
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/importUrl.cpp
@@ -0,0 +1,320 @@
+#include "main.h"
+#include "./import.h"
+#include "./wasabi.h"
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+#include "./navigation.h"
+#include "./resource.h"
+#include "./config.h"
+
+#include <ifc_omstorage.h>
+#include <ifc_omwebstorage.h>
+#include <ifc_omstorageenum.h>
+#include <ifc_omservice.h>
+#include <ifc_omserviceenum.h>
+
+#include <strsafe.h>
+
+
+typedef std::vector<WCHAR*> StringList;
+
+static StringList recentList;
+static BOOL recentListModified = FALSE;
+
+typedef struct __OPENURLDLG
+{
+ HWND hOwner;
+ LPCWSTR pszAddress;
+ LPWSTR pszBuffer;
+ UINT cchBufferMax;
+} OPENURLDLG;
+
+static INT_PTR ImportUrlDlg_Show(OPENURLDLG *poud);
+
+static HRESULT ImportUrl_GetEnumerator(ifc_omstorageenumerator **enumerator)
+{
+ if (NULL == OMSERVICEMNGR) return E_UNEXPECTED;
+ return OMSERVICEMNGR->EnumStorage(&STID_OmWebStorage, ifc_omstorage::capPublic | ifc_omstorage::capLoad, enumerator);
+}
+
+HRESULT ImportService_GetUrlSupported()
+{
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportUrl_GetEnumerator(&enumerator);
+
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *storage;
+ if(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ storage->Release();
+ hr = S_OK;
+ }
+ else
+ {
+ hr = S_FALSE;
+ }
+
+ enumerator->Release();
+ }
+ return hr;
+}
+
+HRESULT ImportUrl_LoadAddress(HWND hOwner, LPCWSTR pszAddress, ifc_omstorageenumerator *enumerator,
+ WebDevServiceHost *serviceHost, ifc_omstorage *serviceStorage, Navigation *navigation)
+{
+ HRESULT hr = S_OK;
+ ULONG loaded(0);
+
+ ifc_omstorage *storage;
+ enumerator->Reset();
+
+ while(S_OK == enumerator->Next(1, &storage, NULL))
+ {
+ ifc_omstorageasync *async;
+ hr = storage->BeginLoad(pszAddress, serviceHost, NULL, NULL, &async);
+ if(SUCCEEDED(hr))
+ {
+ ifc_omserviceenum *serviceEnum;
+ hr = storage->EndLoad(async, &serviceEnum);
+ async->Release();
+
+ if (SUCCEEDED(hr))
+ {
+ ifc_omservice *service;
+ while(S_OK == serviceEnum->Next(1, &service, NULL))
+ {
+ loaded++;
+ if (SUCCEEDED(service->SetAddress(NULL)))
+ {
+ ULONG savedOk;
+ if (SUCCEEDED(serviceStorage->Save(&service, 1, ifc_omstorage::saveClearModified, &savedOk)))
+ {
+ navigation->CreateItem(service);
+ }
+ }
+ service->Release();
+ }
+ serviceEnum->Release();
+ }
+ break;
+ }
+ else if (OMSTORAGE_E_UNKNOWN_FORMAT != hr)
+ {
+ break;
+ }
+ storage->Release();
+ }
+
+ return hr;
+}
+
+HRESULT ImportService_FromUrl(HWND hOwner)
+{
+ OPENURLDLG dlg = {0};
+ WCHAR szBuffer[4096] = {0};
+
+ dlg.hOwner = hOwner;
+ dlg.pszAddress = NULL;
+ dlg.pszBuffer = szBuffer;
+ dlg.cchBufferMax = ARRAYSIZE(szBuffer);
+ if (IDOK != ImportUrlDlg_Show(&dlg))
+ return S_FALSE;
+
+ ifc_omstorageenumerator *enumerator;
+ HRESULT hr = ImportUrl_GetEnumerator(&enumerator);
+ if (SUCCEEDED(hr))
+ {
+ Navigation *navigation;
+ hr = Plugin_GetNavigation(&navigation);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omstorage *serviceStorage;
+ hr = ServiceHelper_QueryStorage(&serviceStorage);
+ if (SUCCEEDED(hr))
+ {
+ WebDevServiceHost *serviceHost;
+ hr = WebDevServiceHost::GetCachedInstance(&serviceHost);
+ if (SUCCEEDED(hr))
+ {
+ hr = ImportUrl_LoadAddress(hOwner, szBuffer, enumerator, serviceHost, serviceStorage, navigation);
+ serviceHost->Release();
+ }
+ serviceStorage->Release();
+ }
+ navigation->Release();
+ }
+ enumerator->Release();
+ }
+ return hr;
+}
+
+static INT_PTR ImportUrlDlg_OnInit(HWND hwnd, HWND hFocus, LPARAM param)
+{
+ OPENURLDLG *poud = (OPENURLDLG*)param;
+ if (NULL != poud)
+ {
+ SetProp(hwnd, L"OPENURLDLG", poud);
+
+ HWND hAddress = GetDlgItem(hwnd, IDC_ADDRESS);
+ if (NULL != hAddress)
+ {
+ LPWSTR p;
+
+ size_t count = recentList.size();
+ if (0 == count)
+ {
+ char szKey[32] = {0}, szBuffer[4096] = {0};
+ for(int i = 1; i < 101; i++)
+ {
+ if (FAILED(StringCchPrintfA(szKey, ARRAYSIZE(szKey), "entry%d", i)) ||
+ 0 == Config_ReadStr("RecentUrl", szKey, NULL, szBuffer, ARRAYSIZE(szBuffer)) ||
+ '\0' == szBuffer[0])
+ {
+ break;
+ }
+
+ p = Plugin_MultiByteToWideChar(CP_UTF8, 0, szBuffer, -1);
+ if (NULL != p) recentList.push_back(p);
+ }
+ count = recentList.size();
+ }
+
+ for (size_t i = 0; i < count; i ++)
+ {
+ p = recentList[i];
+ if(NULL != p && L'\0' != *p)
+ SendMessage(hAddress, CB_ADDSTRING, 0, (LPARAM)p);
+ }
+
+ if (NULL != poud->pszAddress)
+ SetWindowText(hAddress, poud->pszAddress);
+ }
+
+ RECT ownerRect;
+ if (NULL != poud->hOwner && IsWindowVisible(poud->hOwner) &&
+ GetWindowRect(poud->hOwner, &ownerRect))
+ {
+ RECT myRect;
+ GetWindowRect(hwnd, &myRect);
+ LONG x = ownerRect.left + ((ownerRect.right - ownerRect.left) - (myRect.right - myRect.left))/2;
+ LONG y = ownerRect.top + ((ownerRect.bottom - ownerRect.top) - (myRect.bottom - myRect.top))/2;
+ SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+ }
+
+ return 0;
+}
+
+static void ImportUrlDlg_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, L"OPENURLDLG");
+
+}
+static INT_PTR ImportUrlDlg_ReturnAddress(HWND hwnd)
+{
+ OPENURLDLG *poud = (OPENURLDLG*)GetProp(hwnd, L"OPENURLDLG");
+ if (NULL == poud) return -1;
+
+ HWND hAddress = GetDlgItem(hwnd, IDC_ADDRESS);
+ if (NULL == hAddress) return -2;
+
+ if (0 == GetWindowText(hAddress, poud->pszBuffer, poud->cchBufferMax))
+ return -3;
+
+ if (NULL != poud->pszBuffer && L'\0' != *poud->pszBuffer)
+ {
+ LPWSTR p;
+ size_t index = recentList.size();
+ while(index--)
+ {
+ p = recentList[index];
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, poud->pszBuffer, -1, p, -1))
+ {
+ Plugin_FreeString(p);
+ recentList.erase(recentList.begin() + index);
+ }
+ }
+
+ p = Plugin_CopyString(poud->pszBuffer);
+ if (NULL != p)
+ {
+ //recentList.push_front(p);
+ recentList.insert(recentList.begin(), p);
+ recentListModified = TRUE;
+ }
+ }
+ return IDOK;
+}
+
+static void ImportUrlDlg_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND hControl)
+{
+ switch(commandId)
+ {
+ case IDOK:
+ EndDialog(hwnd, ImportUrlDlg_ReturnAddress(hwnd));
+ break;
+ case IDCANCEL:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+ }
+
+}
+static INT_PTR WINAPI ImportUrlDlg_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return ImportUrlDlg_OnInit(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: ImportUrlDlg_OnDestroy(hwnd); break;
+ case WM_COMMAND: ImportUrlDlg_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ }
+
+ return 0;
+
+}
+
+
+static INT_PTR ImportUrlDlg_Show(OPENURLDLG *poud)
+{
+ if (NULL == poud || NULL == poud->pszBuffer)
+ return -1;
+
+ HWND hParent = poud->hOwner;
+ if (NULL == poud->hOwner)
+ hParent = Plugin_GetLibrary();
+
+ return WASABI_API_DIALOGBOXPARAMW(IDD_OPENURL, hParent, ImportUrlDlg_DialogProc, (LPARAM)poud);
+
+}
+
+void ImportService_SaveRecentUrl()
+{
+ if (FALSE == recentListModified)
+ return;
+
+ Config_WriteSection("RecentUrl", NULL);
+
+ size_t count = recentList.size();
+ if (count > 100) count = 100;
+
+ char szKey[32], szBuffer[4096];
+ UINT entry = 1;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ LPCWSTR p = recentList[i];
+ if (NULL != p && L'\0' != *p &&
+ 0 != WideCharToMultiByte(CP_UTF8, 0, p, -1, szBuffer, ARRAYSIZE(szBuffer), NULL, NULL) &&
+ SUCCEEDED(StringCchPrintfA(szKey, ARRAYSIZE(szKey), "entry%d", entry)) &&
+ SUCCEEDED(Config_WriteStr("RecentUrl", szKey, szBuffer)))
+ {
+ entry++;
+ }
+ }
+
+ recentListModified = FALSE;
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/local_menu.cpp b/Src/Plugins/Library/ml_webdev/local_menu.cpp
new file mode 100644
index 00000000..dd2eb09c
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/local_menu.cpp
@@ -0,0 +1,222 @@
+#include "main.h"
+#include "./local_menu.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./navigation.h"
+#include "./import.h"
+
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#include "../nu/menuHelpers.h"
+#include "./ombrowser/browserView.h"
+#include "./ombrowser/toolbar.h"
+
+#include "./serviceHelper.h"
+#include <ifc_omservice.h>
+
+#define SUBMENU_VIEW 0
+#define SUBMENU_SERVICEMNGR 1
+#define SUBMENU_NAVIGATION 2
+#define SUBMENU_PLUGIN 3
+
+static INT Menu_AddViewContext(HMENU destMenu, HMENU baseMenu, BOOL viewActive)
+{
+ if (NULL == destMenu) return 0;
+
+ HMENU sorceMenu = GetSubMenu(baseMenu, SUBMENU_VIEW);
+ if (NULL == sorceMenu) return 0;
+
+ INT c = GetMenuItemCount(destMenu);
+ c = MenuHelper_CopyMenu(destMenu, c, sorceMenu);
+
+
+ if (FALSE != viewActive)
+ {
+ EnableMenuItem(destMenu, ID_VIEW_OPEN, MF_BYCOMMAND | MF_GRAYED | MF_DISABLED);
+ }
+ else
+ {
+ EnableMenuItem(destMenu, ID_VIEW_OPEN, MF_BYCOMMAND | MF_ENABLED);
+ SetMenuDefaultItem(destMenu, ID_VIEW_OPEN, FALSE);
+ }
+ return c;
+}
+
+typedef struct __NAVITEMPAIR
+{
+ INT wId;
+ LPCSTR toolItem;
+} NAVITEMPAIR;
+
+static INT Menu_AddNavigationContext(HMENU destMenu, HMENU baseMenu)
+{
+ if (NULL == destMenu) return 0;
+
+ HMENU sorceMenu = GetSubMenu(baseMenu, SUBMENU_NAVIGATION);
+ if (NULL == sorceMenu) return 0;
+
+ INT c = GetMenuItemCount(destMenu);
+ c = MenuHelper_CopyMenu(destMenu, c, sorceMenu);
+ if (0 == c) return c;
+
+ HWND hView = NULL;
+
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ hView = navigation->GetActiveView(NULL);
+ navigation->Release();
+ }
+
+ HWND hToolbar = (NULL != hView) ? BrowserView_GetToolbar(hView) : NULL;
+ if (NULL != hToolbar)
+ {
+ NAVITEMPAIR szItems[] =
+ {
+ {ID_NAVIGATION_BACK, TOOLITEM_BUTTON_BACK},
+ {ID_NAVIGATION_FORWARD, TOOLITEM_BUTTON_FORWARD},
+ {ID_NAVIGATION_HOME, TOOLITEM_BUTTON_HOME},
+ {ID_NAVIGATION_STOP, TOOLITEM_BUTTON_STOP},
+ {ID_NAVIGATION_REFRESH, TOOLITEM_BUTTON_REFRESH},
+ };
+
+ for (INT i = 0; i < ARRAYSIZE(szItems); i++)
+ {
+ INT index = Toolbar_FindItem(hToolbar, szItems[i].toolItem);
+ if (-1 != index)
+ {
+ BOOL fDisabled = Toolbar_GetItemStyle(hToolbar, MAKEINTRESOURCE(index), TBIS_DISABLED);
+ EnableMenuItem(destMenu, szItems[i].wId, MF_BYCOMMAND | (fDisabled) ? (MF_DISABLED | MF_GRAYED) : (MF_ENABLED));
+ }
+ }
+ }
+
+ return c;
+}
+
+static INT Menu_AddServiceContext(HMENU destMenu, HMENU baseMenu, ifc_omservice *service)
+{
+ if (NULL == destMenu || NULL == service) return 0;
+
+ HMENU sorceMenu = GetSubMenu(baseMenu, SUBMENU_SERVICEMNGR);
+ if (NULL == sorceMenu) return 0;
+
+ INT origCount = GetMenuItemCount(destMenu);
+ INT c = MenuHelper_CopyMenu(destMenu, origCount, sorceMenu);
+ UINT serviceFlags;
+ if (FAILED(service->GetFlags(&serviceFlags))) serviceFlags = 0;
+
+ if (0 != ((WDSVCF_ROOT | WDSVCF_SPECIAL) & serviceFlags))
+ {
+ UINT szDisabled[] = {ID_SERVICE_EDIT, ID_SERVICE_DELETE, ID_SERVICE_RELOAD,
+ ID_SERVICE_LOCATE, ID_SERVICE_EDITEXTERNAL };
+
+ for (INT i = 0; i < ARRAYSIZE(szDisabled); i++)
+ EnableMenuItem(destMenu, szDisabled[i], MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
+ }
+
+ UINT uEnable;
+
+ uEnable = (S_OK == ImportService_GetFileSupported()) ? MF_ENABLED : (MF_DISABLED | MF_GRAYED);
+ EnableMenuItem(destMenu, ID_SERVICE_IMPORT_FILE, MF_BYCOMMAND | uEnable);
+
+ uEnable = (S_OK == ImportService_GetUrlSupported()) ? MF_ENABLED : (MF_DISABLED | MF_GRAYED);
+ EnableMenuItem(destMenu, ID_SERVICE_IMPORT_URL, MF_BYCOMMAND | uEnable);
+
+ return c;
+}
+
+static INT Menu_AddPluginContext(HMENU destMenu, HMENU baseMenu, ifc_omservice *service)
+{
+ UINT serviceFlags;
+ if (NULL == service || FAILED(service->GetFlags(&serviceFlags)))
+ serviceFlags = 0;
+
+ if (NULL == destMenu || NULL == service ||
+ 0 == (WDSVCF_ROOT & serviceFlags))
+ {
+ return 0;
+ }
+
+ HMENU sorceMenu = GetSubMenu(baseMenu, SUBMENU_PLUGIN);
+ if (NULL == sorceMenu) return 0;
+
+ INT origCount = GetMenuItemCount(destMenu);
+ INT c = MenuHelper_CopyMenu(destMenu, origCount, sorceMenu);
+
+ return c;
+}
+
+static HMENU Menu_GetServiceContext(HMENU baseMenu, ifc_omservice *service)
+{
+ HMENU hMenu = CreatePopupMenu();
+ if(NULL == hMenu) return NULL;
+
+ ifc_omservice *activeService = NULL;
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ navigation->GetActive(&activeService);
+ navigation->Release();
+ }
+
+ BOOL fActive = (NULL != service && activeService == service);
+
+ INT c, total;
+ total = Menu_AddViewContext(hMenu, baseMenu, fActive);
+ if (FALSE != fActive)
+ {
+ c = Menu_AddNavigationContext(hMenu, baseMenu);
+ if (0 != c && total > 0 && 0 != MenuHelper_InsertSeparator(hMenu, total))
+ total++;
+
+ total += c;
+ }
+
+ if (NULL != service)
+ {
+ c = Menu_AddServiceContext(hMenu, baseMenu, service);
+ if (0 != c && total > 0 && 0 != MenuHelper_InsertSeparator(hMenu, total))
+ total++;
+
+ total += c;
+
+ c = Menu_AddPluginContext(hMenu, baseMenu, service);
+ if (0 != c && total > 0 && 0 != MenuHelper_InsertSeparator(hMenu, total))
+ total++;
+
+ total += c;
+ }
+
+
+ if (NULL != activeService)
+ activeService->Release();
+
+ return hMenu;
+}
+
+HMENU Menu_GetMenu(UINT menuKind, ifc_omservice *service)
+{
+ HMENU baseMenu = WASABI_API_LOADMENUW(IDR_CONTEXTMENU);
+ if (NULL == baseMenu)
+ return NULL;
+
+ switch(menuKind)
+ {
+ case MENU_SERVICECONTEXT:
+ return Menu_GetServiceContext(baseMenu, service);
+ }
+
+ return NULL;
+}
+
+BOOL Menu_ReleaseMenu(HMENU hMenu, UINT menuKind)
+{
+ if (NULL == hMenu) return FALSE;
+
+ switch(menuKind)
+ {
+ case MENU_SERVICECONTEXT:
+ return DestroyMenu(hMenu);
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/local_menu.h b/Src/Plugins/Library/ml_webdev/local_menu.h
new file mode 100644
index 00000000..50df5ca3
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/local_menu.h
@@ -0,0 +1,17 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_MENU_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_MENU_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ifc_omservice;
+
+#define MENU_SERVICECONTEXT 0
+
+HMENU Menu_GetMenu(UINT menuKind, ifc_omservice *service);
+BOOL Menu_ReleaseMenu(HMENU hMenu, UINT menuKind);
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/main.cpp b/Src/Plugins/Library/ml_webdev/main.cpp
new file mode 100644
index 00000000..344a49aa
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/main.cpp
@@ -0,0 +1,167 @@
+#include "main.h"
+#include "./navigation.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./external.h"
+#include "./serviceHost.h"
+#include "./import.h"
+#include "../winamp/wa_ipc.h"
+
+
+#include <initguid.h>
+#include <strsafe.h>
+
+static DWORD externalCookie = 0;
+static Navigation *navigation = NULL;
+
+// {BF4F80A7-7470-4b08-8A4C-34382C146202}
+DEFINE_GUID(WebDevLangUid, 0xbf4f80a7, 0x7470, 0x4b08, 0x8a, 0x4c, 0x34, 0x38, 0x2c, 0x14, 0x62, 0x2);
+
+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_webdev.dll)",
+ Plugin_Init,
+ Plugin_Quit,
+ Plugin_MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+HINSTANCE Plugin_GetInstance(void)
+{
+ return plugin.hDllInstance;
+}
+
+HWND Plugin_GetWinamp(void)
+{
+ return plugin.hwndWinampParent;
+}
+
+HWND Plugin_GetLibrary(void)
+{
+ return plugin.hwndLibraryParent;
+}
+
+HRESULT Plugin_GetExternal(ExternalDispatch **ppExternal)
+{
+ if (NULL == ppExternal) return E_POINTER;
+ HRESULT hr = ExternalDispatch::CreateInstance(ppExternal);
+ return hr;
+}
+
+static int Plugin_Init()
+{
+ if (!WasabiApi_Initialize(Plugin_GetInstance()))
+ return 1;
+
+ if (NULL == OMBROWSERMNGR ||
+ NULL == OMSERVICEMNGR ||
+ NULL == OMUTILITY )
+ {
+ return 2;
+ }
+
+ if (NULL != WASABI_API_LNG)
+ {
+ 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;
+ }
+
+ ExternalDispatch *externalDispatch;
+ if (SUCCEEDED(ExternalDispatch::CreateInstance(&externalDispatch)))
+ {
+ DispatchInfo dispatchInfo;
+ dispatchInfo.id = 0;
+ dispatchInfo.name =(LPWSTR)externalDispatch->GetName();
+ dispatchInfo.dispatch = externalDispatch;
+
+ if (0 == SENDWAIPC(Plugin_GetWinamp(), IPC_ADD_DISPATCH_OBJECT, (WPARAM)&dispatchInfo))
+ externalCookie = dispatchInfo.id;
+
+ externalDispatch->Release();
+ }
+
+ if (NULL == navigation)
+ {
+ if (FAILED(Navigation::CreateInstance(&navigation)))
+ {
+ navigation = NULL;
+
+ if (0 != externalCookie)
+ {
+ HWND hWinamp = Plugin_GetWinamp();
+ SENDWAIPC(hWinamp, IPC_REMOVE_DISPATCH_OBJECT, (WPARAM)externalCookie);
+ externalCookie = 0;
+ }
+
+ return 2;
+ }
+ }
+
+ return 0;
+}
+
+static void Plugin_Quit()
+{
+ if (0 != externalCookie)
+ {
+ HWND hWinamp = Plugin_GetWinamp();
+ SENDWAIPC(hWinamp, IPC_REMOVE_DISPATCH_OBJECT, (WPARAM)externalCookie);
+ externalCookie = 0;
+ }
+
+ WebDevServiceHost::ReleseCache();
+
+ if (NULL != navigation)
+ {
+ navigation->Finish();
+ navigation->Release();
+ navigation = NULL;
+ }
+
+ ImportService_SaveRecentUrl();
+
+ WasabiApi_Release();
+}
+
+static INT_PTR Plugin_MessageProc(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ INT_PTR result = 0;
+ if (NULL != navigation &&
+ FALSE != navigation->ProcessMessage(msg, param1, param2, param3, &result))
+ {
+ return result;
+ }
+
+ return FALSE;
+}
+
+HRESULT Plugin_GetNavigation(Navigation **instance)
+{
+ if(NULL == instance) return E_POINTER;
+
+ if (NULL == navigation)
+ {
+ *instance = NULL;
+ return E_UNEXPECTED;
+ }
+
+ *instance = navigation;
+ navigation->AddRef();
+
+ return S_OK;
+}
+
+EXTERN_C __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/main.h b/Src/Plugins/Library/ml_webdev/main.h
new file mode 100644
index 00000000..7762ec9c
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/main.h
@@ -0,0 +1,25 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_MAIN_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_MAIN_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../General/gen_ml/ml.h"
+#include "./common.h"
+
+#define PLUGIN_VERSION_MAJOR 2
+#define PLUGIN_VERSION_MINOR 34
+
+// {BF4F80A7-7470-4b08-8A4C-34382C146202}
+DEFINE_GUID(WebDevLangUid, 0xbf4f80a7, 0x7470, 0x4b08, 0x8a, 0x4c, 0x34, 0x38, 0x2c, 0x14, 0x62, 0x2);
+
+HINSTANCE Plugin_GetInstance(void);
+HWND Plugin_GetWinamp(void);
+HWND Plugin_GetLibrary(void);
+
+class Navigation;
+HRESULT Plugin_GetNavigation(Navigation **instance);
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_MAIN_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/ml_webdev.rc b/Src/Plugins/Library/ml_webdev/ml_webdev.rc
new file mode 100644
index 00000000..37589a2c
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/ml_webdev.rc
@@ -0,0 +1,182 @@
+// 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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENU MENU
+BEGIN
+ POPUP "ViewContext"
+ BEGIN
+ MENUITEM "&Open", ID_VIEW_OPEN
+ MENUITEM "Open in &New Window", ID_VIEW_NEWWINDOW
+ END
+ POPUP "ServiceMngrContext"
+ BEGIN
+ POPUP "&Service"
+ BEGIN
+ MENUITEM "&New", ID_SERVICE_NEW
+ POPUP "&Import"
+ BEGIN
+ MENUITEM "&File...", ID_SERVICE_IMPORT_FILE
+ MENUITEM "&Url..", ID_SERVICE_IMPORT_URL
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Edit", ID_SERVICE_EDIT
+ MENUITEM "&Reload", ID_SERVICE_RELOAD
+ MENUITEM "Reset &Permissions", ID_SERVICE_RESETPERMISSIONS
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete", ID_SERVICE_DELETE
+ MENUITEM "Delete &All", ID_SERVICE_DELETEALL
+ MENUITEM SEPARATOR
+ MENUITEM "Open Containing &Folder", ID_SERVICE_LOCATE
+ MENUITEM "Open in E&xternal Editor", ID_SERVICE_EDITEXTERNAL
+ END
+ END
+ POPUP "NavigationContext"
+ BEGIN
+ MENUITEM "&Back", ID_NAVIGATION_BACK
+ MENUITEM "&Forward", ID_NAVIGATION_FORWARD
+ MENUITEM "&Home", ID_NAVIGATION_HOME
+ MENUITEM "&Refresh", ID_NAVIGATION_REFRESH
+ MENUITEM "Sto&p", ID_NAVIGATION_STOP
+ END
+ POPUP "PluginContext"
+ BEGIN
+ MENUITEM "omBrowser Options...", ID_OMBROWSER_OPTIONS
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// HTML
+//
+
+IDR_HTML_WELCOME HTML ".\\resources\\pages\\welcome.htm"
+IDR_HTML_EDITOR HTML ".\\resources\\pages\\serviceEditor.htm"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_OPENURL DIALOGEX 0, 0, 220, 54
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Open Url"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Enter service list address:",IDC_STATIC,5,4,210,10
+ COMBOBOX IDC_ADDRESS,5,18,210,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ DEFPUSHBUTTON "OK",IDOK,111,36,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,165,36,50,13
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_OPENURL, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 215
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 49
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft WebDev Test Platform v%d.%d"
+ 65535 "{BF4F80A7-7470-4b08-8A4C-34382C146202}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ROOTSERVICE_NAME "WebDev Platform"
+ IDS_DOCSERVICE_NAME "Online Documentation"
+ IDS_TESTSERVICE_NAME "JSAPI2 Test"
+ IDS_USERSERVICE_NAME "My Service"
+ IDS_FILEFILTER_ALL "All Files"
+ IDS_FILEFILTER_ALLKNOWN "All Supported Files"
+ IDS_LOADSERVICE "Load WebDev Service"
+ IDS_UNKNOWNNAME "<Unknown>"
+ IDS_IMPORT_FILES "Import Service..."
+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_webdev/ml_webdev.sln b/Src/Plugins/Library/ml_webdev/ml_webdev.sln
new file mode 100644
index 00000000..5702953f
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/ml_webdev.sln
@@ -0,0 +1,30 @@
+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_webdev", "ml_webdev.vcxproj", "{2DA51094-B28D-4CAC-8C92-8460A6E63504}"
+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
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Debug|Win32.ActiveCfg = Debug|Win32
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Debug|Win32.Build.0 = Debug|Win32
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Debug|x64.ActiveCfg = Debug|x64
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Debug|x64.Build.0 = Debug|x64
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Release|Win32.ActiveCfg = Release|Win32
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Release|Win32.Build.0 = Release|Win32
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Release|x64.ActiveCfg = Release|x64
+ {2DA51094-B28D-4CAC-8C92-8460A6E63504}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B9FEFFB9-5976-4591-B204-3323609B831B}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj b/Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj
new file mode 100644
index 00000000..0d7093e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj
@@ -0,0 +1,382 @@
+<?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>{2DA51094-B28D-4CAC-8C92-8460A6E63504}</ProjectGuid>
+ <RootNamespace>ml_webdev</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;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;TEST_EXPORTS;_DEBUG;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ </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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;TEST_EXPORTS;_DEBUG;WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ </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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;NDEBUG;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <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>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;..\..\..\ombrowser;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;NDEBUG;WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <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>
+ </Link>
+ <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\menuHelpers.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\nu\trace.cpp" />
+ <ClCompile Include="..\..\..\Winamp\JSAPI_CallbackParameters.cpp" />
+ <ClCompile Include="commands.cpp" />
+ <ClCompile Include="common.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="external.cpp" />
+ <ClCompile Include="forceUrl.cpp" />
+ <ClCompile Include="importFile.cpp" />
+ <ClCompile Include="importUrl.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="navigation.cpp" />
+ <ClCompile Include="serviceHelper.cpp" />
+ <ClCompile Include="serviceHost.cpp" />
+ <ClCompile Include="wasabi.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_webdev.rc" />
+ <ResourceCompile Include="png.rc" />
+ <ResourceCompile Include="testPages.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="resource.hi" />
+ <None Include="resources\pages\api2.js" />
+ <None Include="resources\pages\applicationApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\asyncDownloaderApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\bookmarkApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\configApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\historyApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\mediaCoreApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\playlistApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\playQueueApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\podcastApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\securityApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\serviceEditor.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\skinApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\transportApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\transportEventsApi.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\webdev.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ <None Include="resources\pages\webdev.js" />
+ <None Include="resources\pages\welcome.htm">
+ <DeploymentContent>true</DeploymentContent>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="commands.h" />
+ <ClInclude Include="common.h" />
+ <ClInclude Include="config.h" />
+ <ClInclude Include="external.h" />
+ <ClInclude Include="forceUrl.h" />
+ <ClInclude Include="import.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="navigation.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="serviceHelper.h" />
+ <ClInclude Include="serviceHost.h" />
+ <ClInclude Include="wasabi.h" />
+ </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_webdev/ml_webdev.vcxproj.filters b/Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj.filters
new file mode 100644
index 00000000..f880b5ce
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/ml_webdev.vcxproj.filters
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="commands.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="common.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="external.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="forceUrl.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="importFile.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="importUrl.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="navigation.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wasabi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="serviceHost.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="serviceHelper.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\JSAPI_CallbackParameters.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menuHelpers.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\trace.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="commands.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="external.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="forceUrl.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="import.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="navigation.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="serviceHelper.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="serviceHost.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="wasabi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="png.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="testPages.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="ml_webdev.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="resource.hi">
+ <Filter>Ressource Files</Filter>
+ </None>
+ <None Include="resources\pages\api2.js">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\applicationApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\asyncDownloaderApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\bookmarkApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\configApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\historyApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\mediaCoreApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\playlistApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\playQueueApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\podcastApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\securityApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\serviceEditor.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\skinApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\transportApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\transportEventsApi.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\webdev.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\webdev.js">
+ <Filter>HTM Files</Filter>
+ </None>
+ <None Include="resources\pages\welcome.htm">
+ <Filter>HTM Files</Filter>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{ae54a1c4-5018-4345-b9e9-0721b28bca7e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{70f7d7d1-2117-46a3-9271-35a7b3817d6b}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{36a28036-6df6-46f8-a5fc-6846e411aa7a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="HTM Files">
+ <UniqueIdentifier>{db057e45-2170-41cd-85d0-b8250030d0ac}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/navigation.cpp b/Src/Plugins/Library/ml_webdev/navigation.cpp
new file mode 100644
index 00000000..ebbe555f
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/navigation.cpp
@@ -0,0 +1,1261 @@
+#include "main.h"
+#include "./navigation.h"
+#include "./resource.h"
+#include "./wasabi.h"
+#include "./local_menu.h"
+#include "./commands.h"
+#include "./forceUrl.h"
+#include "./config.h"
+
+#include "../omBrowser/browserView.h"
+#include "../winamp/wa_ipc.h"
+
+#include "./serviceHost.h"
+#include "./serviceHelper.h"
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+#include <storageIni.h>
+#include <ifc_omserviceenum.h>
+#include <ifc_mlnavigationhelper.h>
+#include <ifc_omserviceeventmngr.h>
+#include <ifc_ombrowserwndmngr.h>
+#include <ifc_ombrowserwndenum.h>
+
+#define _ML_HEADER_IMPMLEMENT
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#undef _ML_HEADER_IMPMLEMENT
+#include "../../General/gen_ml/menu.h"
+
+#include <vector>
+#include "../nu/sort.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <algorithm>
+
+#define NAVITEM_PREFIX L"webdev_svc_"
+#define E_NAVITEM_UNKNOWN E_NOINTERFACE
+
+
+static BOOL Navigation_CheckInvariantName(LPCWSTR pszInvarian)
+{
+ INT cchInvariant = (NULL != pszInvarian) ? lstrlen(pszInvarian) : 0;
+ INT cchPrefix = ARRAYSIZE(NAVITEM_PREFIX) - 1;
+ return (cchInvariant > cchPrefix &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, 0, NAVITEM_PREFIX, cchPrefix, pszInvarian, cchPrefix));
+}
+
+
+Navigation::Navigation()
+ : ref(1), cookie(0), hRoot(NULL), hLibrary(NULL)
+{
+}
+
+Navigation::~Navigation()
+{
+}
+
+HRESULT Navigation::CreateInstance(Navigation **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ HRESULT hr;
+
+ Navigation *navigation = new Navigation();
+ if (NULL != navigation)
+ {
+ hr = navigation->Initialize();
+ if (FAILED(hr))
+ {
+ navigation->Release();
+ navigation = NULL;
+ }
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ *instance = navigation;
+ return hr;
+}
+
+size_t Navigation::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t Navigation::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int Navigation::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_MlNavigationCallback))
+ *object = static_cast<ifc_mlnavigationcallback*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT Navigation::Initialize()
+{
+ hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ if (0 == cookie)
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->RegisterCallback(this, &cookie);
+ navHelper->Release();
+ }
+ }
+
+ ifc_omservice *service;
+
+ MLNavCtrl_BeginUpdate(hLibrary, NUF_LOCK_TOP);
+
+ if (SUCCEEDED(ServiceHelper_Create(700, MAKEINTRESOURCE(IDS_ROOTSERVICE_NAME),
+ NULL, MAKEINTRESOURCE(IDR_HTML_WELCOME),
+ WDSVCF_ROOT, FALSE, &service)))
+ {
+ hRoot = CreateItemInt(NULL, service);
+ service->Release();
+ }
+
+ if (NULL == hRoot)
+ {
+ MLNavCtrl_EndUpdate(hLibrary);
+ return E_FAIL;
+ }
+
+
+ if (SUCCEEDED(ServiceHelper_Create(701, MAKEINTRESOURCE(IDS_DOCSERVICE_NAME),
+ MAKEINTRESOURCE(IDR_HELP_ICON), L"http://dev.winamp.com/online-service-developers",
+ WDSVCF_SPECIAL, FALSE, &service)))
+ {
+ CreateItemInt(hRoot, service);
+ service->Release();
+ }
+
+ if (SUCCEEDED(ServiceHelper_Create(702, MAKEINTRESOURCE(IDS_TESTSERVICE_NAME),
+ MAKEINTRESOURCE(IDR_GEAR_ICON), MAKEINTRESOURCE(IDR_HTML_TEST),
+ WDSVCF_SPECIAL, FALSE, &service)))
+ {
+ CreateItemInt(hRoot, service);
+ service->Release();
+ }
+
+ ifc_omstorage *storage;
+ if (NULL != OMSERVICEMNGR && SUCCEEDED(OMSERVICEMNGR->QueryStorage(&SUID_OmStorageIni, &storage)))
+ {
+ ifc_omserviceenum *enumerator;
+ WebDevServiceHost *serviceHost;
+ if (FAILED(WebDevServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ if (SUCCEEDED(storage->Load(L"*.ini", serviceHost, &enumerator)))
+ {
+ ifc_omservice *service;
+ std::vector<ifc_omservice*> serviceList;
+ while (S_OK == enumerator->Next(1, &service, NULL))
+ {
+ serviceList.push_back(service);
+ }
+ enumerator->Release();
+
+ size_t count = serviceList.size();
+ Order(serviceList);
+ for(size_t i =0; i < count; i++)
+ {
+ service = serviceList[i];
+ CreateItemInt(hRoot, service);
+ service->Release();
+ }
+ }
+ storage->Release();
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+ }
+
+ MLNavCtrl_EndUpdate(hLibrary);
+
+ return S_OK;
+}
+
+HRESULT Navigation::Finish()
+{
+ ForceUrl_Remove();
+
+ if (0 != cookie)
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->UnregisterCallback(cookie);
+ navHelper->Release();
+ }
+ }
+
+ if (NULL != OMBROWSERMNGR)
+ {
+ OMBROWSERMNGR->Finish();
+ }
+
+ return S_OK;
+}
+
+HRESULT Navigation::SaveOrder()
+{
+ if (NULL == hRoot || NULL == hLibrary)
+ return E_UNEXPECTED;
+
+ LPSTR buffer = NULL;
+ INT count = MLNavItem_GetChildrenCount(hLibrary, hRoot);
+ if (count > 0)
+ {
+ size_t bufferMax = 11 * count;
+ buffer = Plugin_MallocAnsiString(bufferMax);
+ if (NULL == buffer) return E_OUTOFMEMORY;
+ *buffer = '\0';
+
+ LPSTR cursor = buffer;
+ size_t remaining = bufferMax;
+
+ NAVITEM item;
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_PARAM;
+ item.hItem = MLNavItem_GetChild(hLibrary, hRoot);
+ while (NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service)
+ {
+ UINT serviceFlags;
+ if (SUCCEEDED(service->GetFlags(&serviceFlags)) &&
+ 0 == ((WDSVCF_ROOT | WDSVCF_SPECIAL) & serviceFlags))
+ {
+ if (cursor == buffer ||
+ SUCCEEDED(StringCchCopyExA(cursor, remaining, ";", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE)))
+ {
+ StringCchPrintfExA(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, "%d", service->GetId());
+ }
+ }
+ }
+ }
+ item.hItem = MLNavItem_GetNext(hLibrary, item.hItem);
+ }
+
+ }
+
+ Config_WriteStr("Navigation", "order", buffer);
+ Plugin_FreeAnsiString(buffer);
+
+ return S_OK;
+}
+
+//static int __fastcall Navigation_OrderComparer(const void *elem1, const void *elem2, const void *context)
+//{
+// std::vector<UINT> *orderList = (std::vector<UINT>*)context;
+//
+// UINT serviceId;
+// size_t index1, index2;
+// size_t count = orderList->size();
+//
+// serviceId = (*(ifc_omservice**)elem1)->GetId();
+// for (index1 = 0; index1 < count && serviceId != orderList->at(index1); index1++);
+//
+// serviceId = (*(ifc_omservice**)elem2)->GetId();
+// for (index2 = 0; index2 < count && serviceId != orderList->at(index2); index2++);
+//
+// return (INT)(index1 - index2);
+//}
+class Navigation_OrderComparer
+{
+public:
+ Navigation_OrderComparer(const void* ctx)
+ : context(ctx)
+ {
+ }
+
+ bool operator()(const void* elem1, const void* elem2)
+ {
+ std::vector<UINT>* orderList = (std::vector<UINT>*)context;
+
+ UINT serviceId;
+ size_t index1, index2;
+ size_t count = orderList->size();
+
+ serviceId = ((ifc_omservice*)elem1)->GetId();
+ for (index1 = 0; index1 < count && serviceId != orderList->at(index1); index1++);
+
+ serviceId = ((ifc_omservice*)elem2)->GetId();
+ for (index2 = 0; index2 < count && serviceId != orderList->at(index2); index2++);
+
+ return (INT)(index1 - index2) < 0;
+ }
+
+
+private:
+ const void* context;
+};
+HRESULT Navigation::Order(std::vector<ifc_omservice*> &list)
+{
+ size_t listSize = list.size();
+
+ if (listSize < 2)
+ return S_FALSE;
+
+ //if (NULL == list) return E_INVALIDARG;
+
+ size_t bufferMax = 16384;
+ LPSTR buffer = Plugin_MallocAnsiString(bufferMax);
+ if (NULL == buffer) return E_OUTOFMEMORY;
+
+ UINT len = Config_ReadStr("Navigation", "order", NULL, buffer, (UINT)bufferMax);
+ std::vector<UINT> orderList;
+
+ LPCSTR end = buffer + len;
+ LPCSTR block = buffer;
+ LPCSTR cursor = block;
+ for(;;)
+ {
+ if (cursor == end || ';' == *cursor)
+ {
+ if (block != cursor)
+ {
+ INT serviceId;
+ if (FALSE != StrToIntExA(block, STIF_DEFAULT, &serviceId))
+ orderList.push_back(serviceId);
+ }
+
+ if (cursor == end) break;
+ cursor++;
+ block = cursor;
+ }
+ cursor++;
+ }
+
+
+ if (0 != orderList.size())
+ {
+ //nu::qsort(list, listSize, sizeof(ifc_omservice*), &orderList, Navigation_OrderComparer);
+ std::sort(list.begin(), list.end(), Navigation_OrderComparer(&orderList));
+ }
+
+ Plugin_FreeAnsiString(buffer);
+ return S_OK;
+}
+
+void Navigation::ImageChanged(LPCWSTR pszName, INT index)
+{
+ if (NULL == hRoot || NULL == hLibrary || NULL == pszName)
+ return;
+
+ WCHAR szBuffer[2048] = {0};
+ NAVITEM item = {0};
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
+ item.pszInvariant = szBuffer;
+ item.cchInvariantMax = ARRAYSIZE(szBuffer);
+
+ item.hItem = hRoot;
+
+ while (NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item) &&
+ FALSE != Navigation_CheckInvariantName(item.pszInvariant))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service &&
+ SUCCEEDED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer))) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, szBuffer, -1, pszName, -1))
+ {
+
+ item.iImage = index;
+ item.iSelectedImage = index;
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(hLibrary, &item);
+ return;
+ }
+ }
+
+ item.hItem = (HNAVITEM)SENDMLIPC(hLibrary,
+ (item.hItem == hRoot) ? ML_IPC_NAVITEM_GETCHILD : ML_IPC_NAVITEM_GETNEXT,
+ (WPARAM)item.hItem);
+
+ }
+}
+
+
+
+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;
+
+ HRESULT hr;
+
+ switch(msg)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ {
+ HWND hView;
+ hr = OnCreateView(GetMessageItem(msg, param1), (HWND)param2, &hView);
+ *result = (SUCCEEDED(hr)) ? (INT_PTR)hView : NULL;
+ }
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ hr = OnContextMenu(GetMessageItem(msg, param1), (HWND)param2, MAKEPOINTS(param3));
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONDELETE:
+ hr = OnDeleteItem(GetMessageItem(msg, param1));
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONENDTITLEEDIT:
+ hr = OnEndTitleEdit(GetMessageItem(msg, param1), (LPCWSTR)param2);
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_TREE_ONKEYDOWN:
+ hr = OnKeyDown(GetMessageItem(msg, param1), (NMTVKEYDOWN*)param2);
+ *result = SUCCEEDED(hr);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONDESTROY:
+ OnControlDestroy();
+ *result = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+HNAVITEM Navigation::GetActive(ifc_omservice **serviceOut)
+{
+ ifc_omservice *service;
+ HNAVITEM hActive = (NULL != hLibrary) ? MLNavCtrl_GetSelection(hLibrary) : NULL;
+ if (NULL == hActive || FAILED(GetService(hActive, &service)))
+ {
+ hActive = NULL;
+ service = NULL;
+ }
+
+ if (NULL != serviceOut)
+ *serviceOut = service;
+ else if (NULL != service)
+ service->Release();
+
+ return hActive;
+}
+
+HWND Navigation::GetActiveView(ifc_omservice **serviceOut)
+{
+ HWND hView = (NULL != hLibrary) ? ((HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0)) : NULL;
+ if (NULL != hView)
+ {
+ WCHAR szBuffer[128] = {0};
+ if (!GetClassName(hView, szBuffer, ARRAYSIZE(szBuffer)) ||
+ CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, szBuffer, -1,
+ L"Nullsoft_omBrowserView", -1))
+ {
+ hView = NULL;
+ }
+ }
+
+ ifc_omservice *service;
+ if (NULL == hView || FALSE == BrowserView_GetService(hView, &service))
+ {
+ hView = NULL;
+ service = NULL;
+ }
+
+ if (NULL != serviceOut)
+ *serviceOut = service;
+ else if (NULL != service)
+ service->Release();
+
+ return hView;
+}
+
+HRESULT Navigation::SelectItem(HNAVITEM hItem, LPCWSTR pszUrl)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+
+ ifc_omservice *service;
+ HRESULT hr = GetService(hItem, &service);
+ if (FAILED(hr)) return hr;
+
+ hr = SelectItemInt(hItem, service->GetId(), pszUrl);
+ service->Release();
+
+ return hr;
+}
+
+HRESULT Navigation::DeleteItem(HNAVITEM hItem)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ ifc_omservice *service;
+ if (FAILED(GetService(hItem, &service)))
+ return E_FAIL;
+
+ UINT serviceFlags;
+ if (FAILED(service->GetFlags(&serviceFlags))) serviceFlags = 0;
+ if (0 != ((WDSVCF_ROOT | WDSVCF_SPECIAL) & serviceFlags))
+ return E_NOTIMPL;
+
+ MLNavCtrl_BeginUpdate(hLibrary, 0);
+ HNAVITEM hSelection = MLNavCtrl_GetSelection(hLibrary);
+ if (hSelection == hItem)
+ {
+ HNAVITEM hNext = MLNavItem_GetNext(hLibrary, hItem);
+ if (NULL == hNext)
+ hNext = MLNavItem_GetPrevious(hLibrary, hItem);
+
+ if (NULL != hNext)
+ {
+ MLNavItem_Select(hLibrary, hNext);
+ }
+ }
+
+ BOOL result = MLNavCtrl_DeleteItem(hLibrary, hItem);
+ HRESULT hr = (FALSE != result) ? S_OK : E_FAIL;
+
+ MLNavCtrl_EndUpdate(hLibrary);
+
+ if (SUCCEEDED(hr))
+ ServiceHelper_Delete(service);
+
+ service->Release();
+
+ return hr;
+}
+
+HRESULT Navigation::DeleteAll()
+{
+ if (NULL == hRoot || NULL == hLibrary) return E_UNEXPECTED;
+
+ std::vector<HNAVITEM> itemList;
+ HNAVITEM hItem = MLNavItem_GetChild(hLibrary, hRoot);
+ while (NULL != hItem)
+ {
+ itemList.push_back(hItem);
+ hItem = MLNavItem_GetNext(hLibrary, hItem);
+ }
+
+ MLNavCtrl_BeginUpdate(hLibrary, 0);
+
+ NAVITEM item;
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_PARAM;
+
+ size_t index = itemList.size();
+ while(index--)
+ {
+ item.hItem = itemList[index];
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service)
+ {
+ service->AddRef();
+
+ UINT serviceFlags;
+ if (SUCCEEDED(service->GetFlags(&serviceFlags)) &&
+ 0 == ((WDSVCF_ROOT | WDSVCF_SPECIAL) & serviceFlags) &&
+ FALSE != MLNavCtrl_DeleteItem(hLibrary, item.hItem))
+ {
+ ServiceHelper_Delete(service);
+ }
+
+ service->Release();
+ }
+ }
+ }
+
+ MLNavCtrl_EndUpdate(hLibrary);
+ return S_OK;
+}
+
+HRESULT Navigation::CreatePopup(HNAVITEM hItem, HWND *hwnd)
+{
+ if (NULL == hwnd) return E_POINTER;
+ *hwnd = NULL;
+
+ if (NULL == hLibrary) return E_UNEXPECTED;
+ if (NULL == hItem) return E_INVALIDARG;
+
+ HRESULT hr;
+
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ HWND hWinamp = Plugin_GetWinamp();
+
+ if (NULL == OMBROWSERMNGR)
+ hr = E_UNEXPECTED;
+
+ if (SUCCEEDED(hr))
+ {
+ hr = OMBROWSERMNGR->Initialize(NULL, hWinamp);
+ if (SUCCEEDED(hr))
+ {
+ RECT rect;
+ HWND hFrame = (HWND)SENDMLIPC(hLibrary, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == hFrame) hFrame = hLibrary;
+ if (NULL == hFrame || FALSE == GetWindowRect(hFrame, &rect))
+ {
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ rect.left += 16;
+ rect.top += 16;
+
+ hr = OMBROWSERMNGR->CreatePopup(service, rect.left, rect.top,
+ rect.right - rect.left, rect.bottom - rect.top, hWinamp, NULL, 0, hwnd);
+ }
+ }
+ }
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::GetService(HNAVITEM hItem, ifc_omservice **service)
+{
+ WCHAR szBuffer[64] = {0};
+
+ if (NULL == service) return E_POINTER;
+ *service = NULL;
+
+ if (NULL == hLibrary || NULL == hItem)
+ return E_INVALIDARG;
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(NAVITEM);
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT;
+
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+
+ if (FALSE == Navigation_CheckInvariantName(szBuffer))
+ return E_NAVITEM_UNKNOWN;
+
+ *service = (ifc_omservice*)itemInfo.lParam;
+ (*service)->AddRef();
+ return S_OK;
+}
+
+static void CALLBACK Navigtaion_UpdateServiceApc(Dispatchable *object, ULONG_PTR param1, ULONG_PTR param2)
+{
+ Navigation *navigation = (Navigation*)object;
+ if (NULL != navigation)
+ {
+ ifc_omservice *service = (ifc_omservice*)param1;
+ navigation->UpdateService(service, (UINT)param2);
+ if (NULL != service) service->Release();
+ }
+}
+
+HRESULT Navigation::UpdateService(ifc_omservice *service, UINT modifiedFlags)
+{
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ DWORD libraryTID = GetWindowThreadProcessId(hLibrary, NULL);
+ DWORD currentTID = GetCurrentThreadId();
+ if (libraryTID != currentTID)
+ {
+ if (NULL != OMUTILITY)
+ {
+ service->AddRef();
+ if (FAILED(OMUTILITY->PostMainThreadCallback2(Navigtaion_UpdateServiceApc, this, (ULONG_PTR)service, (ULONG_PTR)modifiedFlags)))
+ service->Release();
+ }
+ return E_PENDING;
+ }
+
+ HNAVITEM hItem = FindService(service->GetId(), NULL);
+ if (NULL == hItem)
+ return E_FAIL;
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(NAVITEM);
+ itemInfo.hItem = hItem;
+
+ itemInfo.mask = NIMF_IMAGE;
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ itemInfo.iImage= -1;
+
+ itemInfo.mask = 0;
+
+ WCHAR szName[512] = {0};
+ if (0 != (ifc_omserviceeditor::modifiedName & modifiedFlags) &&
+ SUCCEEDED(service->GetName(szName, ARRAYSIZE(szName))))
+ {
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = szName;
+ }
+
+
+ if (0 != (ifc_omserviceeditor::modifiedIcon & modifiedFlags))
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ INT iImage;
+ WCHAR szIcon[1024] = {0};
+ if (FAILED(service->GetIcon(szIcon, ARRAYSIZE(szIcon))) ||
+ FAILED(navHelper->QueryIndex(szIcon, &iImage, NULL)))
+ {
+ iImage = -1;
+ }
+
+ if (itemInfo.iImage != iImage)
+ {
+ itemInfo.mask |= NIMF_IMAGE | NIMF_IMAGESEL;
+ itemInfo.iImage = iImage;
+ itemInfo.iSelectedImage = iImage;
+ }
+
+ navHelper->Release();
+ }
+ }
+
+ if (0 != itemInfo.mask)
+ {
+ if (FALSE == MLNavItem_SetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+ }
+
+ NAVITEMINAVLIDATE invalidate;
+ invalidate.hItem = hItem;
+ invalidate.fErase = FALSE;
+ invalidate.prc = NULL;
+ MLNavItem_Invalidate(hLibrary, &invalidate);
+
+ return S_OK;
+}
+
+HNAVITEM Navigation::FindService(UINT serviceId, ifc_omservice **serviceOut)
+{
+ if (NULL == hRoot || NULL == hLibrary)
+ {
+ if (NULL != serviceOut) *serviceOut = NULL;
+ return NULL;
+ }
+
+ WCHAR szBuffer[128] = {0};
+ NAVITEM item = {0};
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
+ item.pszInvariant = szBuffer;
+ item.cchInvariantMax = ARRAYSIZE(szBuffer);
+
+ item.hItem = hRoot;
+
+ while (NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item) &&
+ FALSE != Navigation_CheckInvariantName(item.pszInvariant))
+ {
+ ifc_omservice *service = (ifc_omservice*)item.lParam;
+ if (NULL != service && serviceId == service->GetId())
+ {
+ if (NULL != serviceOut)
+ {
+ *serviceOut = service;
+ service->AddRef();
+ }
+ return item.hItem;
+ }
+ }
+
+ item.hItem = (HNAVITEM)SENDMLIPC(hLibrary,
+ (item.hItem == hRoot) ? ML_IPC_NAVITEM_GETCHILD : ML_IPC_NAVITEM_GETNEXT,
+ (WPARAM)item.hItem);
+
+ }
+
+ if (NULL != serviceOut) *serviceOut = NULL;
+ return NULL;
+}
+
+HRESULT Navigation::ShowService(UINT serviceId, LPCWSTR pszUrl)
+{
+ ifc_omservice *service;
+ HNAVITEM hItem = FindService(serviceId, &service);
+ if (NULL == hItem) return E_FAIL;
+
+ HRESULT hr = SelectItemInt(hItem, serviceId, pszUrl);
+ service->Release();
+
+ return hr;
+}
+
+HNAVITEM Navigation::CreateItem(ifc_omservice *service)
+{
+ if (NULL == hLibrary || NULL == hRoot) return NULL;
+ return CreateItemInt(hRoot, service);
+}
+
+HRESULT Navigation::GenerateServiceName(LPWSTR pszBuffer, INT cchBufferMax)
+{
+ if (NULL == pszBuffer) return E_POINTER;
+ *pszBuffer = L'\0';
+
+ if (NULL == hLibrary || NULL == hRoot) return E_UNEXPECTED;
+
+ if (FAILED(Plugin_CopyResString(pszBuffer, cchBufferMax, MAKEINTRESOURCE(IDS_USERSERVICE_NAME))))
+ return E_UNEXPECTED;
+
+ INT cchName = lstrlen(pszBuffer);
+ LPWSTR pszFormat = pszBuffer + cchName;
+ INT cchFormatMax = cchBufferMax - cchName;
+
+ WCHAR szText[512] = {0};
+ NAVITEM item = {0};
+
+ item.cbSize = sizeof(item);
+ item.mask = NIMF_TEXT;
+ item.pszText = szText;
+ item.cchTextMax = ARRAYSIZE(szText);
+
+ BOOL fFound = TRUE;
+
+ for(INT index = 1; FALSE != fFound; index++)
+ {
+ fFound = FALSE;
+ if (FAILED(StringCchPrintf(pszFormat, cchFormatMax, L" %d", index)))
+ {
+ pszFormat = L'\0';
+ return E_FAIL;
+ }
+
+ item.hItem = MLNavItem_GetChild(hLibrary, hRoot);
+ while(NULL != item.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &item) &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item.pszText, -1, pszBuffer, -1))
+ {
+ fFound = TRUE;
+ break;
+ }
+ item.hItem = MLNavItem_GetNext(hLibrary, item.hItem);
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Navigation::CreateUserService(HNAVITEM *itemOut)
+{
+ HRESULT hr;
+
+ if (NULL != itemOut)
+ *itemOut = NULL;
+
+ if (NULL == hRoot) return E_FAIL;
+
+ INT serviceId = 710;
+ while(NULL != FindService(serviceId, NULL)) serviceId++;
+
+ WCHAR szName[256];
+ if (FAILED(GenerateServiceName(szName, ARRAYSIZE(szName))))
+ return E_FAIL;
+
+ ifc_omservice *service;
+ hr = ServiceHelper_Create(serviceId, szName, NULL, L"about:blank", 0, TRUE, &service);
+
+ if (SUCCEEDED(hr))
+ {
+ HNAVITEM hItem = CreateItem(service);
+ if (NULL == hItem)
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ if (NULL != itemOut)
+ *itemOut = hItem;
+ }
+
+ service->Release();
+ }
+
+ return hr;
+}
+
+HNAVITEM Navigation::CreateItemInt(HNAVITEM hParent, ifc_omservice *service)
+{
+ if (NULL == service) return NULL;
+ ServiceHelper_RegisterPreAuthorized(service);
+
+ WCHAR szName[256] = {0}, szInvariant[64] = {0};
+ if (FAILED(service->GetName(szName, ARRAYSIZE(szName))))
+ return NULL;
+
+ if (L'\0' == szName[0])
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWNNAME, szName, ARRAYSIZE(szName));
+
+ if (FAILED(StringCchPrintf(szInvariant, ARRAYSIZE(szInvariant), NAVITEM_PREFIX L"%u", service->GetId())))
+ return NULL;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.hInsertAfter = NULL;
+ nis.hParent = hParent;
+
+ INT iIcon = -1;
+ ifc_mlnavigationhelper *navHelper;
+ if (NULL != OMUTILITY && SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ WCHAR szIcon[2048] = {0};
+ if (FAILED(service->GetIcon(szIcon, ARRAYSIZE(szIcon))) ||
+ FAILED(navHelper->QueryIndex(szIcon, &iIcon, NULL)))
+ {
+ iIcon = -1;
+ }
+ navHelper->Release();
+ }
+
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT | NIMF_PARAM | NIMF_IMAGE | NIMF_IMAGESEL;
+
+ nis.item.id = 0;
+ nis.item.pszText = szName;
+ nis.item.pszInvariant = szInvariant;
+ nis.item.lParam = (LPARAM)service;
+
+ nis.item.style = 0;
+ UINT serviceFlags;
+ if (FAILED(service->GetFlags(&serviceFlags)))
+ serviceFlags = 0;
+
+ if (0 != (WDSVCF_ROOT & serviceFlags))
+ {
+ nis.item.style |= (NIS_HASCHILDREN | NIS_ALLOWCHILDMOVE);
+ iIcon = -1;
+ }
+ else if (0 != (WDSVCF_SPECIAL & serviceFlags))
+ nis.item.style |= NIS_ITALIC;
+ else
+ nis.item.style |= NIS_ALLOWEDIT;
+
+ nis.item.styleMask = nis.item.style;
+
+ nis.item.iImage = iIcon;
+ nis.item.iSelectedImage = iIcon;
+
+
+ HNAVITEM hItem = MLNavCtrl_InsertItem(hLibrary, &nis);
+ if (NULL != hItem)
+ {
+ WebDevServiceHost *serviceHost;
+ if (SUCCEEDED(WebDevServiceHost::GetCachedInstance(&serviceHost)))
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->RegisterHandler(serviceHost);
+ eventManager->Release();
+ }
+
+ serviceHost->Release();
+ }
+
+ service->AddRef();
+ }
+
+ return hItem;
+}
+
+HRESULT Navigation::SelectItemInt(HNAVITEM hItem, UINT serviceId, LPCWSTR pszUrl)
+{
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ if (NULL != pszUrl && L'\0' != *pszUrl)
+ {
+ HRESULT hr = ForceUrl_Set(serviceId, pszUrl);
+ if (FAILED(hr)) return hr;
+ }
+ else
+ {
+ ForceUrl_Remove();
+ }
+
+ if (FALSE == MLNavItem_Select(hLibrary, hItem))
+ {
+ ForceUrl_Remove();
+ return E_FAIL;
+ }
+
+ return S_OK;
+
+}
+HNAVITEM Navigation::GetMessageItem(INT msg, INT_PTR param1)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(hLibrary, param1) :
+ (HNAVITEM)param1;
+}
+
+HRESULT Navigation::OnCreateView(HNAVITEM hItem, HWND hParent, HWND *hView)
+{
+ if (NULL == hView) return E_POINTER;
+ *hView = NULL;
+
+ if (NULL == hLibrary) return E_UNEXPECTED;
+ if (NULL == hItem || NULL == hParent) return E_INVALIDARG;
+
+ HRESULT hr;
+
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ if (NULL == OMBROWSERMNGR)
+ hr = E_UNEXPECTED;
+
+ if (SUCCEEDED(hr))
+ {
+ hr = OMBROWSERMNGR->Initialize(NULL, Plugin_GetWinamp());
+ if (SUCCEEDED(hr))
+ {
+ LPCWSTR forceUrl;
+ if (FAILED(ForceUrl_Get(service->GetId(), &forceUrl)))
+ forceUrl = NULL;
+
+ hr = OMBROWSERMNGR->CreateView(service, hParent, forceUrl, 0, hView);
+
+ ForceUrl_Remove();
+ }
+ }
+
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::OnContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts)
+{
+ if (NULL == hItem || NULL == hHost)
+ return E_INVALIDARG;
+
+ HWND hLibrary = Plugin_GetLibrary();
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ HRESULT hr;
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (FAILED(hr)) return hr;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(hLibrary, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU hMenu = Menu_GetMenu(MENU_SERVICECONTEXT, service);
+ if (NULL != hMenu)
+ {
+ INT commandId = Menu_TrackPopup(Plugin_GetLibrary(), hMenu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ pt.x, pt.y, hHost, NULL);
+ Menu_ReleaseMenu(hMenu, MENU_SERVICECONTEXT);
+ CommandManager_Process(hItem, service, commandId);
+ }
+
+ service->Release();
+
+ return hr;
+}
+
+HRESULT Navigation::OnEndTitleEdit(HNAVITEM hItem, LPCWSTR pszNewTitle)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ HRESULT hr;
+
+ ifc_omservice *service;
+ hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != pszNewTitle)
+ {
+ ifc_omserviceeditor *editor;
+ hr = service->QueryInterface(IFC_OmServiceEditor, (void**)&editor);
+ if (SUCCEEDED(hr))
+ {
+ hr = editor->SetName(pszNewTitle, FALSE);
+ editor->Release();
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ ServiceHelper_Save(service);
+
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::OnDeleteItem(HNAVITEM hItem)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ WCHAR szBuffer[2048] = {0};
+ NAVITEM itemInfo = {0};
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT | NIMF_IMAGE;
+
+ if (FALSE == MLNavItem_GetInfo(hLibrary, &itemInfo))
+ return E_FAIL;
+ if (FALSE == Navigation_CheckInvariantName(szBuffer))
+ return E_NAVITEM_UNKNOWN;
+
+ ifc_omservice *service = (ifc_omservice*)itemInfo.lParam;
+
+ if (NULL != service)
+ {
+ if (SUCCEEDED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer))))
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->ReleaseIndex(szBuffer);
+ navHelper->Release();
+ }
+ }
+
+ ifc_ombrowserwndmngr *windowManager;
+ if (NULL != OMBROWSERMNGR && SUCCEEDED(OMBROWSERMNGR->QueryInterface(IFC_OmBrowserWindowManager, (void**)&windowManager)))
+ {
+ UINT serviceId = service->GetId();
+
+ ifc_ombrowserwndenum *windowEnum;
+ if (SUCCEEDED(windowManager->Enumerate(NULL, &serviceId, &windowEnum)))
+ {
+ HWND hwnd;
+ while (S_OK == windowEnum->Next(1, &hwnd, NULL))
+ {
+ DestroyWindow(hwnd);
+ }
+ windowEnum->Release();
+ }
+
+ windowManager->Release();
+ }
+
+ WebDevServiceHost *serviceHost;
+ if (SUCCEEDED(WebDevServiceHost::GetCachedInstance(&serviceHost)))
+ {
+ ifc_omserviceeventmngr *eventManager;
+ if (SUCCEEDED(service->QueryInterface(IFC_OmServiceEventMngr, (void**)&eventManager)))
+ {
+ eventManager->UnregisterHandler(serviceHost);
+ eventManager->Release();
+ }
+ serviceHost->Release();
+ }
+
+ itemInfo.mask = NIMF_PARAM;
+ itemInfo.lParam = 0L;
+ MLNavItem_SetInfo(hLibrary, &itemInfo);
+
+ service->Release();
+ }
+
+
+
+ return S_OK;
+}
+
+HRESULT Navigation::OnKeyDown(HNAVITEM hItem, NMTVKEYDOWN *pnmkd)
+{
+ if (NULL == hItem) return E_INVALIDARG;
+ if (NULL == hLibrary) return E_UNEXPECTED;
+
+ ifc_omservice *service;
+ HRESULT hr = GetService(hItem, &service);
+ if (SUCCEEDED(hr))
+ {
+ switch(pnmkd->wVKey)
+ {
+ case VK_DELETE:
+ Command_DeleteItem(hItem);
+ break;
+ }
+
+ service->Release();
+ }
+ return hr;
+}
+
+HRESULT Navigation::OnControlDestroy()
+{
+ SaveOrder();
+ return S_OK;
+}
+
+#define CBCLASS Navigation
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+VCB(API_IMAGECHANGED, ImageChanged)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/navigation.h b/Src/Plugins/Library/ml_webdev/navigation.h
new file mode 100644
index 00000000..c2575a0a
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/navigation.h
@@ -0,0 +1,83 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_NAVIGATION_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_NAVIGATION_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <ifc_mlnavigationcallback.h>
+#include <vector>
+
+typedef LPVOID HNAVITEM;
+class ifc_omservice;
+
+class Navigation : public ifc_mlnavigationcallback
+{
+protected:
+ Navigation();
+ ~Navigation();
+
+public:
+ static HRESULT CreateInstance(Navigation **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_mlnavigationcallback */
+ void ImageChanged(LPCWSTR pszName, INT index);
+
+public:
+ HRESULT Finish();
+
+ BOOL ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result);
+
+ HNAVITEM GetActive(ifc_omservice **serviceOut);
+ HWND GetActiveView(ifc_omservice **serviceOut);
+
+ HRESULT SelectItem(HNAVITEM hItem, LPCWSTR pszUrl);
+ HRESULT DeleteItem(HNAVITEM hItem);
+ HRESULT DeleteAll();
+
+ HRESULT CreatePopup(HNAVITEM hItem, HWND *hwnd);
+
+ HRESULT GetService(HNAVITEM hItem, ifc_omservice **service);
+ HRESULT UpdateService(ifc_omservice *service, UINT modifiedFlags);
+ HNAVITEM FindService(UINT serviceId, ifc_omservice **serviceOut);
+ HRESULT ShowService(UINT serviceId, LPCWSTR pszUrl);
+
+ HNAVITEM CreateItem(ifc_omservice *service);
+ HRESULT CreateUserService(HNAVITEM *itemOut);
+
+protected:
+ HRESULT Initialize();
+ HRESULT SaveOrder();
+ HRESULT Order(std::vector<ifc_omservice *>&list);
+
+ HNAVITEM GetMessageItem(INT msg, INT_PTR param1);
+ HNAVITEM CreateItemInt(HNAVITEM hParent, ifc_omservice *service);
+ HRESULT SelectItemInt(HNAVITEM hItem, UINT serviceId, LPCWSTR pszUrl);
+
+ HRESULT GenerateServiceName(LPWSTR pszBuffer, INT cchBufferMax);
+
+ HRESULT OnCreateView(HNAVITEM hItem, HWND hParent, HWND *hView);
+ HRESULT OnContextMenu(HNAVITEM hItem, HWND hHost, POINTS pts);
+ HRESULT OnEndTitleEdit(HNAVITEM hItem, LPCWSTR pszNewTitle);
+ HRESULT OnDeleteItem(HNAVITEM hItem);
+ HRESULT OnKeyDown(HNAVITEM hItem, NMTVKEYDOWN *pnmkd);
+ HRESULT OnControlDestroy();
+
+protected:
+ size_t ref;
+ HNAVITEM hRoot;
+ HWND hLibrary;
+ UINT cookie;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_NAVIGATION_HEADER
diff --git a/Src/Plugins/Library/ml_webdev/png.rc b/Src/Plugins/Library/ml_webdev/png.rc
new file mode 100644
index 00000000..12f4920e
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/png.rc
@@ -0,0 +1,9 @@
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Data
+//
+IDR_HELP_ICON RCDATA
+".\\resources\\help.png"
+IDR_GEAR_ICON RCDATA
+".\\resources\\gear.png" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resource.h b/Src/Plugins/Library/ml_webdev/resource.h
new file mode 100644
index 00000000..8a64aac5
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resource.h
@@ -0,0 +1,60 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_webdev.rc
+//
+#define IDS_ROOTSERVICE_NAME 101
+#define IDS_DOCSERVICE_NAME 102
+#define IDR_HTML_EDITOR 102
+#define IDS_TESTSERVICE_NAME 103
+#define IDD_OPENURL 103
+#define IDS_USERSERVICE_NAME 104
+#define IDS_FILEFILTER_ALL 105
+#define IDS_FILEFILTER_INI 106
+#define IDS_FILEFILTER_ALLKNOWN 106
+#define IDS_LOADSERVICE 107
+#define IDS_UNKNOWNNAME 108
+#define IDS_IMPORT_FILES 109
+#define IDS_FILEFILER_XML 110
+#define IDR_CONTEXTMENU 400
+#define IDR_HTML_WELCOME 1000
+#define IDC_EDIT_ADDRESS 1000
+#define IDC_ADDRESS 1000
+#define IDR_HTML_TEST 1001
+#define IDR_HELP_ICON 20001
+#define IDR_GEAR_ICON 20002
+#define ID_NAVIGATION_CREATESEVICE 40001
+#define ID_SERVICE_EDIT 40010
+#define ID_NAVIGATION_BACK 40011
+#define ID_NAVIGATION_FORWARD 40012
+#define ID_NAVIGATION_HOME 40013
+#define ID_NAVIGATION_REFRESH 40014
+#define ID_NAVIGATION_STOP 40015
+#define ID_VIEW_NEWWINDOW 40016
+#define ID_VIEW_OPEN 40017
+#define ID_SERVICE_DELETE 40020
+#define ID_SERVICE_RELOAD 40021
+#define ID_SERVICEMNGR_CREATENEW 40022
+#define ID_SERVICE_RESETPERMISSIONS 40024
+#define ID_SERVICE_NEW 40026
+#define ID_SERVICE_LOAD 40027
+#define ID_SERVICE_LOCATE 40029
+#define ID_SERVICE_EDITEXTERNAL 40031
+#define ID_BROWSER_PREFERENCES 40032
+#define ID_OMBROWSER_OPTIONS 40034
+#define ID_SERVICE_IMPORT 40035
+#define ID_SERVICE_IMPORT_FILE 40039
+#define ID_SERVICE_IMPORT_FOLDER 40040
+#define ID_SERVICE_IMPORT_URL 40041
+#define ID_SERVICE_DELETEALL 40042
+#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 40043
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_webdev/resources/gear.png b/Src/Plugins/Library/ml_webdev/resources/gear.png
new file mode 100644
index 00000000..da8a31d9
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/gear.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_webdev/resources/help.png b/Src/Plugins/Library/ml_webdev/resources/help.png
new file mode 100644
index 00000000..b9eab4e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/help.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/api2.js b/Src/Plugins/Library/ml_webdev/resources/pages/api2.js
new file mode 100644
index 00000000..f61eb4bd
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/api2.js
@@ -0,0 +1,624 @@
+function startup(){
+}
+
+function NotImplementedYet(){
+ alert("This method has not been implemented yet");
+}
+
+function tStopClicked(){
+ var t_rc = window.external.Transport.Stop();
+}
+
+function tPlayClicked(){
+ var t_rc = window.external.Transport.Play();
+}
+
+function tPauseClicked(){
+ var t_rc = window.external.Transport.Pause();
+}
+
+function tPrevClicked(){
+ var t_rc = window.external.Transport.Prev();
+}
+
+function tNextClicked(){
+ var t_rc = window.external.Transport.Next();
+}
+
+function tGetMetadataClicked(){
+ var metaTag = document.getElementById("t_t_mTag_in").value;
+ var metadata = window.external.Transport.GetMetadata(metaTag);
+ document.getElementById("t_t_mTag_Response").value = metadata;
+}
+
+function tGetShuffleClicked(){
+ var t_shuffle = window.external.Transport.shuffle;
+ document.getElementById("t_t_shuffle").value = t_shuffle;
+}
+
+function tSetShuffleClicked(){
+ var t_shuffle = document.getElementById("t_t_shuffle_in").value;
+ if (t_shuffle == "true") window.external.Transport.shuffle = true;
+ else window.external.Transport.shuffle = false;
+ tGetShuffleClicked();
+}
+
+function tGetRepeatClicked(){
+ var t_repeat = window.external.Transport.repeat;
+ document.getElementById("t_t_repeat").value = t_repeat;
+}
+
+function tSetRepeatClicked(){
+ var t_repeat = document.getElementById("t_t_repeat_in").value;
+ if (t_repeat == "true") window.external.Transport.repeat = true;
+ else window.external.Transport.repeat = false;
+ tGetRepeatClicked();
+}
+
+function tGetPositionClicked(){
+ var t_position = window.external.Transport.position;
+ document.getElementById("t_t_position").value = t_position;
+}
+
+function tSetPositionClicked(){
+ var t_position = document.getElementById("t_t_position_in").value;
+ window.external.Transport.position = parseInt(t_position);
+ tGetPositionClicked();
+}
+
+function tLengthClicked(){
+ var length = window.external.Transport.length;
+ document.getElementById("t_t_Length").value = length;
+}
+
+function tURLClicked(){
+ var url = window.external.Transport.url;
+ document.getElementById("t_t_URL").value = url;
+}
+
+function tTitleClicked(){
+ var title = window.external.Transport.title;
+ document.getElementById("t_t_Title").value = title;
+}
+
+function tPlayingClicked(){
+ var playing = window.external.Transport.playing;
+ document.getElementById("t_t_Playing").value = playing;
+}
+
+function tPausedClicked(){
+ var paused = window.external.Transport.paused;
+ document.getElementById("t_t_Paused").value = paused;
+}
+
+function pqPlayClicked() {
+ var myMusic = document.getElementById("pq_t_PlayURL_in").value;
+ var title = document.getElementById("pq_t_PlayTitle_in").value
+ var length = document.getElementById("pq_t_PlayLength_in").value;
+ var ilength = parseInt(length);
+ if (isNaN(ilength)) {ilength = 0;}
+ var pq_play = window.external.PlayQueue.Play(myMusic, title, ilength);
+}
+
+function pqEnqueueClicked() {
+ var myMusic = document.getElementById("pq_t_EnqueueURL_in").value;
+ var title = document.getElementById("pq_t_EnqueueTitle_in").value
+ var length = document.getElementById("pq_t_EnqueueLength_in").value;
+ var ilength = parseInt(length);
+ if (isNaN(ilength)) {ilength = 0};
+ var pq_play = window.external.PlayQueue.Enqueue(myMusic, title, ilength);
+}
+
+function pqInsertClicked() {
+ var position = document.getElementById("pq_t_InsertPosition_in").value;
+ var iposition = parseInt(position);
+ if (isNaN(iposition)) {iposition = 0;}
+ var myMusic = document.getElementById("pq_t_InsertURL_in").value;
+ var title = document.getElementById("pq_t_InsertTitle_in").value
+ var length = document.getElementById("pq_t_InsertLength_in").value;
+ var ilength = parseInt(length);
+ if (isNaN(ilength)) {ilength = 0;}
+ var pq_play = window.external.PlayQueue.Insert(iposition, myMusic, title, ilength);
+}
+
+function pqClearQClicked() {
+ var pq_rc = window.external.PlayQueue.ClearQueue();
+}
+
+function pqGetMetadataClicked(){
+ var metaPos = document.getElementById("pq_t_MetadataPosition_in").value;
+ var imetaPos = parseInt(metaPos);
+ var metaTag = document.getElementById("pq_t_MTag_in").value;
+ var metadata = window.external.PlayQueue.GetMetadata(imetaPos, metaTag);
+ document.getElementById("pq_t_MTag_Response").value = metadata;
+}
+
+function pqGetTitleClicked(){
+ var position = document.getElementById("pq_t_TitlePosition_in").value;
+ var iposition = parseInt(position);
+ var title = window.external.PlayQueue.GetTitle(iposition);
+ document.getElementById("pq_t_Title").value = title;
+}
+
+function pqGetURLClicked() {
+ var position = document.getElementById("pq_t_URLPosition_in").value;
+ var iposition = parseInt(position);
+ var url = window.external.PlayQueue.GetURL(iposition);
+ document.getElementById("pq_t_URL").value = url;
+}
+
+function pqGetLengthClicked(){
+ var length = window.external.PlayQueue.length;
+ document.getElementById("pq_t_Length").value = length;
+}
+
+function pqGetCursorClicked(){
+ var cursor = window.external.PlayQueue.cursor;
+ document.getElementById("pq_t_Cursor").value = cursor;
+}
+
+function pqSetCursorClicked(){
+ var pq_cursor = document.getElementById("pq_t_Cursor_in").value;
+ window.external.PlayQueue.cursor = parseInt(pq_cursor);
+ pqGetCursorClicked();
+}
+
+var listPlaylists;
+function plsGetPlaylists(){
+ listPlaylists = window.external.Playlists.GetPlaylists();
+ var numPlaylists = listPlaylists.length;
+ document.getElementById("pls_t_Count").value = numPlaylists;
+}
+
+function plsViewIndexClicked(){
+ var view_index = document.getElementById("pls_t_Index_in").value;
+ var iview_index = parseInt(view_index);
+ document.getElementById("pls_t_Filename").value = listPlaylists[iview_index].filename;
+ document.getElementById("pls_t_Title").value = listPlaylists[iview_index].title;
+ document.getElementById("pls_t_PlaylistId").value = listPlaylists[iview_index].playlistId;
+ document.getElementById("pls_t_Length").value = listPlaylists[iview_index].length;
+ document.getElementById("pls_t_NumItems").value = listPlaylists[iview_index].numitems;
+ document.getElementById("pls_t_OpenPlaylistId_in").value = listPlaylists[iview_index].playlistId;
+ document.getElementById("pls_t_SavePlaylistId_in").value = listPlaylists[iview_index].playlistId;
+}
+
+var currentPlaylist;
+function plsOpenPlaylistClicked(){
+ var id = document.getElementById("pls_t_OpenPlaylistId_in").value;
+ currentPlaylist = window.external.Playlists.OpenPlaylist(id);
+ document.getElementById("playlist_t_NumItems").value = currentPlaylist.numitems;
+ plsDisableListMethods(false);
+ showPlaylist();
+}
+
+var currentItemIndex = 0;
+
+function plsDisablePlaylistMethods(disable){
+ var playlistSection = document.getElementById("playlistmethods");
+ playlistSection.disabled = disable;
+ for (var i=0; i < playlistSection.childNodes.length; i++){
+ if (playlistSection.childNodes[i].nodeType == 1) {
+ playlistSection.childNodes[i].disabled = disable;
+ }
+ }
+}
+
+function plsDisableListMethods(disable){
+ var listSection = document.getElementById("listmethods");
+ listSection.disabled = disable;
+ for (var i=0; i < listSection.childNodes.length; i++){
+ if (listSection.childNodes[i].nodeType == 1) {
+ listSection.childNodes[i].disabled = disable;
+ }
+ }
+}
+
+function plsPlaylistViewIndexClicked(){
+ var view_index = document.getElementById("playlist_t_ItemIndex_in").value;
+ var iview_index = parseInt(view_index);
+ currentItemIndex = iview_index;
+ document.getElementById("playlist_t_Filename").value = currentPlaylist.GetItemFilename(currentItemIndex);
+ document.getElementById("playlist_t_Title").value = currentPlaylist.GetItemTitle(currentItemIndex);
+ document.getElementById("playlist_t_Length").value = currentPlaylist.GetItemLength(currentItemIndex);
+ plsDisablePlaylistMethods(false);
+ showPlaylist();
+}
+
+var playlistwin;
+var playlistWinShown = false;
+function showPlaylist(){
+ var output = "";
+ for (var i = 0; i < currentPlaylist.numitems; i++) {
+ var title = currentPlaylist.GetItemTitle(i);
+ output += i + ":" + title + "\n";
+ }
+ alert(output);
+}
+
+function plsPlaylistSetItemFilenameClicked(){
+ var filename = document.getElementById("playlist_t_SetItemFilename_in").value;
+ currentPlaylist.SetItemFilename(currentItemIndex, filename);
+ document.getElementById("playlist_t_Filename").value = filename;
+}
+
+function plsPlaylistSetItemTitleClicked(){
+ var title = document.getElementById("playlist_t_SetItemTitle_in").value;
+ currentPlaylist.SetItemTitle(currentItemIndex, title);
+ document.getElementById("playlist_t_Title").value = title;
+}
+
+function plsPlaylistSetItemLengthClicked(){
+ var length = document.getElementById("playlist_t_SetItemLength_in").value;
+ var iLength = parseInt(length);
+ currentPlaylist.SetItemLengthMilliseconds(currentItemIndex, iLength);
+ document.getElementById("playlist_t_Length").value = length;
+}
+
+function plsPlaylistReverseClicked(){
+ currentPlaylist.Reverse();
+ showPlaylist();
+}
+
+function plsPlaylistSwapItemsClicked(){
+ var swap1 = document.getElementById("playlist_t_Swap1_in").value;
+ var swap2 = document.getElementById("playlist_t_Swap2_in").value;
+ var iswap1 = parseInt(swap1);
+ var iswap2 = parseInt(swap2);
+ var rc = currentPlaylist.SwapItems(iswap1, iswap2);
+ showPlaylist();
+}
+
+function plsPlaylistRandomizeClicked(){
+ currentPlaylist.Randomize();
+ showPlaylist();
+}
+
+function plsPlaylistRemoveClicked(){
+ alert("Inside Remove, currentItemIndex=" + currentItemIndex);
+ currentPlaylist.RemoveItem(currentItemIndex);
+ showPlaylist();
+}
+
+function plsPlaylistSortByTitleClicked(){
+ currentPlaylist.SortByTitle();
+ showPlaylist();
+}
+
+function plsPlaylistSortByFilenameClicked(){
+ currentPlaylist.SortByFilename();
+ showPlaylist();
+}
+
+function plsPlaylistInsertUrlClicked(){
+ var url = document.getElementById("playlist_t_InsertUrl_in").value;
+ var title = document.getElementById("playlist_t_InsertTitle_in").value;
+ var length = document.getElementById("playlist_t_InsertLength_in").value;
+ var iLength = parseInt(length);
+ var rc = currentPlaylist.InsertURL(currentItemIndex, url, title, iLength);
+ showPlaylist();
+}
+
+function plsPlaylistAppendUrlClicked(){
+ var url = document.getElementById("playlist_t_AppendUrl_in").value;
+ var title = document.getElementById("playlist_t_AppendTitle_in").value;
+ var length = document.getElementById("playlist_t_AppendLength_in").value;
+ var iLength = parseInt(length);
+ var rc = currentPlaylist.AppendURL(url, title, iLength);
+ showPlaylist();
+}
+
+function plsPlaylistClearClicked(){
+ var rc = currentPlaylist.Clear();
+ showPlaylist();
+}
+
+function plsSavePlaylistClicked() {
+ var playlistId = document.getElementById("pls_t_SavePlaylistId_in").value;
+ var rc = window.external.Playlists.SavePlaylist(playlistId, currentPlaylist);
+ document.getElementById("playlistmethods").disabled = true;
+}
+
+function BMarkAddClicked(){
+ var url = document.getElementById("bmark_t_AddURL_in").value;
+ var title = document.getElementById("bmark_t_AddTitle_in").value;
+ var rc = window.external.Bookmarks.Add(url, title);
+}
+
+function PodcastSubscribeClicked(){
+ var podcUrl = document.getElementById("podc_t_SubscribeURL_in").value;
+ var rc = window.external.Podcasts.Subscribe(podcUrl);
+}
+
+function ConfigSetPropertyClicked(){
+ var inParam = document.getElementById("config_t_SetPropertyName_in").value;
+ var inValue = document.getElementById("config_t_SetPropertyValue_in").value;
+ if (pType[0].checked) {
+ inValue = "'" + inValue + "'";
+ } else if (pType[2].checked){
+ if (inValue != "false"){
+ inValue = "true";
+ }
+ }
+ var funcBody = "var rc = window.external.Config." + inParam + "=" + inValue;
+ var configPropSet = new Function(funcBody);
+ configPropSet();
+}
+
+function ConfigGetPropertyClicked(){
+ var inParam = document.getElementById("config_t_GetPropertyName_in").value;
+ var funcBody = "return window.external.Config." + inParam;
+ var configPropGet = new Function(funcBody);
+ var propValue = configPropGet();
+ document.getElementById("config_t_GetPropertyValue").value = propValue;
+}
+
+function ApplicationLaunchURLClicked(){
+ var url = document.getElementById("application_t_URL_in").value;
+ var forceExternal = document.getElementById("application_t_ForceExternal_in").checked;
+ var rc = window.external.Application.LaunchURL(url, forceExternal);
+}
+
+function ApplicationNumVersionClicked(){
+ var numVer = window.external.Application.version;
+ document.getElementById("application_t_NumVersion").value = parseInt(numVer);
+}
+
+function ApplicationStringVersionClicked(){
+ var stringVer = window.external.Application.versionstring;
+ document.getElementById("application_t_StringVersion").value = stringVer;
+}
+
+function ApplicationLanguageClicked(){
+ var lang = window.external.Application.language;
+ document.getElementById("application_t_Language").value = lang;
+}
+
+function ApplicationLanguagePackClicked(){
+ var langPack = window.external.Application.languagepack;
+ document.getElementById("application_t_LanguagePack").value = langPack;
+}
+
+function SkinGetClassicColorClicked(){
+ var colorNum = document.getElementById("skin_t_ClassicColorNumber_in").value;
+ var iColorNum = parseInt(colorNum);
+ var classicColor = window.external.Skin.GetClassicColor(iColorNum);
+ document.getElementById("skin_t_ClassicColor").value = classicColor;
+}
+
+function SkinGetPlaylistColorClicked(){
+ var colorNum = document.getElementById("skin_t_PlaylistColorNumber_in").value;
+ var iColorNum = parseInt(colorNum);
+ var playlistColor = window.external.Skin.GetPlaylistColor(iColorNum);
+ document.getElementById("skin_t_PlaylistColor").value = playlistColor;
+}
+
+function SkinGetSkinColorClicked(){
+ var colorName = document.getElementById("skin_t_SkinColorName_in").value;
+ var skinColor = window.external.Skin.GetSkinColor(colorName);
+ document.getElementById("skin_t_SkinColor").value = skinColor;
+}
+
+function SkinGetNameClicked(){
+ var name = window.external.Skin.name;
+ document.getElementById("skin_t_Name").value = name;
+}
+
+function SkinSetNameClicked() {
+ var skinName = document.getElementById("skin_t_Name_in").value;
+ window.external.Skin.name = skinName;
+}
+
+function SkinGetFontClicked(){
+ var font = window.external.Skin.font;
+ document.getElementById("skin_t_Font").value = font;
+}
+
+function SkinGetFontSizeClicked(){
+ var fontsize = window.external.Skin.fontsize;
+ document.getElementById("skin_t_FontSize").value = fontsize;
+}
+
+function mcGetMetadataClicked(){
+ var metaFile = document.getElementById("mc_t_MediaCoreFilename_in").value;
+ var metaTag = document.getElementById("mc_t_MediaCoreTag_in").value;
+ var metadata = window.external.MediaCore.GetMetadata(metaFile, metaTag);
+ document.getElementById("mc_t_MediaCoreMetadata_Response").value = metadata;
+}
+
+function mcIsRegisteredExtensionClicked(){
+ var extension = document.getElementById("mc_t_MediaCoreExtension_in").value;
+ var supported = window.external.MediaCore.IsRegisteredExtension(extension);
+ document.getElementById("mc_t_MediaCoreRegisteredExtension_Response").value = supported;
+}
+
+function mcAddMetadataHookClicked(){
+ var mUrl = document.getElementById("mc_t_AddMetadataHookUrl_in").value;
+ var mTag = document.getElementById("mc_t_AddMetadataHookTag_in").value;
+ var mValue = document.getElementById("mc_t_AddMetadataHookValue_in").value;
+ window.external.MediaCore.AddMetadataHook(mUrl,mTag,mValue);
+}
+
+function mcRemoveMetadataHookClicked(){
+ var mUrl = document.getElementById("mc_t_RemoveMetadataHookUrl_in").value;
+ var mTag = document.getElementById("mc_t_RemoveMetadataHookTag_in").value;
+ window.external.MediaCore.RemoveMetadataHook(mUrl,mTag);
+}
+
+function teRegisterClicked(){
+ var rc = window.external.Transport.RegisterForEvents(onEvents);
+ document.getElementById("teRegister").disabled = true;
+ document.getElementById("teUnregister").disabled = false;
+}
+
+function teUnregisterClicked(){
+ var rc = window.external.Transport.UnregisterFromEvents(onEvents);
+ document.getElementById("teRegister").disabled = false;
+ document.getElementById("teUnregister").disabled = true;
+}
+
+var eventsArray = new Array();
+var eventCount = 0;
+function onEvents(event){
+ eventsArray[eventCount] = event;
+
+ // populate the select box
+ var eventSelect = document.getElementById("te_s_Select");
+ var newOption = new Option((eventCount + 1) + ":" + event.event, event);
+ eventSelect.options[eventCount] = newOption;
+ eventCount++;
+}
+
+function teEventSelected(){
+ var sel = document.getElementById("te_s_Select");
+ var area = document.getElementById("te_ta_Area");
+ var tarea = "";
+ var obj = eventsArray[sel.selectedIndex];
+ for (var prop in obj){
+ tarea += "property:" + prop + " value:" + obj[prop] + "\n\n";
+ }
+ area.value = tarea;
+}
+
+function te_AreaCleared(){
+ eventCount = 0;
+ document.getElementById("te_ta_Area").value = "";
+ document.getElementById("te_s_Select").options.length = 0;
+}
+
+function hisQueryClicked(){
+ var query = document.getElementById("his_t_Query_in").value;
+ var respArray = window.external.History.Query(query);
+ alert(respArray[0].filename);
+ var textOut = "";
+ for (var obj in respArray){
+ if (textOut != ""){
+ textOut += "\n";
+ }
+ textOut += "==============";
+ textOut += "\nTitle: " + respArray[obj].title;
+ textOut += "\nLastPlay: " + respArray[obj].lastplay;
+ textOut += "\nPlaycount: " + respArray[obj].playcount;
+ textOut += "\nFilename: " + respArray[obj].filename;
+ textOut += "\nLength: " + respArray[obj].length;
+ }
+ var textAreaOut = document.getElementById("his_t_Query");
+ textAreaOut.value = textOut;
+}
+
+function AsyncDownloadMediaClicked(){
+ var url = document.getElementById("asyncdownloader_t_URL_in").value;
+ var destFile = document.getElementById("asyncdownloader_t_DestinationFile_in").value;
+ document.getElementById("progressbar").firstChild.nodeValue = "";
+ document.getElementById("slider").style.clip = "rect(0px 0px 16px 0px)";
+ if (destFile)
+ var rc = window.external.AsyncDownloader.DownloadMedia(url, destFile);
+ else
+ var rc = window.external.AsyncDownloader.DownloadMedia(url);
+}
+
+function AsyncDownloadMediaClicked1(){
+ var url = document.getElementById("asyncdownloader_t_URL_in_1").value;
+ var destFile = document.getElementById("asyncdownloader_t_DestinationFile_in_1").value;
+ document.getElementById("progressbar1").firstChild.nodeValue = "";
+ document.getElementById("slider1").style.clip = "rect(0px 0px 16px 0px)";
+ if (destFile)
+ var rc = window.external.AsyncDownloader.DownloadMedia(url, destFile);
+ else
+ var rc = window.external.AsyncDownloader.DownloadMedia(url);
+}
+
+function AsyncDownloadMultipleMediaClicked() {
+ var multipleUrls = document.getElementById("multiple_urls").value;
+ var urls = multipleUrls.split(";");
+ for(index = 0; index < urls.length; index++){
+ if (urls[index].length > 0) {
+ if (urls[index].indexOf("http://") != -1) urls[index] = urls[index].substr(7);
+ var rc = window.external.AsyncDownloader.DownloadMedia("http://"+escape(urls[index]));
+ }
+ }
+}
+
+function downloaderRegisterClicked(){
+ var rc = window.external.AsyncDownloader.RegisterForEvents(onDownloaderEvents);
+ document.getElementById("downloaderRegister").disabled = true;
+ document.getElementById("downloaderUnregister").disabled = false;
+}
+
+function downloaderUnregisterClicked(){
+ var rc = window.external.AsyncDownloader.UnregisterFromEvents(onDownloaderEvents);
+ document.getElementById("downloaderRegister").disabled = false;
+ document.getElementById("downloaderUnregister").disabled = true;
+}
+
+var downloaderEventsArray = new Array();
+var downloaderEventCount = 0;
+function downloader_AreaCleared(){
+ downloaderEventCount = 0;
+ document.getElementById("downloader_ta_Area").value = "";
+ document.getElementById("downloader_s_Select").options.length = 0;
+}
+
+function onDownloaderEvents(event){
+ downloaderEventsArray[downloaderEventCount] = event;
+
+ // populate the select box
+ var eventSelect = document.getElementById("downloader_s_Select");
+ var newOption = new Option((downloaderEventCount + 1) + ":" + event.event, event);
+ eventSelect.options[downloaderEventCount] = newOption;
+ downloaderEventCount++;
+
+ var progress, slider;
+ if (event.url == document.getElementById("asyncdownloader_t_URL_in").value)
+ {
+ progress = document.getElementById("progressbar");
+ slider = document.getElementById("slider");
+ }
+ if (event.url == document.getElementById("asyncdownloader_t_URL_in_1").value)
+ {
+ progress = document.getElementById("progressbar1");
+ slider = document.getElementById("slider1");
+ }
+
+ if (progress && slider)
+ {
+ if (event.event == 'OnInit')
+ {
+ progress.firstChild.nodeValue = 'Progress: Start downloading ...';
+ }
+ if (event.event == 'OnData')
+ {
+ progress.firstChild.nodeValue = 'Progress: Downloading ...';
+ var factor = event.downloadedlen/event.totallen;
+ slider.firstChild.nodeValue = Math.ceil(factor * 100) + '%';
+ slider.style.clip = "rect(0px " + parseInt(factor * 417) + "px 16px 0px)";
+ }
+ if (event.event == 'OnFinish')
+ {
+ progress.firstChild.nodeValue = 'Progress: Downloading Succeed';
+ slider.firstChild.nodeValue = '100%';
+ }
+ if (event.event == 'OnError' || event.event == 'OnCancel')
+ {
+ progress.firstChild.nodeValue = 'Progress: Downloading Failed';
+ }
+ }
+}
+
+function downloaderEventSelected(){
+ var sel = document.getElementById("downloader_s_Select");
+ var area = document.getElementById("downloader_ta_Area");
+ var tarea = "";
+ var obj = downloaderEventsArray[sel.selectedIndex];
+ if (obj.url)
+ tarea += "property:url value:" + obj.url + "\n\n";
+ if (obj.event == 'OnData')
+ {
+ tarea += "property:downloadedlen value:" + obj.downloadedlen + "\n\n";
+ tarea += "property:totallen value:" + obj.totallen + "\n\n";
+ }
+ if (obj.event == 'OnFinish')
+ tarea += "property:destfilename value:" + obj.destfilename + "\n\n";
+ if (obj.event == 'OnError')
+ tarea += "property:error value:" + obj.error + "\n\n";
+ area.value = tarea;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/applicationApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/applicationApi.htm
new file mode 100644
index 00000000..96bf4137
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/applicationApi.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Application Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Application API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Launch URL" onclick="ApplicationLaunchURLClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:<input type="text" size="20" id="application_t_URL_in" value="http://dev.winamp.com">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Force External Browser:<input type="checkbox" checked="unchecked" id="application_t_ForceExternal_in">
+ <h3>Properties:</h3>
+ <input type="button" value="Get number version" onclick="ApplicationNumVersionClicked();">
+ <input type="text" readonly size="20" id="application_t_NumVersion">
+ <br>
+ <br>
+ <input type="button" value="Get string version" onclick="ApplicationStringVersionClicked();">
+ <input type="text" readonly size="20" id="application_t_StringVersion">
+ <br>
+ <br>
+ <input type="button" value="Get language" onclick="ApplicationLanguageClicked();">
+ <input type="text" readonly size="20" id="application_t_Language">
+ <br>
+ <br>
+ <input type="button" value="Get languagepack" onclick="ApplicationLanguagePackClicked();">
+ <input type="text" readonly size="20" id="application_t_LanguagePack">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/asyncDownloaderApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/asyncDownloaderApi.htm
new file mode 100644
index 00000000..4aab77d1
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/asyncDownloaderApi.htm
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::AsyncDownloader Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <style type="text/css">
+ #slider{position:absolute; left:20px; top:180px; width:420px; height:15px; clip:rect(0px 0px 15px 0px); background-color:#666699; text-align:left; color:#ffffff; font-size:12px;}
+ #slider1{position:absolute; left:20px; top:280px; width:420px; height:15px; clip:rect(0px 0px 15px 0px); background-color:#666699; text-align:left; color:#ffffff; font-size:12px;}
+ </style>
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>AsyncDownloader API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Download Media" onclick="AsyncDownloadMediaClicked();">&nbsp;&nbsp;URL:&nbsp;<input type="text" size="40" id="asyncdownloader_t_URL_in">&nbsp;&nbsp;Destination File:&nbsp;<input type="text" size="20" id="asyncdownloader_t_DestinationFile_in">
+ <div id="progressbardiv"><h5 id="progressbar">Progress: </h5>
+ <br>
+ <div id="slider">0%</div>
+ </div>
+ <input type="button" value="Download Media" onclick="AsyncDownloadMediaClicked1();">&nbsp;&nbsp;URL:&nbsp;<input type="text" size="40" id="asyncdownloader_t_URL_in_1">&nbsp;&nbsp;Destination File:&nbsp;<input type="text" size="20" id="asyncdownloader_t_DestinationFile_in_1">
+ <div id="progressbardiv1"><h5 id="progressbar1">Progress: </h5>
+ <br>
+ <div id="slider1">0%</div>
+ </div>
+ <input type="button" value="Download Multiple Medias" onclick="AsyncDownloadMultipleMediaClicked();"><br>&nbsp;&nbsp;URLs:&nbsp;<textarea cols="60" rows="10" id="multiple_urls"></textarea>
+ <br>
+ <h3>Events:</h3>
+ <input type="button" value="Register For Downloader Events" id="downloaderRegister" onclick="downloaderRegisterClicked();">
+ <input type="button" value="Unregister From Downloader Events" id="downloaderUnregister" onclick="downloaderUnregisterClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Callback function:&nbsp;<input readonly type="text" id="downloader_t_Callback" value="OnDownloaderEvents">
+ <br><br>
+ <b>Events Received</b>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <a href="javascript:downloader_AreaCleared();"><small>clear</small></a>
+ <br>
+ <select size="10" id="downloader_s_Select" style="width: 100px" onchange="downloaderEventSelected();">
+ <option>No Events</option>
+ </select>
+ <textarea rows="10" cols="40" id="downloader_ta_Area"></textarea>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/bookmarkApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/bookmarkApi.htm
new file mode 100644
index 00000000..c31bacb5
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/bookmarkApi.htm
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Bookmarks Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Bookmarks API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Add Bookmark" onclick="BMarkAddClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:<input type="text" size="20" id="bmark_t_AddURL_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Title:<input type="text" size="20" id="bmark_t_AddTitle_in">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/configApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/configApi.htm
new file mode 100644
index 00000000..a114144e
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/configApi.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Config Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Config API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Set Property" onclick="ConfigSetPropertyClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Property Name:<input type="text" size="20" id="config_t_SetPropertyName_in" value="myProperty">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Property Type:
+ <input type="radio" name="pType" id="config_t_SetPropertyType_in" checked>string</input>
+ <input type="radio" name="pType" id="config_t_SetPropertyType_in">number</input>
+ <input type="radio" name="pType" id="config_t_SetPropertyType_in">boolean</input>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Property Value:<input type="text" size="20" id="config_t_SetPropertyValue_in" value="myPropertyValue">
+ <br><br>
+ <input type="button" value="Get Property" onclick="ConfigGetPropertyClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Property Value:<input type="text" readonly size="20" id="config_t_GetPropertyValue">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Property Name:<input type="text" size="20" id="config_t_GetPropertyName_in" value="myProperty">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/historyApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/historyApi.htm
new file mode 100644
index 00000000..b7732c01
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/historyApi.htm
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::History Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>History API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Query" onClick="hisQueryClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="text" size="80" value="LASTPLAY<[now]" id="his_t_Query_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<textarea rows="24" cols="50" id="his_t_Query">
+ </textarea>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/mediaCoreApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/mediaCoreApi.htm
new file mode 100644
index 00000000..3daee11f
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/mediaCoreApi.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::MediaCore Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>MediaCore API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Get Metadata" onclick="mcGetMetadataClicked();">
+ <input type="text" readonly size="40" id="mc_t_MediaCoreMetadata_Response">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Filename:&nbsp;<input type="text" value="C:\Program Files\Winamp\demoedit.aac" id="mc_t_MediaCoreFilename_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Metadata Tag:&nbsp;<input type="text" value="artist" id="mc_t_MediaCoreTag_in">
+ <br><br>
+ <input type="button" value="Is Registered Extension" onclick="mcIsRegisteredExtensionClicked();">
+ <input type="text" readonly size="40" id="mc_t_MediaCoreRegisteredExtension_Response">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Extension:&nbsp;<input type="text" value=".mp3" id="mc_t_MediaCoreExtension_in">
+ <br><br>
+ <input type="button" value="AddMetadataHook" onclick="mcAddMetadataHookClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:&nbsp;<input type="text" size="50" id="mc_t_AddMetadataHookURL_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Metadata Tag:&nbsp;<input type="text" id="mc_t_AddMetadataHookTag_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Metadata Value:&nbsp;<input type="text" id="mc_t_AddMetadataHookValue_in">
+ <br><br>
+ <input type="button" value="RemoveMetadataHook" onclick="mcRemoveMetadataHookClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:&nbsp;<input type="text" size="50" id="mc_t_RemoveMetadataHookURL_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Metadata Tag:&nbsp;<input type="text" id="mc_t_RemoveMetadataHookTag_in">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/playQueueApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/playQueueApi.htm
new file mode 100644
index 00000000..6fc63c82
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/playQueueApi.htm
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::PlayQueue Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>PlayQueue API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Play" onclick="pqPlayClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:&nbsp;<input type="text" id="pq_t_PlayURL_in" value="c:\Documents and Settings\smontgo444\My Documents\My Music\Winamp\01 - Hello City.mp3">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;title:&nbsp;<input type="text" id="pq_t_PlayTitle_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;length:&nbsp;<input type="text" id="pq_t_PlayLength_in">
+ <br>
+ <br>
+ <input type="button" value="Enqueue" onclick="pqEnqueueClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:&nbsp;<input type="text" id="pq_t_EnqueueURL_in" value="c:\Documents and Settings\smontgo444\My Documents\My Music\Winamp\01 - Hello City.mp3">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;title:&nbsp;<input type="text" id="pq_t_EnqueueTitle_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;length:&nbsp;<input type="text" id="pq_t_EnqueueLength_in">
+ <br>
+ <br>
+ <input type="button" value="Insert" onclick="pqInsertClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;<input type="text" id="pq_t_InsertPosition_in" value="0">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:&nbsp;<input type="text" id="pq_t_InsertURL_in" value="c:\Documents and Settings\smontgo444\My Documents\My Music\Winamp\08 - The King Of Bedside Manor.mp3">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;title:&nbsp;<input type="text" id="pq_t_InsertTitle_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;length:&nbsp;<input type="text" id="pq_t_InsertLength_in">
+ <br>
+ <br>
+ <input type="button" value="Clear Queue" onclick="pqClearQClicked();">
+ <br>
+ <br>
+ <input type="button" value="Get Metadata" onclick="pqGetMetadataClicked();">
+ <input type="text" readonly size="40" id="pq_t_MTag_Response">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;<input type="text" value="0" id="pq_t_MetadataPosition_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Metadata Tag:&nbsp;<input type="text" value="artist" id="pq_t_MTag_in">
+ <br>
+ <br>
+ <input type="button" value="Get Title" onclick="pqGetTitleClicked();">
+ <input type="text" readonly size="20" id="pq_t_Title">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;<input type="text" value="0" id="pq_t_TitlePosition_in">
+ <br>
+ <br>
+ <input type="button" value="Get URL" onclick="pqGetURLClicked();">
+ <input type="text" readonly size="20" id="pq_t_URL">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;<input type="text" value="0" id="pq_t_URLPosition_in">
+ <br>
+ <br>
+ <h2>Properties</h2>
+ <input type="button" value="Get length" onclick="pqGetLengthClicked();">
+ <input type="text" readonly size="20" id="pq_t_Length">
+ <br>
+ <br>
+ <input type="button" value="Get cursor" onclick="pqGetCursorClicked();">
+ <input type="text" readonly size="20" id="pq_t_Cursor">
+ <br>
+ <input type="button" value="Set cursor" onclick="pqSetCursorClicked();">
+ <input type="text" size="20" id="pq_t_Cursor_in">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/playlistApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/playlistApi.htm
new file mode 100644
index 00000000..1553e875
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/playlistApi.htm
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Playlists Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body onload="plsDisablePlaylistMethods(true);plsDisableListMethods(true);" style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Playlists API</h2>
+ <p><small>Since it is hard to obtain a playlist ID to use in the OpenPlaylist and SavePlaylist methods, click "Get Playlists" to get all the playlists and then "Set Index" to choose one of the playlists. This will pre-populate the playlist IDs for the OpenPlaylist and SavePlaylist buttons.</small>
+ <h3>Methods:</h3>
+ <input type="button" value="Get Playlists" onclick="plsGetPlaylists();">
+ <br>
+ &nbsp;&nbsp;Count (of playlists):<input type="text" readonly size="20" id="pls_t_Count">
+ <br>
+ &nbsp;&nbsp;<input type="button" value="Set Playlist Index" onclick="plsViewIndexClicked();">
+ <input type="text" size="10" value="0" id="pls_t_Index_in">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist Summary Info:
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist ID: <input type="text" readonly size="60" id="pls_t_PlaylistId">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist Num Items: <input type="text" readonly size="20" id="pls_t_NumItems">
+ &nbsp;&nbsp;<small>(cached, may not be current)</small>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist Filename: <input type="text" size="80" id="pls_t_Filename">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist Title: <input type="text" size="80" id="pls_t_Title">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist (total) Length: <input type="text" readonly size="20" id="pls_t_Length">
+ &nbsp;&nbsp;<small>(cached, may not be current)</small>
+ <br>
+ <br>
+ <input type="button" value="OpenPlaylist" onclick="plsOpenPlaylistClicked();">
+ <small>(will also display the playlist)</small>
+ <br>
+ Count (of items in playlist):<input type="text" readonly size="20" id="playlist_t_NumItems">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist ID: <input type="text" size="60" id="pls_t_OpenPlaylistId_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Set Item Index" onclick="plsPlaylistViewIndexClicked()">
+ <input type="text" size="10" value="0" id="playlist_t_ItemIndex_in">
+ <br>
+ <br>
+ <div id="playlistmethods">
+ &nbsp;&nbsp;&nbsp;&nbsp;Get Item Filename: <input type="text" size="80" id="playlist_t_Filename">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Get Item Title: <input type="text" size="80" id="playlist_t_Title">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Get Item Length: <input type="text" readonly size="20" id="playlist_t_Length">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Set Item Filename" onclick="plsPlaylistSetItemFilenameClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Item Filename: <input type="text" size="80" id="playlist_t_SetItemFilename_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Set Item Title" onclick="plsPlaylistSetItemTitleClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Item Title: <input type="text" size="80" id="playlist_t_SetItemTitle_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Set Item Length" onclick="plsPlaylistSetItemLengthClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Item Length: <input type="text" id="playlist_t_SetItemLength_in">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Remove" onclick="plsPlaylistRemoveClicked();">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Insert URL" onclick="plsPlaylistInsertUrlClicked();">
+ <small>(before the currently selected index)</small>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;URL: <input type="text" size="80" id="playlist_t_InsertURL_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: <input type="text" size="80" id="playlist_t_InsertTitle_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;length: <input type="text" size="20" id="playlist_t_InsertLength_in">
+ <br>
+ </div>
+ <div id="listmethods">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Append URL" onclick="plsPlaylistAppendUrlClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;URL: <input type="text" size="80" id="playlist_t_AppendURL_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: <input type="text" size="80" id="playlist_t_AppendTitle_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;length: <input type="text" size="20" id="playlist_t_AppendLength_in">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Reverse" onclick="plsPlaylistReverseClicked();">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Swap Items" onclick="plsPlaylistSwapItemsClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Index #1: <input type="text" value="0" id="playlist_t_Swap1_in">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Index #2: <input type="text" value="1" id="playlist_t_Swap2_in">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Randomize" onclick="plsPlaylistRandomizeClicked();">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Sort By Title" onclick="plsPlaylistSortByTitleClicked();">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Sort By Filename" onclick="plsPlaylistSortByFilenameClicked();">
+ <br>
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Clear" onclick="plsPlaylistClearClicked();">
+ </div>
+ <br>
+ <br>
+ <input type="button" value="SavePlaylist" onclick="plsSavePlaylistClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Playlist ID: <input type="text" size="80" id="pls_t_SavePlaylistId_in">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/podcastApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/podcastApi.htm
new file mode 100644
index 00000000..5008aba7
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/podcastApi.htm
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Podcast Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Podcasts API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Subscribe URL" onclick="PodcastSubscribeClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:<input type="text" size="20" id="podc_t_SubscribeURL_in" value="http://services.winamp.com/rss/news">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/securityApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/securityApi.htm
new file mode 100644
index 00000000..72850262
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/securityApi.htm
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Security Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Security API</h2>
+ <h3>TBD</h3>
+ <!-- <h2>Methods:</h2>
+ <input type="button" value="Get Action Authorization" onclick="SecurityGetAuthorizationClicked();">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;URL:<input type="text" readonly size="20" id="security_t_Authorization">
+ -->
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/serviceEditor.htm b/Src/Plugins/Library/ml_webdev/resources/pages/serviceEditor.htm
new file mode 100644
index 00000000..c23c73a2
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/serviceEditor.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Online Services Editor</title>
+ <meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema"/>
+ <meta content="True" name="vs_snapToGrid"/>
+ <meta content="False" name="vs_showGrid"/>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
+ <script src="webdev.js" type="text/javascript"></script>
+ <style type="text/css">#Checkbox1 {width: 321px; top: 160px; left: 64px;}</style>
+</head>
+<body style="overflow-y: auto; overflow-x: auto; overflow: visible; FONT-FAMILY: MS Shell Dlg 2;" onload="WebDevEditor_Init();">
+ <h2 align="center">Online Services Editor</h2>
+ <div align="left" nowrap>
+ <div id="DIV1" style="border: 1px solid gray; clear: both; DISPLAY: block; left: 50%; float: none; visibility: visible; margin-left: -204px; overflow: visible; width: 408px; color: black; position: absolute; height: 217px; background-color: dimgray; top: 126px;" align="center" nowrap ms_positioning="GridLayout">
+ <div style="z-index: 110; left: 16px; position: absolute; top: 14px">Id:</div>
+ <input id="scvedt_edt_id" style="z-index: 101; left: 64px; position: absolute; top: 14px" readonly maxlength="16" size="9" tabindex="1"/>
+ <div style="z-index: 102; left: 16px; position: absolute; top: 46px">Name:</div>
+ <input id="scvedt_edt_name" style="z-index: 103; left: 64px; position: absolute; top: 46px; width: 322px;" maxlength="512" size="48" tabindex="2"/>
+ <div style="z-index: 104; left: 16px; position: absolute; top: 78px">Url:</div>
+ <input id="scvedt_edt_url" style="z-index: 105; left: 64px; position: absolute; top: 78px; width: 321px;" maxlength="2048" size="48" name="Text1" tabindex="3"/>
+ <div style="z-index: 106; left: 16px; position: absolute; top: 110px">Icon:</div>
+ <input id="scvedt_edt_icon" style="z-index: 107; left: 64px; position: absolute; top: 110px; width: 321px;" maxlength="512" size="48" name="Text2" tabindex="4"/>
+ <div align = "left" style="z-index: 108; position: absolute; width: 320px; top: 143px; left: 60px;" >
+ <input id="svcedt_chk_bypass" name="Checkbox1" tabindex="5" title="Preauthorized service" type="checkbox" value="bypass"/>Preauthorized service</div>
+ <input id="svcedt_btn_save" style="z-index: 109; left: 200px; width: 88px; position: absolute; top: 180px; height: 24px; right: 118px;" onclick="WebDevEditor_Save();" type="submit" value="Save" name="buttonOk" tabindex="6"/>
+ <input id="svcedt_btn_close" style="z-index: 110; left: 297px; width: 88px; position: absolute; top: 180px; height: 24px" onclick="WebDevEditor_Close();" type="button" value="Cancel" name="buttonCancel" tabindex="7"/>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/skinApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/skinApi.htm
new file mode 100644
index 00000000..425c3217
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/skinApi.htm
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Skin Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Skin API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Get Classic Color" onclick="SkinGetClassicColorClicked();">
+ <input type="text" size="20" id="skin_t_ClassicColor">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;color number:<input type="text" size="20" id="skin_t_ClassicColorNumber_in" value="1">
+ <br><br>
+ <input type="button" value="Get Playlist Color" onclick="SkinGetPlaylistColorClicked();">
+ <input type="text" readonly size="20" id="skin_t_PlaylistColor">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;color number:<input type="text" size="20" id="skin_t_PlaylistColorNumber_in" value="1">
+ <br><br>
+ <input type="button" value="Get Skin Color" onclick="SkinGetSkinColorClicked();">
+ <input type="text" readonly size="20" id="skin_t_SkinColor">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;color name:<input type="text" size="20" id="skin_t_SkinColorName_in" value="wasabi.text.color">
+ <br>
+ <h3>Properties:</h3>
+ <input type="button" value="Get name" onclick="SkinGetNameClicked();">
+ <input type="text" readonly size="20" id="skin_t_Name">
+ <br>
+ <input type="button" value="Set name" onclick="NotImplementedYet();">
+ <!-- <input type="button" value="Set name" onclick="SkinSetNameClicked();"> -->
+ <input type="text" size="20" id="skin_t_Name_in" value="Bento">
+ <br><br>
+ <input type="button" value="Get font" onclick="SkinGetFontClicked();">
+ <input type="text" readonly size="20" id="skin_t_Font">
+ <br><br>
+ <input type="button" value="Get fontsize" onclick="SkinGetFontSizeClicked();">
+ <input type="text" readonline size="20" id="skin_t_FontSize">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/transportApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/transportApi.htm
new file mode 100644
index 00000000..c2f9d70d
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/transportApi.htm
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Transport Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Transport API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Prev" onclick="tPrevClicked();">
+ <input type="button" value="Play" onclick="tPlayClicked();">
+ <input type="button" value="Pause" onclick="tPauseClicked();">
+ <input type="button" value="Stop" onclick="tStopClicked();">
+ <input type="button" value="Next" onclick="tNextClicked();">
+ <br>
+ <br>
+ <input type="button" value="Get Metadata" onclick="tGetMetadataClicked();">
+ <input type="text" readonline size="40" id="t_t_mTag_Response">
+ <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Metadata Tag:&nbsp;<input type="text" value="artist" id="t_t_mTag_in">
+ <br>
+ <h3>Properties</h3>
+ <input type="button" value="Get shuffle" onclick="tGetShuffleClicked();">
+ <input type="text" readonline size="20" id="t_t_shuffle">
+ <br>
+ <input type="button" value="Set shuffle" onclick="tSetShuffleClicked();">
+ <input type="text" size="20" id="t_t_shuffle_in">
+ <br>
+ <br>
+ <input type="button" value="Get repeat" onclick="tGetRepeatClicked();">
+ <input type="text" readonly size="20" id="t_t_repeat">
+ <br>
+ <input type="button" value="Set repeat" onclick="tSetRepeatClicked();">
+ <input type="text" size="20" id="t_t_repeat_in">
+ <br>
+ <br>
+ <input type="button" value="Get position" onclick="tGetPositionClicked();">
+ <input type="text" readonly size="20" id="t_t_position">
+ <br>
+ <input type="button" value="Set position" onclick="tSetPositionClicked();">
+ <input type="text" size="20" id="t_t_position_in">
+ <br>
+ <br>
+ <input type="button" value="Get length" id="b_t_length" onclick="tLengthClicked();">
+ <input type="text" readonly size="20" id="t_t_Length">
+ <br>
+ <br>
+ <input type="button" value="Get url" id="b_t_url" onclick="tURLClicked();">
+ <input type="text" size="95" id="t_t_URL">
+ <br>
+ <br>
+ <input type="button" value="Get title" id="b_t_title" onclick="tTitleClicked();">
+ <input type="text" size="95" id="t_t_Title">
+ <br>
+ <br>
+ <input type="button" value="Get playing" id="b_t_playing" onclick="tPlayingClicked();">
+ <input type="text" readonly size="20" id="t_t_Playing">
+ <br>
+ <br>
+ <input type="button" value="Get paused" id="b_t_paused" onclick="tPausedClicked();">
+ <input type="text" readonly size="20" id="t_t_Paused">
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/transportEventsApi.htm b/Src/Plugins/Library/ml_webdev/resources/pages/transportEventsApi.htm
new file mode 100644
index 00000000..61d91e2b
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/transportEventsApi.htm
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Transport Events Api</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+</head>
+<body onload="teUnregisterClicked();" onunload="teUnregisterClicked();" style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Transport Events API</h2>
+ <h3>Methods:</h3>
+ <input type="button" value="Register For Events" id="teRegister" onclick="teRegisterClicked();">
+ <input type="button" value="Unregister From Events" id="teUnregister" onclick="teUnregisterClicked();">
+ <br><br>
+ &nbsp;&nbsp;&nbsp;&nbsp;Callback function:&nbsp;<input readonly type="text" id="te_t_Callback" value="OnEvents">
+ <br><br><br>
+ <b>Events Received</b>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <a href="javascript:te_AreaCleared();"><small>clear</small></a>
+ <br><br>
+ <select size="10" id="te_s_Select" style="width: 100px" onchange="teEventSelected();">
+ <option>No Events</option>
+ </select>
+ <textarea rows="10" cols="40" id="te_ta_Area"></textarea>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/webdev.htm b/Src/Plugins/Library/ml_webdev/resources/pages/webdev.htm
new file mode 100644
index 00000000..b48a8515
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/webdev.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>JSAPI2::Main Page</title>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <script type="text/javascript" src="api2.js"></script>
+ <script type="text/javascript" src="webdev.js"></script>
+</head>
+<body style="FONT-FAMILY: MS Shell Dlg 2;">
+ <h2>Winamp Online Services API - Test Web Page</h2>
+ <small>
+ <p>This page is to help with testing the different API methods of the Winamp Javascript API. In order to point Winamp to your own online service, click <a href="javascript:void(0);" onclick="WebDev_CreateService(); return false;">here</a> and set the URL to the URL of the main page of your service when prompted.</p>
+ <p>Check the <a target="_blank" href="http://dev.winamp.com/wiki/Complete_JavaScript_API_technology_framework">Winamp Javascript API</a> for more info on the API methods used on these test web pages.</p>
+ <a href="transportApi.htm">Transport API</a><br><br>
+ <a href="transportEventsApi.htm">Transport Events</a><br><br>
+ <a href="playQueueApi.htm">Playqueue API</a><br><br>
+ <a href="playlistApi.htm">Playlists API</a><br><br>
+ <a href="bookmarkApi.htm">Bookmarks API</a><br><br>
+ <a href="podcastApi.htm">Podcasts API</a><br><br>
+ <a href="historyApi.htm">History API</a><br><br>
+ <a href="configApi.htm">Config API</a><br><br>
+ <a href="applicationApi.htm">Application API</a><br><br>
+ <a href="securityApi.htm">Security API</a><br><br>
+ <a href="skinApi.htm">Skins API</a><br><br>
+ <a href="mediaCoreApi.htm">MediaCore API</a><br><br>
+ <a href="asyncDownloaderApi.htm">AsyncDownloader API</a>
+ </small>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/webdev.js b/Src/Plugins/Library/ml_webdev/resources/pages/webdev.js
new file mode 100644
index 00000000..14f5d199
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/webdev.js
@@ -0,0 +1,97 @@
+function GetUrlParam(name)
+{
+ name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
+ var regexS = "[\\?&]"+name+"=([^&#]*)";
+ var regex = new RegExp( regexS );
+ var results = regex.exec( window.location.href );
+ if( results == null )
+ return "";
+ else
+ return results[1];
+}
+
+function WebDev_OpenService(serviceId, forceUrl)
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else if (false == window.external.WebDev.serviceOpen(serviceId, forceUrl))
+ alert("Unable to open service");
+}
+function WebDev_OpenDocumentation()
+{
+ WebDev_OpenService(701, null);
+}
+
+function WebDev_OpenJSAPI2Test()
+{
+ WebDev_OpenService(702, null);
+}
+
+function WebDev_CreateService()
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else if (false == window.external.WebDev.serviceCreate())
+ alert("Unable to create service");
+}
+
+function WebDevEditor_Init(url)
+{
+ var serviceId = parseInt(GetUrlParam("serviceId"), 10);
+
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else
+ {
+ var info = window.external.WebDev.serviceGetInfo(serviceId);
+ if (null == info)
+ {
+ alert("Unable to get service information");
+ }
+ else
+ {
+ document.getElementById("scvedt_edt_id").value = info.id;
+ document.getElementById("scvedt_edt_name").value = info.name;
+ document.getElementById("scvedt_edt_url").value = info.url;
+ document.getElementById("scvedt_edt_icon").value = info.icon;
+ document.getElementById("svcedt_chk_bypass").checked = (true == info.preauthorized) ? "checked" : null;
+ }
+ }
+}
+
+function WebDevEditor_Save()
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else
+ {
+ var serviceId = parseInt(document.getElementById("scvedt_edt_id").value, 10);
+ if (0 != serviceId)
+ {
+ var serviceName = document.getElementById("scvedt_edt_name").value;
+ var serviceUrl = document.getElementById("scvedt_edt_url").value;
+ var serviceIcon = document.getElementById("scvedt_edt_icon").value;
+ var serviceAuth = document.getElementById("svcedt_chk_bypass").checked;
+ if (false == window.external.WebDev.serviceSetInfo(serviceId, serviceName, serviceIcon, serviceUrl, serviceAuth))
+ {
+ alert("Unable to set service info");
+ }
+ if (false == window.external.WebDev.serviceOpen(serviceId, 1))
+ {
+ alert("Unable to navigate");
+ }
+ }
+ }
+}
+
+function WebDevEditor_Close()
+{
+ if (typeof(window.external.WebDev) == "undefined")
+ alert("Cannot access Webdev Api");
+ else
+ {
+ var serviceId = parseInt(document.getElementById("scvedt_edt_id").value, 10);
+ if (0 == serviceId || false == window.external.WebDev.serviceOpen(serviceId, 1))
+ alert("Unable to navigate");
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/resources/pages/welcome.htm b/Src/Plugins/Library/ml_webdev/resources/pages/welcome.htm
new file mode 100644
index 00000000..a89bc2c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/resources/pages/welcome.htm
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>WebDev Home Page</title>
+ <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
+ <meta name="vs_snapToGrid" content="True">
+ <meta name="vs_showGrid" content="True">
+ <meta content="text/html;charset=utf-8" http-equiv="content-type">
+ <script type="text/javascript" src="webdev.js"></script>
+</head>
+<body style="OVERFLOW-Y: auto; OVERFLOW-X: auto; OVERFLOW: visible; FONT-FAMILY: MS Shell Dlg 2;">
+ <div align="left">
+ <div style="DISPLAY: inline; FONT-WEIGHT: bold; FONT-SIZE: x-large; LEFT: 50%; MARGIN-LEFT: -216px; WIDTH: 432px; POSITION: absolute" id="DIV4" ms_positioning="FlowLayout" language="javascript" onclick="return DIV2_onclick()" align="center">Welcome to WebDev</DIV>
+ <div style="DISPLAY: inline; FONT-SIZE: 9pt; LEFT: 50%; MARGIN-LEFT: -212px; WIDTH: 424px; POSITION: absolute; TOP: 96px; HEIGHT: 248px" ms_positioning="FlowLayout" align="justify" id='DIV1"'>
+ <p>The Webdev plug-in is designed to help with the process of creating Online Services for Winamp. The plug-in uses the same browser engine as the Online Media plug-in in the Media Library and also has access to JSAPI2, making it perfect for you to work with.</p>
+ <p>If you have any questions about how to write an Online Service or just want to share knowledge with others - the <a href="javascript:void(0);" onclick="WebDev_OpenDocumentation(); return false;">Online Documentation</a> is your best friend.</p>
+ <p>New to JSAPI? Want to know what it is capable of and see the client web interaction with your own eyes? Then visit the <a href="javascript:void(0);" onclick="WebDev_OpenJSAPI2Test(); return false;">JSAPI2 Test Page</a>.</p>
+ <p>Having your own service is always better than using another, so if you feel like it is time to change the world - <a href="javascript:void(0);" onclick="WebDev_CreateService(); return false;">Create Your Own Service</a> and make the world a better place!</p>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/serviceHelper.cpp b/Src/Plugins/Library/ml_webdev/serviceHelper.cpp
new file mode 100644
index 00000000..4f0723e2
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/serviceHelper.cpp
@@ -0,0 +1,232 @@
+#include "main.h"
+#include "./serviceHelper.h"
+#include "./wasabi.h"
+#include "./serviceHost.h"
+
+#include <ifc_omservice.h>
+#include <storageIni.h>
+#include <ifc_omserviceeditor.h>
+#include <ifc_omserviceeditor.h>
+#include <ifc_mlnavigationhelper.h>
+
+#include <strsafe.h>
+
+HRESULT ServiceHelper_QueryStorage(ifc_omstorage **storage)
+{
+ if (NULL == storage) return E_POINTER;
+
+ if (NULL == OMSERVICEMNGR)
+ {
+ *storage = NULL;
+ return E_UNEXPECTED;
+ }
+
+ return OMSERVICEMNGR->QueryStorage(&SUID_OmStorageIni, storage);
+}
+
+HRESULT ServiceHelper_Create(UINT serviceId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, UINT flags, BOOL fSave, ifc_omservice **serviceOut)
+{
+ if (NULL == serviceOut)
+ return E_POINTER;
+
+ *serviceOut = NULL;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ WebDevServiceHost *serviceHost;
+ if (FAILED(WebDevServiceHost::GetCachedInstance(&serviceHost)))
+ serviceHost = NULL;
+
+ HRESULT hr = OMSERVICEMNGR->CreateService(serviceId, serviceHost, serviceOut);
+ if (SUCCEEDED(hr))
+ {
+ ifc_omserviceeditor *editor;
+ if (SUCCEEDED((*serviceOut)->QueryInterface(IFC_OmServiceEditor, (void**)&editor)))
+ {
+ WCHAR szBuffer[4096] = {0};
+ editor->BeginUpdate();
+ if (NULL != pszName && IS_INTRESOURCE(pszName))
+ {
+ if (NULL != WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszName, szBuffer, ARRAYSIZE(szBuffer)))
+ editor->SetName(szBuffer, FALSE);
+ }
+ else
+ editor->SetName(pszName, FALSE);
+
+ if (NULL != pszIcon && IS_INTRESOURCE(pszIcon))
+ {
+ if (SUCCEEDED(Plugin_MakeResourcePath(szBuffer, ARRAYSIZE(szBuffer), RT_RCDATA, pszIcon, RESPATH_COMPACT)))
+ editor->SetIcon(szBuffer, FALSE);
+ }
+ else
+ editor->SetIcon(pszIcon, FALSE);
+
+ if (NULL != pszUrl && IS_INTRESOURCE(pszUrl))
+ {
+ if (SUCCEEDED(Plugin_MakeResourcePath(szBuffer, ARRAYSIZE(szBuffer), RT_HTML, pszUrl, RESPATH_TARGETIE | RESPATH_COMPACT)))
+ editor->SetUrl(szBuffer, FALSE);
+ }
+ else
+ editor->SetUrl(pszUrl, FALSE);
+
+ editor->SetFlags(flags, 0xFFFFFFFF);
+
+ if (FALSE != fSave)
+ {
+ hr = ServiceHelper_Save(*serviceOut);
+ if (FAILED(hr))
+ {
+ (*serviceOut)->Release();
+ *serviceOut = NULL;
+ }
+ }
+ else
+ {
+ editor->SetModified(0, (UINT)-1);
+ }
+ editor->EndUpdate();
+ editor->Release();
+ }
+ }
+
+ if (NULL != serviceHost)
+ serviceHost->Release();
+
+ return hr;
+}
+
+HRESULT ServiceHelper_Save(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ HRESULT hr;
+ ifc_omstorage *storage;
+ hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ hr = storage->Save(&service, 1, ifc_omstorage::saveModifiedOnly | ifc_omstorage::saveClearModified, NULL);
+ storage->Release();
+ }
+ return hr;
+}
+
+HRESULT ServiceHelper_Delete(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ifc_omstorage *storage;
+ HRESULT hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ hr = storage->Delete(&service, 1, NULL);
+ storage->Release();
+ }
+ return hr;
+}
+
+HRESULT ServiceHelper_Reload(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ if (NULL == OMSERVICEMNGR)
+ return E_UNEXPECTED;
+
+ ifc_omstorage *storage;
+ HRESULT hr = ServiceHelper_QueryStorage(&storage);
+ if (SUCCEEDED(hr))
+ {
+ hr = storage->Reload(&service, 1, NULL);
+ storage->Release();
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_UpdateIcon(ifc_omserviceeditor *editor, LPCWSTR pszImage)
+{
+ WCHAR szBuffer[8192];
+ szBuffer[0] = L'\0';
+
+ if (NULL == editor)
+ return E_INVALIDARG;
+
+ ifc_omservice *service;
+ if (SUCCEEDED(editor->QueryInterface(IFC_OmService, (void**)&service)))
+ {
+ if (FAILED(service->GetIcon(szBuffer, ARRAYSIZE(szBuffer))))
+ szBuffer[0] = L'\0';
+ service->Release();
+ }
+
+ HRESULT hr = editor->SetIcon(pszImage, FALSE);
+ if (FAILED(hr) || S_FALSE == hr) return hr;
+
+ if (L'\0' != szBuffer)
+ {
+ ifc_mlnavigationhelper *navHelper;
+ if (SUCCEEDED(OMUTILITY->GetMlNavigationHelper(Plugin_GetLibrary(), &navHelper)))
+ {
+ navHelper->ReleaseIndex(szBuffer);
+ navHelper->Release();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ServiceHelper_IsSpecial(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ UINT serviceFlags;
+ HRESULT hr = service->GetFlags(&serviceFlags);
+ if (SUCCEEDED(hr))
+ {
+ hr = (0 != ((WDSVCF_ROOT | WDSVCF_SPECIAL) & serviceFlags)) ? S_OK : S_FALSE;
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_IsPreAuthorized(ifc_omservice *service)
+{
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ UINT serviceFlags;
+ HRESULT hr = service->GetFlags(&serviceFlags);
+ if (SUCCEEDED(hr))
+ {
+ hr = (0 != (WDSVCF_PREAUTHORIZED & serviceFlags)) ? S_OK : S_FALSE;
+ }
+
+ return hr;
+}
+
+HRESULT ServiceHelper_RegisterPreAuthorized(ifc_omservice *service)
+{
+ if (NULL == AGAVE_API_JSAPI2_SECURITY)
+ return E_UNEXPECTED;
+
+ HRESULT hr;
+ WCHAR szBuffer[64];
+ hr = StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer), L"%u", service->GetId());
+ if (SUCCEEDED(hr))
+ {
+ UINT flags;
+ bool bypassEnabled = (SUCCEEDED(service->GetFlags(&flags)) && 0 != (WDSVCF_PREAUTHORIZED & flags));
+ AGAVE_API_JSAPI2_SECURITY->SetBypass(szBuffer, bypassEnabled);
+ }
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/serviceHelper.h b/Src/Plugins/Library/ml_webdev/serviceHelper.h
new file mode 100644
index 00000000..6359a199
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/serviceHelper.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_SERVICE_HELPER_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_SERVICE_HELPER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class ifc_omservice;
+class ifc_omserviceeditor;
+class ifc_omstorage;
+
+#define WDSVCF_ROOT 0x00000001
+#define WDSVCF_SPECIAL 0x00000002
+#define WDSVCF_PREAUTHORIZED 0x00000004
+
+HRESULT ServiceHelper_QueryStorage(ifc_omstorage **storage);
+HRESULT ServiceHelper_Create(UINT serviceId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, UINT flags, BOOL fSave, ifc_omservice **serviceOut);
+HRESULT ServiceHelper_Save(ifc_omservice *service);
+HRESULT ServiceHelper_Delete(ifc_omservice *service);
+HRESULT ServiceHelper_Reload(ifc_omservice *service);
+HRESULT ServiceHelper_UpdateIcon(ifc_omserviceeditor *editor, LPCWSTR pszImage);
+HRESULT ServiceHelper_IsSpecial(ifc_omservice *service);
+HRESULT ServiceHelper_IsPreAuthorized(ifc_omservice *service);
+HRESULT ServiceHelper_RegisterPreAuthorized(ifc_omservice *service);
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_SERVICE_HELPER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/serviceHost.cpp b/Src/Plugins/Library/ml_webdev/serviceHost.cpp
new file mode 100644
index 00000000..a5c746bb
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/serviceHost.cpp
@@ -0,0 +1,243 @@
+#include "main.h"
+#include "./serviceHost.h"
+#include "./wasabi.h"
+#include "./resource.h"
+#include "./external.h"
+#include "./navigation.h"
+#include "./commands.h"
+#include "./serviceHelper.h"
+
+#include <ifc_omservice.h>
+#include <ifc_omserviceeditor.h>
+#include <ifc_omservicecommand.h>
+#include <browserView.h>
+
+#include "../winamp/wa_ipc.h"
+#include "../winamp/IWasabiDispatchable.h"
+#include "../winamp/JSAPI_Info.h"
+
+#include <strsafe.h>
+
+
+#define IS_INVALIDISPATCH(__disp) (((IDispatch *)1) == (__disp) || NULL == (__disp))
+
+static WebDevServiceHost *cachedInstance = NULL;
+
+WebDevServiceHost::WebDevServiceHost()
+ : ref(1)
+{
+
+}
+
+WebDevServiceHost::~WebDevServiceHost()
+{
+
+}
+
+HRESULT WebDevServiceHost::CreateInstance(WebDevServiceHost **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new WebDevServiceHost();
+ if (NULL == instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+HRESULT WebDevServiceHost::GetCachedInstance(WebDevServiceHost **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+
+ if (NULL == cachedInstance)
+ {
+ HRESULT hr = CreateInstance(&cachedInstance);
+ if (FAILED(hr))
+ {
+ *instance = NULL;
+ return hr;
+ }
+ }
+
+ cachedInstance->AddRef();
+ *instance = cachedInstance;
+ return S_OK;
+}
+
+HRESULT WebDevServiceHost::ReleseCache()
+{
+ if (NULL == cachedInstance)
+ return S_FALSE;
+
+ WebDevServiceHost *t = cachedInstance;
+ cachedInstance = NULL;
+ t->Release();
+
+ return S_OK;
+}
+
+size_t WebDevServiceHost::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t WebDevServiceHost::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int WebDevServiceHost::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_OmServiceHost))
+ *object = static_cast<ifc_omservicehost*>(this);
+ else if (IsEqualIID(interface_guid, IFC_OmServiceEvent))
+ *object = static_cast<ifc_omserviceevent*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT WebDevServiceHost::GetExternal(ifc_omservice *service, IDispatch **ppDispatch)
+{
+ if (NULL == ppDispatch)
+ return E_POINTER;
+
+ if (NULL != *ppDispatch)
+ {
+ // try to connect our external
+ IWasabiDispatchable *pWasabi;
+ if (SUCCEEDED((*ppDispatch)->QueryInterface(IID_IWasabiDispatchable, (void**)&pWasabi)))
+ {
+ JSAPI::ifc_info *pInfo;
+ if (SUCCEEDED(pWasabi->QueryDispatchable(JSAPI::IID_JSAPI_ifc_info, (Dispatchable**)&pInfo)))
+ {
+ ExternalDispatch *pExternal;
+ if (SUCCEEDED(ExternalDispatch::CreateInstance(&pExternal)))
+ {
+ pInfo->AddAPI(pExternal->GetName(), pExternal);
+ pExternal->Release();
+ }
+ pInfo->Release();
+ }
+ pWasabi->Release();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT WebDevServiceHost::GetBasePath(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ return StringCchCopy(pszBuffer, cchBufferMax, L".\\Plugins\\webDev");
+}
+
+HRESULT WebDevServiceHost::GetDefaultName(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ if (NULL == service)
+ return E_INVALIDARG;
+
+ return StringCchPrintf(pszBuffer, cchBufferMax, L"wdService_{%010u}.ini", service->GetId());
+}
+
+HRESULT WebDevServiceHost::QueryCommandState(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId)
+{
+ if (NULL == service || NULL == commandGroup)
+ return E_NOTIMPL;
+
+ if (IsEqualGUID(*commandGroup, CMDGROUP_ADDRESSBAR))
+ {
+ switch(commandId)
+ {
+ case ADDRESSCOMMAND_VISIBLE:
+ if (S_FALSE == ServiceHelper_IsSpecial(service))
+ return CMDSTATE_ENABLED;
+ return CMDSTATE_UNKNOWN;
+ case ADDRESSCOMMAND_READONLY:
+ if (S_FALSE == ServiceHelper_IsSpecial(service))
+ return CMDSTATE_DISABLED;
+ return CMDSTATE_ENABLED;
+ }
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT WebDevServiceHost::ExecuteCommand(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId, ULONG_PTR commandArg)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT WebDevServiceHost::GetUrl(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax)
+{
+ return E_NOTIMPL;
+}
+
+
+void WebDevServiceHost::ServiceChange(ifc_omservice *service, UINT nModified)
+{
+ if (NULL == service) return;
+ Navigation *navigation;
+ if (SUCCEEDED(Plugin_GetNavigation(&navigation)))
+ {
+ navigation->UpdateService(service, nModified);
+ navigation->Release();
+ }
+
+ if ( 0 != (ifc_omserviceeditor::modifiedUrl & nModified))
+ {
+ Command_PostNavigateSvc(service, MAKEINTRESOURCE
+ (NAVIGATE_HOME), TRUE);
+ }
+ if (0 != (ifc_omserviceeditor::modifiedFlags & nModified))
+ {
+ ServiceHelper_RegisterPreAuthorized(service);
+ }
+}
+
+#define CBCLASS WebDevServiceHost
+START_MULTIPATCH;
+ START_PATCH(MPIID_OMSVCHOST)
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, ADDREF, AddRef);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, RELEASE, Release);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, QUERYINTERFACE, QueryInterface);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETEXTERNAL, GetExternal);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETBASEPATH, GetBasePath);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETDEFAULTNAME, GetDefaultName);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_QUERYCOMMANDSTATE, QueryCommandState);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_EXECUTECOMMAND, ExecuteCommand);
+ M_CB(MPIID_OMSVCHOST, ifc_omservicehost, API_GETURL, GetUrl);
+
+ NEXT_PATCH(MPIID_OMSVCEVENT)
+ M_CB(MPIID_OMSVCEVENT, ifc_omserviceevent, ADDREF, AddRef);
+ M_CB(MPIID_OMSVCEVENT, ifc_omserviceevent, RELEASE, Release);
+ M_CB(MPIID_OMSVCEVENT, ifc_omserviceevent, QUERYINTERFACE, QueryInterface);
+ M_VCB(MPIID_OMSVCEVENT, ifc_omserviceevent, API_SERVICECHANGE, ServiceChange);
+
+ END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_webdev/serviceHost.h b/Src/Plugins/Library/ml_webdev/serviceHost.h
new file mode 100644
index 00000000..7dbce0e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/serviceHost.h
@@ -0,0 +1,55 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_SERVICE_HOST_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_SERVICE_HOST_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <ifc_omservicehost.h>
+#include <ifc_omserviceevent.h>
+#include <bfc/multipatch.h>
+
+#define MPIID_OMSVCHOST 10
+#define MPIID_OMSVCEVENT 20
+
+class WebDevServiceHost : public MultiPatch<MPIID_OMSVCHOST, ifc_omservicehost>,
+ public MultiPatch<MPIID_OMSVCEVENT, ifc_omserviceevent>
+{
+
+protected:
+ WebDevServiceHost();
+ ~WebDevServiceHost();
+
+public:
+ static HRESULT CreateInstance(WebDevServiceHost **instance);
+ static HRESULT GetCachedInstance(WebDevServiceHost **instance);
+ static HRESULT ReleseCache();
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_omservicehost */
+ HRESULT GetExternal(ifc_omservice *service, IDispatch **ppDispatch);
+ HRESULT GetBasePath(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT GetDefaultName(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax);
+ HRESULT QueryCommandState(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId);
+ HRESULT ExecuteCommand(ifc_omservice *service, HWND hBrowser, const GUID *commandGroup, UINT commandId, ULONG_PTR commandArg);
+ HRESULT GetUrl(ifc_omservice *service, LPWSTR pszBuffer, UINT cchBufferMax);
+
+ /* ifc_omsvceventhandler */
+ void ServiceChange(ifc_omservice *service, UINT nModified);
+
+protected:
+ ULONG ref;
+
+protected:
+ RECVS_MULTIPATCH;
+};
+
+
+
+
+#endif //NULLSOFT_WEBDEV_PLUGIN_SERVICE_HOST_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/testPages.rc b/Src/Plugins/Library/ml_webdev/testPages.rc
new file mode 100644
index 00000000..620c0138
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/testPages.rc
@@ -0,0 +1,22 @@
+#include "./resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// HTML
+//
+
+IDR_HTML_TEST HTML ".\\resources\\pages\\webdev.htm"
+APPLICATIONAPI.HTM HTML ".\\resources\\pages\\applicationApi.htm"
+BOOKMARKAPI.HTM HTML ".\\resources\\pages\\bookmarkApi.htm"
+CONFIGAPI.HTM HTML ".\\resources\\pages\\configApi.htm"
+HISTORYAPI.HTM HTML ".\\resources\\pages\\historyApi.htm"
+MEDIACOREAPI.HTM HTML ".\\resources\\pages\\mediaCoreApi.htm"
+PLAYLISTAPI.HTM HTML ".\\resources\\pages\\playlistApi.htm"
+PLAYQUEUEAPI.HTM HTML ".\\resources\\pages\\playQueueApi.htm"
+PODCASTAPI.HTM HTML ".\\resources\\pages\\podcastApi.htm"
+SECURITYAPI.HTM HTML ".\\resources\\pages\\securityApi.htm"
+SKINAPI.HTM HTML ".\\resources\\pages\\skinApi.htm"
+TRANSPORTAPI.HTM HTML ".\\resources\\pages\\transportApi.htm"
+TRANSPORTEVENTSAPI.HTM HTML ".\\resources\\pages\\transportEventsApi.htm"
+API2.JS HTML ".\\resources\\pages\\api2.js"
+WEBDEV.JS HTML ".\\resources\\pages\\webdev.js"
+ASYNCDOWNLOADERAPI.HTM HTML ".\\resources\\pages\\asyncDownloaderApi.htm" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/version.rc2 b/Src/Plugins/Library/ml_webdev/version.rc2
new file mode 100644
index 00000000..1b875991
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,3,4,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", "2,3,4,0"
+ VALUE "InternalName", "Nullsoft WebDev Test Platform"
+ VALUE "LegalCopyright", "Copyright © 1997-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_webdev.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_webdev/wasabi.cpp b/Src/Plugins/Library/ml_webdev/wasabi.cpp
new file mode 100644
index 00000000..136cfe5f
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/wasabi.cpp
@@ -0,0 +1,87 @@
+#include "main.h"
+#include "./wasabi.h"
+#include <api/service/waservicefactory.h>
+
+static ULONG wasabiRef = 0;
+
+api_application *WASABI_API_APP = NULL;
+api_language *WASABI_API_LNG = NULL;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = NULL;
+obj_ombrowser *browserManager = NULL;
+ifc_omservicemanager *serviceManager = NULL;
+ifc_omutility *omUtility = NULL;
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = NULL;
+
+HINSTANCE WASABI_API_LNG_HINST = NULL;
+HINSTANCE WASABI_API_ORIG_HINST = NULL;
+EXTERN_C winampMediaLibraryPlugin plugin;
+
+void *Wasabi_QueryInterface(REFGUID interfaceGuid)
+{
+ waServiceFactory *serviceFactory = plugin.service->service_getServiceByGuid(interfaceGuid);
+ return (NULL != serviceFactory) ? serviceFactory->getInterface() : NULL;
+}
+
+void Wasabi_ReleaseInterface(REFGUID interfaceGuid, void *pInstance)
+{
+ if (NULL == pInstance) return;
+ waServiceFactory *serviceFactory = plugin.service->service_getServiceByGuid(interfaceGuid);
+ if (NULL != serviceFactory) serviceFactory->releaseInterface(pInstance);
+}
+
+void Wasabi_SafeRelease(Dispatchable *pDisp)
+{
+ if (NULL != pDisp)
+ pDisp->Release();
+}
+
+BOOL WasabiApi_Initialize(HINSTANCE hInstance)
+{
+ WASABI_API_APP = QueryWasabiInterface(api_application, applicationApiServiceGuid);
+ WASABI_API_LNG = QueryWasabiInterface(api_language, languageApiGUID);
+ WASABI_API_EXPLORERFINDFILE = QueryWasabiInterface(api_explorerfindfile, ExplorerFindFileApiGUID);
+ AGAVE_API_JSAPI2_SECURITY = QueryWasabiInterface(JSAPI2::api_security, JSAPI2::api_securityGUID);
+ OMBROWSERMNGR = QueryWasabiInterface(obj_ombrowser, OBJ_OmBrowser);
+ OMSERVICEMNGR = QueryWasabiInterface(ifc_omservicemanager, IFC_OmServiceManager);
+ OMUTILITY = QueryWasabiInterface(ifc_omutility, IFC_OmUtility);
+
+ if (NULL != WASABI_API_LNG)
+ WASABI_API_START_LANG(hInstance, WebDevLangUid);
+
+ WasabiApi_AddRef();
+ return TRUE;
+}
+
+static void WasabiApi_Uninitialize()
+{
+ ReleaseWasabiInterface(applicationApiServiceGuid, WASABI_API_APP);
+ ReleaseWasabiInterface(languageApiGUID, WASABI_API_LNG);
+ ReleaseWasabiInterface(ExplorerFindFileApiGUID, WASABI_API_EXPLORERFINDFILE);
+ ReleaseWasabiInterface(JSAPI2::api_securityGUID, AGAVE_API_JSAPI2_SECURITY);
+ ReleaseWasabiInterface(OBJ_OmBrowser, OMBROWSERMNGR);
+ ReleaseWasabiInterface(IFC_OmServiceManager, OMSERVICEMNGR);
+ ReleaseWasabiInterface(IFC_OmUtility, OMUTILITY);
+
+ WASABI_API_APP = NULL;
+ WASABI_API_LNG = NULL;
+ WASABI_API_EXPLORERFINDFILE = NULL;
+ AGAVE_API_JSAPI2_SECURITY = NULL;
+}
+
+ULONG WasabiApi_AddRef()
+{
+ return InterlockedIncrement((LONG*)&wasabiRef);
+}
+
+ULONG WasabiApi_Release()
+{
+ if (0 == wasabiRef)
+ return wasabiRef;
+
+ LONG r = InterlockedDecrement((LONG*)&wasabiRef);
+ if (0 == r)
+ {
+ WasabiApi_Uninitialize();
+ }
+ return r;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_webdev/wasabi.h b/Src/Plugins/Library/ml_webdev/wasabi.h
new file mode 100644
index 00000000..1149cac5
--- /dev/null
+++ b/Src/Plugins/Library/ml_webdev/wasabi.h
@@ -0,0 +1,45 @@
+#ifndef NULLSOFT_WEBDEV_PLUGIN_WASABI_HEADER
+#define NULLSOFT_WEBDEV_PLUGIN_WASABI_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_securityApi;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_securityApi
+
+#include <obj_ombrowser.h>
+extern obj_ombrowser *browserManager;
+#define OMBROWSERMNGR browserManager
+
+#include <ifc_omservicemanager.h>
+extern ifc_omservicemanager *serviceManager;
+#define OMSERVICEMNGR serviceManager
+
+#include <ifc_omutility.h>
+extern ifc_omutility *omUtility;
+#define OMUTILITY omUtility
+
+BOOL WasabiApi_Initialize(HINSTANCE hInstance);
+ULONG WasabiApi_AddRef(void);
+ULONG WasabiApi_Release(void);
+
+void *Wasabi_QueryInterface(REFGUID interfaceGuid);
+void Wasabi_ReleaseInterface(REFGUID interfaceGuid, void *pInstance);
+
+#define QueryWasabiInterface(__interfaceType, __interfaceGuid) ((##__interfaceType*)Wasabi_QueryInterface(__interfaceGuid))
+#define ReleaseWasabiInterface(__interfaceGuid, __interfaceInstance) (Wasabi_ReleaseInterface((__interfaceGuid), (__interfaceInstance)))
+
+void Wasabi_SafeRelease(Dispatchable *pDisp);
+
+#endif // NULLSOFT_WEBDEV_PLUGIN_WASABI_HEADER
diff --git a/Src/Plugins/Library/ml_wire/AtomParse.h b/Src/Plugins/Library/ml_wire/AtomParse.h
new file mode 100644
index 00000000..ab76a1f9
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/AtomParse.h
@@ -0,0 +1,51 @@
+#ifndef NULLSOFT_ATOMPARSEH
+#define NULLSOFT_ATOMPARSEH
+#if 0
+#include "RFCDate.h"
+#include "XMLNode.h"
+#include "Feeds.h"
+#include "../nu/AutoChar.h"
+#include "ChannelSync.h"
+
+void ReadAtomItem(XMLNode *item, Channel &channel)
+{
+}
+
+void ReadAtomChannel(XMLNode *node, Channel &newChannel)
+{
+ XMLNode *curNode = 0;
+ curNode = node->Get(L"title");
+ if (curNode)
+ newChanneltitle = curNode->content;
+
+ curNode = node->Get(L"subtitle");
+ if (curNode)
+ newChannel.description = curNode->content;
+
+ XMLNode::NodeList &links = node->GetList(L"link");
+ XMLNode::NodeList::iterator linkItr;
+ for (linkItr=links.begin();linkItr!=links.end();linkItr++)
+ {
+ if ((*linkItr)->properties[L"rel"].empty()
+ || (*linkItr)->properties[L"rel"]== L"alternate")
+ newChannel.link = (*linkItr)->properties[L"href"];
+ }
+
+ XMLNode::NodeList &entries = node->GetList(L"entry");
+ XMLNode::NodeList::iterator entryItr;
+ for (entryItr=entries.begin();entryItr!=entries.end();entryItr++)
+ {
+ }
+}
+
+void ReadAtom(XMLNode *atom, ChannelSync *sync)
+{
+ sync->BeginChannelSync();
+ Channel newChannel;
+ ReadAtomChannel(atom, newChannel);
+ sync->NewChannel(newChannel);
+
+ sync->EndChannelSync();
+}
+#endif
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/BackgroundDownloader.cpp b/Src/Plugins/Library/ml_wire/BackgroundDownloader.cpp
new file mode 100644
index 00000000..9e8c0cac
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/BackgroundDownloader.cpp
@@ -0,0 +1,338 @@
+#include <vector>
+#include <atomic>
+
+#include "Main.h"
+#include "Downloaded.h"
+#include "BackgroundDownloader.h"
+
+#include "Feeds.h"
+#include "DownloadStatus.h"
+#include "DownloadsDialog.h"
+#include "api__ml_wire.h"
+#include "api/service/waServiceFactory.h"
+#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
+
+
+using namespace Nullsoft::Utility;
+
+#define SIMULTANEOUS_DOWNLOADS 2
+std::vector<DownloadToken> downloadsQueue;
+LockGuard downloadsQueueLock;
+
+class DownloaderCallback : public ifc_downloadManagerCallback
+{
+public:
+ DownloaderCallback( const wchar_t *url, const wchar_t *destination_filepath, const wchar_t *channel, const wchar_t *item, __time64_t publishDate )
+ {
+ this->hFile = INVALID_HANDLE_VALUE;
+ this->url = _wcsdup( url );
+ this->destination_filepath = _wcsdup( destination_filepath );
+ this->channel = _wcsdup( channel );
+ this->item = _wcsdup( item );
+ this->publishDate = publishDate;
+ this->totalSize = 0;
+ this->downloaded = 0;
+ }
+
+ void OnInit(DownloadToken token)
+ {
+ // ---- Inform the download status service of our presence----
+ downloadStatus.AddDownloadThread(token, this->channel, this->item, this->destination_filepath);
+ }
+
+ void OnConnect(DownloadToken token)
+ {
+ // ---- retrieve total size
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver(token);
+ if (http)
+ {
+ this->totalSize = http->content_length();
+ }
+
+ // ---- create file handle
+ hFile = CreateFile(destination_filepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if ( hFile == INVALID_HANDLE_VALUE )
+ {
+ WAC_API_DOWNLOADMANAGER->CancelDownload(token);
+ }
+ }
+
+ void OnData(DownloadToken token, void *data, size_t datalen)
+ {
+ // ---- OnConnect copied here due to dlmgr OnData called first
+ // ---- retrieve total size
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver(token);
+ if ( !this->totalSize && http )
+ {
+ this->totalSize = http->content_length();
+ }
+
+ if ( hFile == INVALID_HANDLE_VALUE )
+ {
+ // ---- create file handle
+ hFile = CreateFile(destination_filepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if ( hFile == INVALID_HANDLE_VALUE )
+ {
+ WAC_API_DOWNLOADMANAGER->CancelDownload(token);
+ return;
+ }
+ }
+ // ---- OnConnect to be removed once dlmgr is fixed
+
+ // ---- OnData
+ // ---- if file handle is invalid, then cancel download
+ if ( hFile == INVALID_HANDLE_VALUE )
+ {
+ WAC_API_DOWNLOADMANAGER->CancelDownload(token);
+ return;
+ }
+
+ this->downloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded(token);
+
+ if ( datalen > 0 )
+ {
+ // ---- hFile is valid handle, and write to disk
+ DWORD numWritten = 0;
+ WriteFile(hFile, data,(DWORD)datalen, &numWritten, FALSE);
+
+ // ---- failed writing the number of datalen characters, cancel download
+ if (numWritten != datalen)
+ {
+ WAC_API_DOWNLOADMANAGER->CancelDownload(token);
+ return;
+ }
+ }
+
+ // if killswitch is turned on, then cancel download
+ if ( downloadStatus.UpdateStatus(token, this->downloaded, this->totalSize) )
+ {
+ WAC_API_DOWNLOADMANAGER->CancelDownload(token);
+ }
+ }
+
+ void OnCancel(DownloadToken token)
+ {
+ if ( hFile != INVALID_HANDLE_VALUE )
+ {
+ CloseHandle(hFile);
+ DeleteFile(destination_filepath);
+ }
+ DownloadsUpdated(token,NULL);
+ downloadStatus.DownloadThreadDone(token);
+
+
+ {
+ AutoLock lock( downloadsQueueLock );
+
+ size_t l_index = 0;
+ for ( DownloadToken &l_download_token : downloadsQueue )
+ {
+ if ( l_download_token == token )
+ {
+ downloadsQueue.erase( downloadsQueue.begin() + l_index );
+ break;
+ }
+
+ ++l_index;
+ }
+ }
+
+ for ( DownloadToken &l_download_token : downloadsQueue )
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) )
+ {
+ WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token );
+ break;
+ }
+ }
+
+ this->Release();
+ }
+
+ void OnError(DownloadToken token, int error)
+ {
+ if ( hFile != INVALID_HANDLE_VALUE )
+ {
+ CloseHandle(hFile);
+ DeleteFile(destination_filepath);
+ }
+ DownloadsUpdated(token,NULL);
+ downloadStatus.DownloadThreadDone(token);
+
+ {
+ AutoLock lock(downloadsQueueLock);
+ for (size_t index = 0; index < downloadsQueue.size(); index++)
+ {
+ if (downloadsQueue.at(index) == token)
+ {
+ downloadsQueue.erase(downloadsQueue.begin() + index);
+ break;
+ }
+ }
+ for (size_t index = 0; index < downloadsQueue.size(); index++)
+ {
+ if(WAC_API_DOWNLOADMANAGER->IsPending(downloadsQueue.at(index)))
+ {
+ WAC_API_DOWNLOADMANAGER->ResumePendingDownload(downloadsQueue.at(index));
+ break;
+ }
+ }
+ }
+ this->Release();
+ }
+
+ void OnFinish( DownloadToken token )
+ {
+ if ( hFile != INVALID_HANDLE_VALUE )
+ {
+ CloseHandle( hFile );
+
+ DownloadedFile *data = new DownloadedFile( this->url, this->destination_filepath, this->channel, this->item, this->publishDate );
+ data->bytesDownloaded = this->downloaded;
+ data->totalSize = this->totalSize;
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+ addToLibrary_thread( *data );
+
+ AddPodcastData( *data );
+
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ }
+ else
+ {
+ DownloadsUpdated( token, NULL );
+ downloadStatus.DownloadThreadDone( token );
+ }
+
+ {
+ AutoLock lock( downloadsQueueLock );
+ size_t l_index = 0;
+ for ( DownloadToken &l_download_token : downloadsQueue )
+ {
+ if ( l_download_token == token )
+ {
+ downloadsQueue.erase( downloadsQueue.begin() + l_index );
+ break;
+ }
+
+ ++l_index;
+ }
+
+ for ( DownloadToken &l_download_token : downloadsQueue )
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) )
+ {
+ WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token );
+ break;
+ }
+ }
+ }
+
+ this->Release();
+ }
+
+
+ int GetSource( wchar_t *source, size_t source_cch )
+ {
+ return wcscpy_s( source, source_cch, this->channel );
+ }
+
+ int GetTitle( wchar_t *title, size_t title_cch )
+ {
+ return wcscpy_s( title, title_cch, this->item );
+ }
+
+ int GetLocation( wchar_t *location, size_t location_cch )
+ {
+ return wcscpy_s( location, location_cch, this->destination_filepath );
+ }
+
+
+ size_t AddRef()
+ {
+ return _ref_count.fetch_add( 1 );
+ }
+
+ size_t Release()
+ {
+ if ( _ref_count.load() == 0 )
+ return _ref_count.load();
+
+ LONG r = _ref_count.fetch_sub( 1 );
+ if ( r == 0 )
+ delete( this );
+
+ return r;
+ }
+
+private: // private destructor so no one accidentally calls delete directly on this reference counted object
+ ~DownloaderCallback()
+ {
+ if ( url )
+ free( url );
+
+ if ( destination_filepath )
+ free( destination_filepath );
+
+ if ( channel )
+ free( channel );
+
+ if ( item )
+ free( item );
+ }
+
+protected:
+ RECVS_DISPATCH;
+
+private:
+ HANDLE hFile;
+ wchar_t *url;
+ wchar_t *destination_filepath;
+ wchar_t *channel;
+ wchar_t *item;
+ __time64_t publishDate;
+ size_t totalSize;
+ size_t downloaded;
+
+ std::atomic<std::size_t> _ref_count = 1;
+};
+
+void BackgroundDownloader::Download( const wchar_t *url, const wchar_t *savePath, const wchar_t *channel, const wchar_t *item, __time64_t publishDate )
+{
+ DownloaderCallback *callback = new DownloaderCallback( url, savePath, channel, item, publishDate );
+ {
+ Nullsoft::Utility::AutoLock lock( downloadsQueueLock );
+ if ( downloadsQueue.size() < SIMULTANEOUS_DOWNLOADS )
+ {
+ DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( AutoChar( url ), callback, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_UI );
+ downloadsQueue.push_back( dt );
+ }
+ else
+ {
+ DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( AutoChar( url ), callback, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_PENDING | api_downloadManager::DOWNLOADEX_UI );
+ downloadsQueue.push_back( dt );
+ }
+ }
+}
+
+BackgroundDownloader downloader;
+
+#define CBCLASS DownloaderCallback
+START_DISPATCH;
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONFINISH, OnFinish )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCANCEL, OnCancel )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONERROR, OnError )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONDATA, OnData )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCONNECT, OnConnect )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONINIT, OnInit )
+CB( IFC_DOWNLOADMANAGERCALLBACK_GETSOURCE, GetSource )
+CB( IFC_DOWNLOADMANAGERCALLBACK_GETTITLE, GetTitle )
+CB( IFC_DOWNLOADMANAGERCALLBACK_GETLOCATION, GetLocation )
+CB( ADDREF, AddRef )
+CB( RELEASE, Release )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/BackgroundDownloader.h b/Src/Plugins/Library/ml_wire/BackgroundDownloader.h
new file mode 100644
index 00000000..6d2542a7
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/BackgroundDownloader.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_BACKGROUNDDOWNLOADERH
+#define NULLSOFT_BACKGROUNDDOWNLOADERH
+
+#include <windows.h>
+
+class BackgroundDownloader
+{
+public:
+ //void SetSpeed(int kilobytesPerSecond);
+
+ void Download(const wchar_t *url, const wchar_t *savePath,
+ const wchar_t *channel, const wchar_t *item, __time64_t publishDate);
+
+ //void Shutdown();
+};
+
+extern BackgroundDownloader downloader;
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ChannelCheck.h b/Src/Plugins/Library/ml_wire/ChannelCheck.h
new file mode 100644
index 00000000..5a80e7ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ChannelCheck.h
@@ -0,0 +1,14 @@
+#ifndef NULLSOFT_CHANNELCHECKH
+#define NULLSOFT_CHANNELCHECKH
+#include "ChannelSync.h"
+class ChannelCheck : public ChannelSync
+{
+public:
+ void NewChannel(const Channel &newChannel)
+ {
+ channel=newChannel;
+ }
+ Channel channel;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ChannelRefresher.cpp b/Src/Plugins/Library/ml_wire/ChannelRefresher.cpp
new file mode 100644
index 00000000..07539964
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ChannelRefresher.cpp
@@ -0,0 +1,33 @@
+#include "main.h"
+#include "ChannelRefresher.h"
+#include <algorithm>
+
+#include "./subscriptionView.h"
+
+using namespace Nullsoft::Utility;
+void ChannelRefresher::BeginChannelSync()
+{}
+
+void ChannelRefresher::NewChannel(const Channel &newChannel)
+{
+ AutoLock lock (channels LOCKNAME("ChannelRefresher::NewChannel"));
+ ChannelList::iterator found;
+ for (found=channels.begin();found!=channels.end(); found++)
+ {
+ if (!wcscmp(found->url, newChannel.url))
+ break;
+ }
+ if (found != channels.end())
+ {
+ // todo, redo category indexing as necessary.
+ found->UpdateFrom(newChannel);
+ found->lastUpdate = _time64(0);
+ found->needsRefresh = false;
+ }
+}
+
+void ChannelRefresher::EndChannelSync()
+{
+ HWND wnd = SubscriptionView_FindWindow();
+ SubscriptionView_RefreshChannels(wnd, TRUE);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ChannelRefresher.h b/Src/Plugins/Library/ml_wire/ChannelRefresher.h
new file mode 100644
index 00000000..fef67cfe
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ChannelRefresher.h
@@ -0,0 +1,14 @@
+#ifndef NULLSOFT_CHANNELREFRESHERH
+#define NULLSOFT_CHANNELREFRESHERH
+
+#include "ChannelSync.h"
+class ChannelRefresher: public ChannelSync
+{
+public:
+ void BeginChannelSync();
+ void NewChannel(const Channel &newChannel);
+ void EndChannelSync();
+
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ChannelSync.h b/Src/Plugins/Library/ml_wire/ChannelSync.h
new file mode 100644
index 00000000..c7d5fbd9
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ChannelSync.h
@@ -0,0 +1,31 @@
+#ifndef NULLSOFT_CHANNELSYNCH
+#define NULLSOFT_CHANNELSYNCH
+
+#include "Feeds.h"
+
+/*
+ChannelSync is a virtual base class (aka interface) used by the RSS downloader.
+When you instantiate a downloader, you are required to give it a pointer to this interface.
+
+The downloader will call:
+
+ BeginChannelSync();
+ for (;;) // however many it encounters
+ NewChannel(newChannel); // called once for each channel it downloads
+ EndChannelSync();
+
+If you have a class or data structure that wants updates, you'll have to mix-in
+this interface or implement a data-structure-babysitter class (or however you want to deal with it)
+
+See the "WireManager" for an example of how to use it
+*/
+
+class ChannelSync
+{
+public:
+ virtual void BeginChannelSync() {}
+ virtual void NewChannel(const Channel &newChannel) = 0;
+ virtual void EndChannelSync() {}}
+;
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Cloud.cpp b/Src/Plugins/Library/ml_wire/Cloud.cpp
new file mode 100644
index 00000000..07930d47
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Cloud.cpp
@@ -0,0 +1,235 @@
+#include "main.h"
+#include "api__ml_wire.h"
+#include "Cloud.h"
+#include "FeedParse.h"
+#include "Defaults.h"
+#include "./subscriptionView.h"
+#include "ChannelRefresher.h"
+#include "Util.h"
+#include <algorithm>
+#include <strsafe.h>
+
+/* benski> TODO rewrite Callback() so we don't have to reserve a thread */
+using namespace Nullsoft::Utility;
+
+#define CLOUD_TICK_MS 60000
+ChannelRefresher channelRefresher;
+
+static bool kill = false;
+
+static __time64_t RetrieveMinimalUpdateTime()
+{
+ __time64_t minUpdateTime = 0;
+
+ AutoLock lock (channels LOCKNAME("RetrieveMinimalUpdateTime"));
+ ChannelList::iterator itr;
+ for (itr=channels.begin(); itr!=channels.end(); itr++)
+ {
+ if (itr->useDefaultUpdate)
+ {
+ if ( !updateTime ) autoUpdate = 0;
+ if ( autoUpdate && (!minUpdateTime || minUpdateTime && (updateTime < minUpdateTime)) )
+ {
+ minUpdateTime = updateTime;
+ }
+ }
+ else // use the custom values
+ {
+ if ( !itr->updateTime ) itr->autoUpdate = 0;
+ if ( itr->autoUpdate && (!minUpdateTime || minUpdateTime && (itr->updateTime < minUpdateTime)) )
+ {
+ minUpdateTime = itr->updateTime;
+ }
+ }
+ }
+ return minUpdateTime;
+}
+
+int Cloud::CloudThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ Cloud *cloud = (Cloud *)user_data;
+ if (kill)
+ {
+ WASABI_API_THREADPOOL->RemoveHandle(0, cloud->cloudEvent);
+ CloseHandle(cloud->cloudEvent);
+ WASABI_API_THREADPOOL->RemoveHandle(0, cloud->cloudTimerEvent);
+ cloud->cloudTimerEvent.Close();
+ SetEvent(cloud->cloudDone);
+ return 0;
+ }
+
+ cloud->Callback();
+
+ // set waitable timer, overwrite previouse value if any
+ if (!kill)
+ {
+ __time64_t timeToWait = RetrieveMinimalUpdateTime();
+ if ( timeToWait )
+ cloud->cloudTimerEvent.Wait(timeToWait * 1000);
+ }
+
+ return 0;
+}
+
+
+Cloud::Cloud() : cloudThread(0), cloudEvent(0), statusText(0)
+{}
+
+Cloud::~Cloud()
+{
+ free(statusText);
+}
+
+void Cloud::Quit()
+{
+ cloudDone= CreateEvent(NULL, FALSE, FALSE, NULL);
+ kill = true;
+ SetEvent(cloudEvent);
+ WaitForSingleObject(cloudDone, INFINITE);
+ CloseHandle(cloudDone);
+}
+
+void Cloud::RefreshAll()
+{
+ AutoLock lock (channels LOCKNAME("RefreshAll"));
+ ChannelList::iterator itr;
+ for (itr = channels.begin();itr != channels.end();itr++)
+ {
+ itr->needsRefresh = true;
+ }
+
+}
+void Cloud::Init()
+{
+ // setup a periodic callback so we can check on our times
+ cloudEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ cloudThread = WASABI_API_THREADPOOL->ReserveThread(0);
+
+ WASABI_API_THREADPOOL->AddHandle(cloudThread, cloudEvent, CloudThreadPoolFunc, this, 0, 0);
+
+ WASABI_API_THREADPOOL->AddHandle(cloudThread, cloudTimerEvent, CloudThreadPoolFunc, this, 1, 0);
+ __time64_t timeToWait = RetrieveMinimalUpdateTime();
+ if ( timeToWait )
+ cloudTimerEvent.Wait(timeToWait * 1000);
+}
+
+void Cloud::Refresh(Channel &channel)
+{
+ wchar_t lang_buf[1024] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_RECEIVING_UPDATES_FOR, lang_buf, 1024);
+ if (channel.title)
+ StringCbCat(lang_buf, sizeof(lang_buf), channel.title);
+ else
+ lang_buf[0]=0;
+ SetStatus(lang_buf);
+ size_t oldSize = channel.items.size();
+
+ FeedParse downloader(&channelRefresher, false);
+ downloader.DownloadURL(channel.url);
+
+ if (channel.items.size() > oldSize)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_GOT_NEW_ITEMS_FOR, lang_buf, 1024);
+ StringCbCat(lang_buf, sizeof(lang_buf), channel.title);
+ SetStatus(lang_buf);
+ }
+ else
+ SetStatus(L"");
+}
+
+void Cloud::GetStatus(wchar_t *status, size_t len)
+{
+ AutoLock lock (statusGuard);
+ if (statusText)
+ StringCchCopy(status, len, statusText);
+ else
+ status[0]=0;
+}
+
+void Cloud::SetStatus(const wchar_t *newStatus)
+{
+ AutoLock lock (statusGuard);
+ free(statusText);
+ statusText = _wcsdup(newStatus);
+
+ HWND hView = SubscriptionView_FindWindow();
+ if (NULL != hView)
+ SubscriptionView_SetStatus(hView, statusText);
+}
+
+/* --- Private Methods of class Cloud --- */
+
+
+static void ForceLastUpdate(const Channel &channel)
+{
+ AutoLock lock (channels LOCKNAME("ChannelRefresher::NewChannel"));
+ ChannelList::iterator found;
+ for (found=channels.begin();found!=channels.end(); found++)
+ {
+ if (!wcscmp(found->url, channel.url))
+ break;
+ }
+ if (found != channels.end())
+ {
+ found->lastUpdate = _time64(0);
+ found->needsRefresh = false;
+ }
+}
+/*
+@private
+checks all channels and updates any that requiring refreshing.
+*/
+void Cloud::Callback()
+{
+ __time64_t curTime = _time64(0);
+
+ size_t i = 0;
+ Channel temp;
+ bool refreshed = false;
+
+ while (true) // we need to lock the channels object before we check its size, etc, so we can't just use a "for" loop.
+ {
+ { // we want to minimize how long we have to lock, so we'll make a copy of the channel data
+ AutoLock lock (channels LOCKNAME("Callback"));
+ if (i >= channels.size())
+ break;
+ temp = channels[i]; // make a copy the data so we can safely release the lock
+ channels[i].needsRefresh = false; // have to set this now. if the site is down or 404, then the refresh will never "complete".
+ } // end locking scope
+
+ if (temp.needsRefresh) // need an immediate refresh? (usually set when the user clicks refresh or update-on-launch is on)
+ {
+ Refresh(temp);
+ refreshed = true;
+ }
+ else if (temp.useDefaultUpdate) // this flag is set unless the user chose custom update values
+ {
+ if (!updateTime) autoUpdate = 0;
+ if (autoUpdate && (temp.lastUpdate + updateTime) <= curTime)
+ {
+ Refresh(temp);
+ ForceLastUpdate(temp);
+ refreshed = true;
+ }
+ }
+ else // use the custom values
+ {
+ if (temp.updateTime == 0) temp.autoUpdate = 0;
+ if (temp.autoUpdate && (temp.lastUpdate + temp.updateTime) <= curTime)
+ {
+ Refresh(temp);
+ ForceLastUpdate(temp);
+ refreshed = true;
+ }
+ }
+
+ i++;
+ }
+
+ // if we're refreshing then save out
+ if (refreshed)
+ {
+ SaveAll();
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Cloud.h b/Src/Plugins/Library/ml_wire/Cloud.h
new file mode 100644
index 00000000..505d936e
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Cloud.h
@@ -0,0 +1,35 @@
+#ifndef NULLSOFT_CLOUDH
+#define NULLSOFT_CLOUDH
+
+#include <windows.h>
+
+#include "Feeds.h"
+#include "../nu/threadpool/api_threadpool.h"
+#include "../nu/threadpool/timerhandle.hpp"
+#include "../nu/AutoLock.h"
+
+class Cloud
+{
+public:
+ Cloud();
+ ~Cloud();
+ void Init();
+ void Quit();
+ void Refresh( Channel &channel );
+ void GetStatus( wchar_t *status, size_t len );
+ void RefreshAll();
+ void Pulse() { SetEvent( cloudEvent ); }
+
+private:
+ static DWORD WINAPI CloudThread( void *param );
+ void SetStatus( const wchar_t *newStatus );
+ void Callback();
+ ThreadID *cloudThread;
+ wchar_t *statusText;
+ Nullsoft::Utility::LockGuard statusGuard;
+ HANDLE cloudEvent, cloudDone;
+ TimerHandle cloudTimerEvent;
+ static int CloudThreadPoolFunc( HANDLE handle, void *user_data, intptr_t param );
+};
+
+#endif
diff --git a/Src/Plugins/Library/ml_wire/DESIGN.txt b/Src/Plugins/Library/ml_wire/DESIGN.txt
new file mode 100644
index 00000000..47148235
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DESIGN.txt
@@ -0,0 +1,12 @@
+
+
+
+
+major components:
+
+Cloud
+-----
+
+The cloud is the object which is responsible for doing period updates on RSS feeds.
+It automatically scans the feeds. You can manually refresh via the Refresh(string url) method
+
diff --git a/Src/Plugins/Library/ml_wire/Defaults.cpp b/Src/Plugins/Library/ml_wire/Defaults.cpp
new file mode 100644
index 00000000..8c8da669
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Defaults.cpp
@@ -0,0 +1,40 @@
+#include "main.h"
+#include "Defaults.h"
+#include <shlobj.h>
+wchar_t defaultDownloadPath[MAX_PATH] = {0},
+ serviceUrl[1024] = {0};
+__time64_t updateTime = 60 /* 1 minute */ * 60 /* 1 hour */ * 24 /* 1 day */;
+int autoDownloadEpisodes = 1;
+bool autoUpdate = true;
+bool autoDownload = true;
+bool updateOnLaunch = false;
+bool needToMakePodcastsView=true;
+
+static BOOL UtilGetSpecialFolderPath( HWND hwnd, wchar_t *path, int folder )
+{
+ ITEMIDLIST *pidl; // Shell Item ID List ptr
+ IMalloc *imalloc; // Shell IMalloc interface ptr
+ BOOL result; // Return value
+
+ if ( SHGetSpecialFolderLocation( hwnd, folder, &pidl ) != NOERROR )
+ return FALSE;
+
+ result = SHGetPathFromIDList( pidl, path );
+
+ if ( SHGetMalloc( &imalloc ) == NOERROR )
+ {
+ imalloc->Free( pidl );
+ imalloc->Release();
+ }
+
+ return result;
+}
+
+void BuildDefaultDownloadPath( HWND hwnd )
+{
+ wchar_t defaultPath[ MAX_PATH ] = L"";
+ if ( !UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_MYMUSIC ) )
+ UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_PERSONAL );
+
+ lstrcpyn( defaultDownloadPath, defaultPath, MAX_PATH );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Defaults.h b/Src/Plugins/Library/ml_wire/Defaults.h
new file mode 100644
index 00000000..2ffb4757
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Defaults.h
@@ -0,0 +1,34 @@
+#ifndef NULLSOFT_DEFAULTSH
+#define NULLSOFT_DEFAULTSH
+#include <windows.h>
+extern wchar_t defaultDownloadPath[MAX_PATH], serviceUrl[1024];
+extern __time64_t updateTime;
+extern int autoDownloadEpisodes;
+extern bool autoUpdate;
+extern bool autoDownload;
+extern bool updateOnLaunch;
+extern float htmlDividerPercent;
+extern float channelDividerPercent;
+extern int itemTitleWidth;
+extern int itemDateWidth;
+extern int itemMediaWidth;
+extern int itemSizeWidth;
+
+#define DOWNLOADSCHANNELWIDTHDEFAULT 200
+#define DOWNLOADSITEMWIDTHDEFAULT 200
+#define DOWNLOADSPROGRESSWIDTHDEFAULT 100
+#define DOWNLOADSPATHWIDTHDEFAULTS 200
+
+extern int downloadsChannelWidth;
+extern int downloadsItemWidth;
+extern int downloadsProgressWidth;
+extern int downloadsPathWidth;
+
+extern bool needToMakePodcastsView;
+
+extern int currentItemSort;
+extern bool itemSortAscending;
+extern bool channelSortAscending;
+extern int channelLastSelection;
+void BuildDefaultDownloadPath(HWND);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadManager.cpp b/Src/Plugins/Library/ml_wire/DownloadManager.cpp
new file mode 100644
index 00000000..61f734b2
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadManager.cpp
@@ -0,0 +1 @@
+#include "main.h" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadStatus.cpp b/Src/Plugins/Library/ml_wire/DownloadStatus.cpp
new file mode 100644
index 00000000..3ccaf419
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadStatus.cpp
@@ -0,0 +1,153 @@
+#include "main.h"
+#include "api__ml_wire.h"
+#include "DownloadStatus.h"
+#include "DownloadsDialog.h"
+#include "./navigation.h"
+
+#include <strsafe.h>
+
+DownloadStatus downloadStatus;
+
+using namespace Nullsoft::Utility;
+
+DownloadStatus::Status::Status()
+{
+ Init();
+}
+
+DownloadStatus::Status::Status( size_t _downloaded, size_t _maxSize, const wchar_t *_channel, const wchar_t *_item, const wchar_t *_path )
+{
+ Init();
+
+ downloaded = _downloaded;
+ maxSize = _maxSize;
+ channel = _wcsdup( _channel );
+ item = _wcsdup( _item );
+ path = _wcsdup( _path );
+}
+
+const DownloadStatus::Status &DownloadStatus::Status::operator =( const DownloadStatus::Status &copy )
+{
+ Reset();
+ Init();
+
+ downloaded = copy.downloaded;
+ maxSize = copy.maxSize;
+ channel = _wcsdup( copy.channel );
+ item = _wcsdup( copy.item );
+ path = _wcsdup( copy.path );
+ killswitch = copy.killswitch;
+
+ return *this;
+}
+
+DownloadStatus::Status::~Status()
+{
+ Reset();
+}
+
+void DownloadStatus::Status::Init()
+{
+ downloaded = 0;
+ maxSize = 0;
+ killswitch = 0;
+ channel = 0;
+ item = 0;
+ path = 0;
+}
+
+void DownloadStatus::Status::Reset()
+{
+ if ( channel )
+ {
+ free( channel );
+ channel = 0;
+ }
+
+ if ( item )
+ {
+ free( item );
+ item = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+}
+
+void DownloadStatus::AddDownloadThread(DownloadToken token, const wchar_t *channel, const wchar_t *item, const wchar_t *path)
+{
+ {
+ AutoLock lock(statusLock);
+ downloads[token] = Status(0,0,channel,item,path);
+ DownloadsUpdated(downloads[token],token);
+ }
+
+ Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void DownloadStatus::DownloadThreadDone(DownloadToken token)
+{
+ {
+ AutoLock lock(statusLock);
+ downloads.erase(token);
+ }
+
+ Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+bool DownloadStatus::UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize)
+{
+ AutoLock lock(statusLock);
+ downloads[token].downloaded = downloaded;
+ downloads[token].maxSize = maxSize;
+
+ return !!downloads[token].killswitch;
+}
+
+bool DownloadStatus::CurrentlyDownloading()
+{
+ AutoLock lock(statusLock);
+ return !downloads.empty();
+}
+
+void DownloadStatus::GetStatusString( wchar_t *status, size_t len )
+{
+ AutoLock lock( statusLock );
+ Downloads::iterator itr;
+ size_t bytesDownloaded = 0, bytesTotal = 0, numDownloads = 0;
+ bool unknownTotal = false;
+ for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
+ {
+ Status &dlstatus = itr->second;
+ if ( dlstatus.maxSize )
+ {
+ numDownloads++;
+ bytesDownloaded += dlstatus.downloaded;
+ bytesTotal += dlstatus.maxSize;
+ }
+ else // don't have a max size
+ {
+ if ( dlstatus.downloaded ) // if we've downloaded some then we just don't know the total
+ {
+ unknownTotal = true;
+ numDownloads++;
+ bytesDownloaded += dlstatus.downloaded;
+ }
+ }
+ }
+
+ if ( 0 == numDownloads )
+ {
+ status[ 0 ] = L'\0';
+ }
+ else
+ {
+ if ( unknownTotal || bytesTotal == 0 )
+ StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_KB_COMPLETE ), numDownloads, bytesDownloaded / 1024);
+ else
+ StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_KB_PROGRESS ), numDownloads, bytesDownloaded / 1024, bytesTotal / 1024, MulDiv( (int)bytesDownloaded, 100, (int)bytesTotal ) );
+ }
+}
diff --git a/Src/Plugins/Library/ml_wire/DownloadStatus.h b/Src/Plugins/Library/ml_wire/DownloadStatus.h
new file mode 100644
index 00000000..be3dd896
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadStatus.h
@@ -0,0 +1,41 @@
+#ifndef NULLSOFT_DOWNLOADSTATUSH
+#define NULLSOFT_DOWNLOADSTATUSH
+
+#include "../nu/AutoLock.h"
+#include <map>
+
+
+class DownloadStatus
+{
+public:
+ class Status
+ {
+ public:
+ Status();
+ ~Status();
+ const Status &operator =(const Status &copy);
+ Status(size_t _downloaded, size_t _maxSize, const wchar_t *channel, const wchar_t *item, const wchar_t *path);
+ size_t downloaded, maxSize;
+
+ int killswitch;
+ wchar_t *channel;
+ wchar_t *item;
+ wchar_t *path;
+ private:
+
+ void Init();
+ void Reset();
+ };
+
+ void AddDownloadThread(DownloadToken token, const wchar_t *channel, const wchar_t *item, const wchar_t *path);
+ void DownloadThreadDone(DownloadToken token);
+ bool UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize);
+ bool CurrentlyDownloading();
+ void GetStatusString(wchar_t *status, size_t len);
+ typedef std::map<DownloadToken, Status> Downloads;
+ Downloads downloads;
+ Nullsoft::Utility::LockGuard statusLock;
+};
+
+extern DownloadStatus downloadStatus;
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadThread.cpp b/Src/Plugins/Library/ml_wire/DownloadThread.cpp
new file mode 100644
index 00000000..67e1aed7
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadThread.cpp
@@ -0,0 +1,222 @@
+#include "Main.h"
+
+#pragma warning(disable:4786)
+
+#include "DownloadThread.h"
+#include "api__ml_wire.h"
+#include "api/service/waServiceFactory.h"
+#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
+#include "errors.h"
+#include <strsafe.h>
+
+extern int winampVersion;
+
+#define USER_AGENT_SIZE (10 /*User-Agent*/ + 2 /*: */ + 6 /*Winamp*/ + 1 /*/*/ + 1 /*5*/ + 3/*.21*/ + 1 /*Null*/)
+void SetUserAgent( api_httpreceiver *http )
+{
+ char user_agent[ USER_AGENT_SIZE ] = { 0 };
+ int bigVer = ( ( winampVersion & 0x0000FF00 ) >> 12 );
+ int smallVer = ( ( winampVersion & 0x000000FF ) );
+ StringCchPrintfA( user_agent, USER_AGENT_SIZE, "User-Agent: Winamp/%01x.%02x", bigVer, smallVer );
+ http->addheader( user_agent );
+}
+
+#define HTTP_BUFFER_SIZE 32768
+
+static int FeedXMLHTTP( api_httpreceiver *http, obj_xml *parser, bool *noData )
+{
+ char downloadedData[ HTTP_BUFFER_SIZE ] = { 0 };
+ int xmlResult = API_XML_SUCCESS;
+ int downloadSize = http->get_bytes( downloadedData, HTTP_BUFFER_SIZE );
+ if ( downloadSize )
+ {
+ xmlResult = parser->xmlreader_feed( (void *)downloadedData, downloadSize );
+ *noData = false;
+ }
+ else
+ *noData = true;
+
+ return xmlResult;
+}
+
+
+DownloadThread::DownloadThread() : parser( 0 ), parserFactory( 0 )
+{
+ parserFactory = plugin.service->service_getServiceByGuid( obj_xmlGUID );
+ if ( parserFactory )
+ parser = (obj_xml *)parserFactory->getInterface();
+
+ if ( parser )
+ {
+ parser->xmlreader_setCaseSensitive();
+ parser->xmlreader_registerCallback( L"*", &xmlDOM );
+ parser->xmlreader_open();
+ }
+}
+
+DownloadThread::~DownloadThread()
+{
+ if ( parser )
+ {
+ parser->xmlreader_unregisterCallback( &xmlDOM );
+ parser->xmlreader_close();
+ }
+
+ if ( parserFactory && parser )
+ parserFactory->releaseInterface( parser );
+
+ parserFactory = 0;
+ parser = 0;
+}
+
+void URLToFileName( wchar_t *url )
+{
+ while ( url && *url != 0 )
+ {
+ switch ( *url )
+ {
+ case ':':
+ case '/':
+ case '\\':
+ case '*':
+ case '?':
+ case '"':
+ case '<':
+ case '>':
+ case '|':
+ *url = '_';
+ }
+ url++;
+ }
+}
+
+#define FILE_BUFFER_SIZE 32768
+void DownloadThread::DownloadFile( const wchar_t *fileName )
+{
+ if ( !parser )
+ return; // no sense in continuing if there's no parser available
+
+ HANDLE file = CreateFile( fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
+ if ( file == INVALID_HANDLE_VALUE )
+ return;
+
+ while ( true )
+ {
+ char data[ FILE_BUFFER_SIZE ] = { 0 };
+ DWORD bytesRead = 0;
+ if ( ReadFile( file, data, FILE_BUFFER_SIZE, &bytesRead, NULL ) && bytesRead )
+ {
+ parser->xmlreader_feed( (void *)data, bytesRead );
+ }
+ else
+ break;
+ }
+
+ CloseHandle( file );
+ parser->xmlreader_feed( 0, 0 );
+ ReadNodes( fileName );
+}
+
+#ifdef _DEBUG
+#include <iostream>
+void ShowAllHeaders( api_httpreceiver *http )
+{
+ std::cout << "--------------------" << std::endl;
+ const char *blah = http->getallheaders();
+ while ( blah && *blah )
+ {
+ std::cout << blah << std::endl;
+ blah += strlen( blah ) + 1;
+ }
+}
+#else
+#define ShowAllHeaders(x)
+#endif
+
+int RunXMLDownload( api_httpreceiver *http, obj_xml *parser )
+{
+ int ret;
+ bool noData;
+ do
+ {
+ Sleep( 50 );
+ ret = http->run();
+ if ( FeedXMLHTTP( http, parser, &noData ) != API_XML_SUCCESS )
+ return DOWNLOAD_ERROR_PARSING_XML;
+ } while ( ret == HTTPRECEIVER_RUN_OK );
+
+ // finish off the data
+ do
+ {
+ if ( FeedXMLHTTP( http, parser, &noData ) != API_XML_SUCCESS )
+ return DOWNLOAD_ERROR_PARSING_XML;
+ } while ( !noData );
+
+ parser->xmlreader_feed( 0, 0 );
+ if ( ret != HTTPRECEIVER_RUN_ERROR )
+ return DOWNLOAD_SUCCESS;
+ else
+ return DOWNLOAD_CONNECTIONRESET;
+}
+
+
+int DownloadThread::DownloadURL( const wchar_t *url )
+{
+ if ( !parser )
+ return DOWNLOAD_NOPARSER; // no sense in continuing if there's no parser available
+
+ api_httpreceiver *http = 0;
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( httpreceiverGUID );
+ if ( sf )
+ http = (api_httpreceiver *)sf->getInterface();
+
+ if ( !http )
+ return DOWNLOAD_NOHTTP;
+
+ http->AllowCompression();
+ http->open( API_DNS_AUTODNS, HTTP_BUFFER_SIZE, mediaLibrary.GetProxy() );
+
+ SetUserAgent( http );
+
+ http->connect( AutoChar( url ) );
+ int ret;
+
+ do
+ {
+ Sleep( 50 );
+ ret = http->run();
+ if ( ret == -1 ) // connection failed
+ break;
+
+ // ---- check our reply code ----
+ int status = http->get_status();
+ switch ( status )
+ {
+ case HTTPRECEIVER_STATUS_CONNECTING:
+ case HTTPRECEIVER_STATUS_READING_HEADERS:
+ break;
+
+ case HTTPRECEIVER_STATUS_READING_CONTENT:
+ {
+ ShowAllHeaders( http ); // benski> don't cut, only enabled in debug mode
+ int downloadError;
+ downloadError = RunXMLDownload( http, parser );
+ if ( downloadError == DOWNLOAD_SUCCESS )
+ ReadNodes( url );
+ sf->releaseInterface( http );
+ return downloadError;
+ }
+ break;
+ case HTTPRECEIVER_STATUS_ERROR:
+ default:
+ sf->releaseInterface( http );
+ return DOWNLOAD_404;
+ }
+ } while ( ret == HTTPRECEIVER_RUN_OK );
+
+ const char *er = http->geterrorstr();
+
+ sf->releaseInterface( http );
+
+ return DOWNLOAD_404;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadThread.h b/Src/Plugins/Library/ml_wire/DownloadThread.h
new file mode 100644
index 00000000..7e2061c9
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadThread.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_DOWNLOADTHREADH
+#define NULLSOFT_DOWNLOADTHREADH
+
+#include "../xml/obj_xml.h"
+#include "../xml/XMLDOM.h"
+#include "../nu/Alias.h"
+#include "api__ml_wire.h"
+#include <api/service/waServiceFactory.h>
+
+
+class DownloadThread
+{
+public:
+ DownloadThread();
+ virtual ~DownloadThread();
+
+ virtual void ReadNodes(const wchar_t *url) = 0;
+
+ int DownloadURL(const wchar_t *url);
+ void DownloadFile(const wchar_t *fileName);
+protected:
+ XMLDOM xmlDOM;
+private:
+ obj_xml *parser;
+ waServiceFactory *parserFactory;
+
+};
+#endif
diff --git a/Src/Plugins/Library/ml_wire/Downloaded.cpp b/Src/Plugins/Library/ml_wire/Downloaded.cpp
new file mode 100644
index 00000000..e088a376
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Downloaded.cpp
@@ -0,0 +1,121 @@
+#include "main.h"
+#include "Downloaded.h"
+
+DownloadList downloadedFiles;
+using namespace Nullsoft::Utility;
+Nullsoft::Utility::LockGuard downloadedLock;
+
+DownloadedFile::DownloadedFile()
+{
+ Init();
+}
+
+DownloadedFile::DownloadedFile(const wchar_t *_url, const wchar_t *_path, const wchar_t *_channel, const wchar_t *_item, __time64_t publishDate)
+{
+ Init();
+
+ this->publishDate = publishDate;
+
+ SetChannel( _channel );
+ SetItem( _item );
+ SetPath( _path );
+ SetURL( _url );
+}
+
+DownloadedFile::DownloadedFile( const DownloadedFile &copy )
+{
+ Init();
+
+ operator =( copy );
+}
+
+DownloadedFile::~DownloadedFile()
+{
+ Reset();
+}
+
+
+void DownloadedFile::Init()
+{
+ url = 0;
+ path = 0;
+ channel = 0;
+ item = 0;
+ bytesDownloaded = 0;
+ totalSize = 0;
+ publishDate = 0;
+}
+
+void DownloadedFile::Reset()
+{
+ if ( url )
+ {
+ free( url );
+ url = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+
+ if ( channel )
+ {
+ free( channel );
+ channel = 0;
+ }
+
+ if ( item )
+ {
+ free( item );
+ item = 0;
+ }
+}
+
+void DownloadedFile::SetPath( const wchar_t *_path )
+{
+ if ( path )
+ free( path );
+
+ path = _wcsdup( _path );
+}
+
+void DownloadedFile::SetURL( const wchar_t *_url )
+{
+ if ( url )
+ free( url );
+
+ url = _wcsdup( _url );
+}
+
+void DownloadedFile::SetItem( const wchar_t *_item )
+{
+ free( item );
+ item = _wcsdup( _item );
+}
+
+void DownloadedFile::SetChannel( const wchar_t *_channel )
+{
+ free( channel );
+ channel = _wcsdup( _channel );
+}
+
+const DownloadedFile &DownloadedFile::operator =( const DownloadedFile &copy )
+{
+ Reset();
+ Init();
+
+ SetChannel( copy.channel );
+ SetItem( copy.item );
+
+ bytesDownloaded = copy.bytesDownloaded;
+ totalSize = copy.totalSize;
+ publishDate = copy.publishDate;
+ downloadDate = copy.downloadDate;
+
+ SetPath( copy.path );
+ SetURL( copy.url );
+
+ return *this;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Downloaded.h b/Src/Plugins/Library/ml_wire/Downloaded.h
new file mode 100644
index 00000000..2ba1ace8
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Downloaded.h
@@ -0,0 +1,79 @@
+#ifndef NULLSOFT_DOWNLOADEDH
+#define NULLSOFT_DOWNLOADEDH
+
+#include "../nu/AutoLock.h"
+#include "../nu/AutoCharFn.h"
+#include "..\..\General\gen_ml/ml.h"
+#include <vector>
+#include "../nu/MediaLibraryInterface.h"
+
+class DownloadedFile
+{
+public:
+ DownloadedFile();
+ DownloadedFile( const wchar_t *_url, const wchar_t *_path, const wchar_t *_channel, const wchar_t *_item, __time64_t publishDate );
+ DownloadedFile( const DownloadedFile &copy );
+ ~DownloadedFile();
+
+ const DownloadedFile &operator =( const DownloadedFile &copy );
+
+ void SetPath( const wchar_t *_path );
+ void SetURL( const wchar_t *_url );
+ void SetItem( const wchar_t *_item );
+ void SetChannel( const wchar_t *_channel );
+
+ size_t bytesDownloaded = 0;
+ size_t totalSize = 0;
+
+ __time64_t publishDate;
+ __time64_t downloadDate = 0;
+
+ wchar_t *path = 0;
+ wchar_t *url = 0;
+ wchar_t *channel = 0;
+ wchar_t *item = 0;
+
+
+private:
+ void Init();
+ void Reset();
+};
+
+class DownloadList
+{
+public:
+ typedef std::vector<DownloadedFile> DownloadedFileList;
+ typedef DownloadedFileList::iterator iterator;
+ typedef DownloadedFileList::const_iterator const_iterator;
+
+ operator Nullsoft::Utility::LockGuard &() { return downloadedLock; }
+
+ void Remove( size_t index ) { downloadList.erase( downloadList.begin() + index ); }
+
+ bool RemoveAndDelete( int index )
+ {
+ SendMessage( mediaLibrary.library, WM_ML_IPC, (WPARAM)downloadList[ index ].path, ML_IPC_DB_REMOVEITEMW );
+
+ if ( !DeleteFile( downloadList[ index ].path ) && GetLastError() != ERROR_FILE_NOT_FOUND )
+ return false;
+
+ downloadList.erase( downloadList.begin() + index );
+
+ return true;
+ }
+
+ DownloadedFileList downloadList;
+ Nullsoft::Utility::LockGuard downloadedLock;
+
+ iterator begin() { return downloadList.begin(); }
+ iterator end() { return downloadList.end(); }
+};
+
+extern DownloadList downloadedFiles;
+extern int downloadsItemSort;
+extern bool downloadsSortAscending;
+
+void CleanupDownloads();
+__time64_t filetime(const wchar_t *file);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadsDialog.cpp b/Src/Plugins/Library/ml_wire/DownloadsDialog.cpp
new file mode 100644
index 00000000..83fe8ffb
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadsDialog.cpp
@@ -0,0 +1,1453 @@
+#include "main.h"
+#include "api__ml_wire.h"
+#include "Downloaded.h"
+#include "./navigation.h"
+#include "DownloadStatus.h"
+#include "Defaults.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <vector>
+#include "../nu/menushortcuts.h"
+#include <commctrl.h>
+#include <shlwapi.h>
+#include <shellapi.h>
+#include <strsafe.h>
+#include <algorithm>
+
+HWND current_window = 0;
+int groupBtn = 1, enqueuedef = 0, customAllowed = 0;
+HMENU g_context_menus2 = NULL;
+viewButtons view = {0};
+
+#ifndef HDF_SORTUP
+#define HDF_SORTUP 0x0400
+#define HDF_SORTDOWN 0x0200
+#endif // !HDF_SORTUP
+
+using namespace Nullsoft::Utility;
+
+enum
+{
+ COL_CHANNEL = 0,
+ COL_ITEM,
+ COL_PROGRESS,
+ COL_PATH,
+ NUM_COLUMNS,
+};
+
+int downloadsChannelWidth = DOWNLOADSCHANNELWIDTHDEFAULT;
+int downloadsItemWidth = DOWNLOADSITEMWIDTHDEFAULT;
+int downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
+int downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
+
+
+W_ListView downloadList;
+int downloadsItemSort = -1; // -1 means no sort active
+bool downloadsSortAscending = true;
+
+enum
+{
+ DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR = 0,
+};
+
+class DownloadListItem
+{
+public:
+ DownloadedFile *f;
+ DownloadToken token;
+ wchar_t *channel, *item, *path;
+ wchar_t status[ 20 ];
+
+ DownloadListItem( DownloadedFile *fi ) : token( 0 ), channel( 0 ), item( 0 ), path( 0 )
+ {
+ f = new DownloadedFile( *fi );
+ ZeroMemory( status, sizeof( status ) );
+ }
+
+ DownloadListItem( DownloadToken token, const wchar_t *channel0, const wchar_t *item0, const wchar_t *path0, size_t downloaded, size_t maxSize ) : token( token ), f( 0 )
+ {
+ if ( maxSize )
+ StringCchPrintf( status, 20, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PERCENT ), (int)( downloaded / ( maxSize / 100 ) ) );
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOADING, status, 20 );
+ }
+
+ channel = channel0 ? _wcsdup( channel0 ) : NULL;
+ item = item0 ? _wcsdup( item0 ) : NULL;
+ path = path0 ? _wcsdup( path0 ) : NULL;
+ }
+
+ ~DownloadListItem()
+ {
+ clean();
+
+ if ( f )
+ delete f;
+ }
+
+ void clean()
+ {
+ if ( channel )
+ {
+ free( channel );
+ channel = 0;
+ }
+
+ if ( item )
+ {
+ free( item );
+ item = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+ }
+};
+
+static std::vector<DownloadListItem*> listContents;
+
+bool GetDownload( int &download )
+{
+ download = ListView_GetNextItem( downloadList.getwnd(), download, LVNI_ALL | LVNI_SELECTED );
+ if ( download == -1 )
+ return false;
+ else
+ return true;
+}
+
+void Downloads_Play( bool enqueue = false )
+{
+ int download = -1;
+ AutoLock lock( downloadedFiles );
+ while ( GetDownload( download ) )
+ {
+ if ( !enqueue )
+ {
+ if ( listContents[ download ]->f )
+ mediaLibrary.PlayFile( listContents[ download ]->f->path );
+ else if ( listContents[ download ]->path )
+ mediaLibrary.PlayFile( listContents[ download ]->path );
+
+ enqueue = true;
+ }
+ else
+ {
+ if ( listContents[ download ]->f )
+ mediaLibrary.EnqueueFile( listContents[ download ]->f->path );
+ else if ( listContents[ download ]->path )
+ mediaLibrary.EnqueueFile( listContents[ download ]->path );
+ }
+ }
+}
+
+void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token )
+{
+ listContents.push_back( new DownloadListItem( token, s.channel, s.item, s.path, s.downloaded, s.maxSize ) );
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+}
+
+void DownloadsUpdated( DownloadToken token, const DownloadedFile *f )
+{
+ for ( DownloadListItem *l_content : listContents )
+ {
+ if ( l_content->token == token )
+ {
+ l_content->token = 0;
+ if ( f )
+ {
+ l_content->f = new DownloadedFile( *f );
+
+ l_content->clean();
+ }
+ else
+ lstrcpyn( l_content->status, L"Error", 20 );
+
+ break;
+ }
+ }
+
+ PostMessage( downloadList.getwnd(), LVM_REDRAWITEMS, 0, listContents.size() );
+}
+
+void DownloadsUpdated()
+{
+ for ( DownloadListItem *l_content : listContents )
+ delete l_content;
+
+ listContents.clear();
+
+ for ( DownloadedFile &l_download : downloadedFiles.downloadList )
+ listContents.push_back( new DownloadListItem( &l_download ) );
+
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ for ( DownloadStatus::Downloads::iterator itr = downloadStatus.downloads.begin(); itr != downloadStatus.downloads.end(); itr++ )
+ {
+ listContents.push_back( new DownloadListItem( itr->first, itr->second.channel, itr->second.item, itr->second.path, itr->second.downloaded, itr->second.maxSize ) );
+ }
+ }
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+// Navigation_ShowService( SERVICE_DOWNLOADS, SHOWMODE_AUTO );
+}
+
+static void CleanupDownloads()
+{
+ {
+ AutoLock lock( downloadedFiles );
+ DownloadList::DownloadedFileList &downloads = downloadedFiles.downloadList;
+ DownloadList::iterator itr, next;
+ for ( itr = downloads.begin(); itr != downloads.end();)
+ {
+ next = itr;
+ ++next;
+ if ( !PathFileExists( itr->path ) )
+ downloads.erase( itr );
+ else
+ itr = next;
+ }
+ }
+
+// Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void Downloads_UpdateStatusBar(HWND hwndDlg)
+{
+ wchar_t status[256]=L"";
+ downloadStatus.GetStatusString(status, 256);
+ SetWindowText(GetDlgItem(hwndDlg, IDC_STATUS), status);
+}
+
+void Downloads_Paint(HWND hwndDlg)
+{
+ int tab[] = { IDC_DOWNLOADLIST | DCW_SUNKENBORDER, };
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_REMOVE, IDC_CLEANUP, IDC_STATUS,
+ GROUP_MAIN, IDC_DOWNLOADLIST
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+
+ if (rc.right == rc.left || rc.bottom == rc.top)
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect( &rg, rc.left, rc.top, rc.right, rc.top );
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDC_PLAY, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(GetDlgItem(hwnd, IDC_PLAY), buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ break;
+ }
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+
+ if ( !pl->hwnd )
+ continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ case IDC_REMOVE:
+ case IDC_CLEANUP:
+ if ( IDC_CUSTOM != pl->id || customAllowed )
+ {
+ if ( groupBtn && pl->id == IDC_PLAY && enqueuedef == 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && pl->id == IDC_ENQUEUE && enqueuedef != 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && ( pl->id == IDC_PLAY || pl->id == IDC_ENQUEUE ) && customAllowed )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowTextW( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
+ LONG width = LOWORD( idealSize ) + WASABI_API_APP->getScaleX( 6 );
+
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+
+ pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ if ( SWP_SHOWWINDOW & pl->flags )
+ rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_STATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > 16) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_DOWNLOADLIST:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ), rg.right - rg.left + WASABI_API_APP->getScaleY( 1 ), ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+
+ pl++;
+ }
+ else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
+ {
+ ValidateRect( hwnd, &ri );
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+
+ if (hdwp)
+ EndDeferWindowPos(hdwp);
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for(pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn(hwnd, NULL);
+}
+
+void Downloads_DisplayChange(HWND hwndDlg)
+{
+ ListView_SetTextColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ downloadList.SetFont(dialogSkinner.GetFont());
+ LayoutWindows(hwndDlg, TRUE);
+}
+
+static void DownloadsDialog_SkinControls(HWND hwnd, const INT *itemList, INT itemCount, UINT skinType, UINT skinStyle)
+{
+ MLSKINWINDOW skinWindow = {0};
+ skinWindow.style = skinStyle;
+ skinWindow.skinType = skinType;
+
+ for(INT i = 0; i < itemCount; i++)
+ {
+ skinWindow.hwndToSkin = GetDlgItem(hwnd, itemList[i]);
+ if (NULL != skinWindow.hwndToSkin)
+ {
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+ }
+ }
+}
+
+static void DownloadDialog_InitializeList(HWND hwnd)
+{
+ HWND hControl = GetDlgItem(hwnd, IDC_DOWNLOADLIST);
+ if (NULL == hControl) return;
+
+ UINT styleEx = (UINT)GetWindowLongPtr(hControl, GWL_EXSTYLE);
+ SetWindowLongPtr(hControl, GWL_EXSTYLE, styleEx & ~WS_EX_NOPARENTNOTIFY);
+
+ styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP;
+ SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+ SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
+
+ MLSKINWINDOW skinWindow;
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.hwndToSkin = hControl;
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+}
+
+bool COL_CHANNEL_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ (item1->f?item1->f->channel:item1->channel), -1,
+ (item2->f?item2->f->channel:item2->channel), -1));
+}
+
+bool COL_ITEM_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ (item1->f?item1->f->item:item1->item), -1,
+ (item2->f?item2->f->item:item2->item), -1));
+}
+
+bool COL_PROGRESS_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ (item1->f?WASABI_API_LNGSTRINGW(IDS_DONE):item1->status), -1,
+ (item2->f?WASABI_API_LNGSTRINGW(IDS_DONE):item2->status), -1));
+}
+
+bool COL_PATH_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ (item1->f?item1->f->path:item1->path), -1,
+ (item2->f?item2->f->path:item2->path), -1));
+}
+
+static BOOL Downloads_SortItems(int sortColumn)
+{
+ AutoLock lock (downloadedFiles);
+ switch (sortColumn)
+ {
+ case COL_CHANNEL:
+ std::sort(listContents.begin(), listContents.end(), COL_CHANNEL_Sort);
+ return TRUE;
+ case COL_ITEM:
+ std::sort(listContents.begin(), listContents.end(), COL_ITEM_Sort);
+ return TRUE;
+ case COL_PROGRESS:
+ std::sort(listContents.begin(), listContents.end(), COL_PROGRESS_Sort);
+ return TRUE;
+ case COL_PATH:
+ std::sort(listContents.begin(), listContents.end(), COL_PATH_Sort);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void Downloads_SetListSortColumn(HWND hwnd, INT listId, INT index, BOOL fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL == hItems) return;
+
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL == hHeader) return;
+
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+ // reset first (ml req)
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (index != i && FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item))
+ {
+ if (0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
+ SNDMSG(hHeader, HDM_SETITEM, i, (LPARAM)&item);
+ }
+ }
+ }
+
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, index, (LPARAM)&item))
+ {
+ INT fmt = item.fmt & ~(HDF_SORTUP | HDF_SORTDOWN);
+ fmt |= (FALSE == fAscending) ? HDF_SORTDOWN : HDF_SORTUP;
+ if (fmt != item.fmt)
+ {
+ item.fmt = fmt;
+ SNDMSG(hHeader, HDM_SETITEM, index, (LPARAM)&item);
+ }
+ }
+}
+
+static BOOL Downloads_Sort(HWND hwnd, INT iColumn, bool fAscending)
+{
+ BOOL result = TRUE;
+ downloadsSortAscending = fAscending;
+ Downloads_SortItems(iColumn);
+ Downloads_SetListSortColumn(hwnd, IDC_DOWNLOADLIST, iColumn, fAscending);
+
+ if (FALSE != result)
+ {
+ HWND hItems = GetDlgItem(hwnd, IDC_DOWNLOADLIST);
+ if (NULL != hItems)
+ InvalidateRect(hItems, NULL, TRUE);
+ }
+
+ return TRUE;
+}
+
+void Downloads_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+static void Downloads_ManageButtons( HWND hwndDlg )
+{
+ int has_selection = downloadList.GetSelectedCount();
+
+ const int buttonids[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_REMOVE };
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ HWND controlHWND = GetDlgItem( hwndDlg, buttonids[ i ] );
+ EnableWindow( controlHWND, has_selection );
+ }
+}
+
+void Downloads_Init(HWND hwndDlg)
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+ current_window = hwndDlg;
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_DOWNLOAD_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ g_context_menus2 = WASABI_API_LOADMENU(IDR_MENU1);
+ groupBtn = ML_GROUPBTN_VAL();
+ enqueuedef = (ML_ENQDEF_VAL() == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_CUSTOM, IDC_ENQUEUE), (INT_PTR)L"ml_downloads"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_CUSTOM, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ MLSkinWindow2(hLibrary, hwndDlg, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ const INT szControls[] = {IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM};
+ DownloadsDialog_SkinControls(hwndDlg, szControls, ARRAYSIZE(szControls), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0));
+
+ const INT szControlz[] = {IDC_REMOVE, IDC_CLEANUP, IDC_STATUS};
+ DownloadsDialog_SkinControls(hwndDlg, szControlz, ARRAYSIZE(szControlz), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ DownloadDialog_InitializeList(hwndDlg);
+ Downloads_UpdateStatusBar(hwndDlg);
+
+ downloadList.setwnd(GetDlgItem(hwndDlg, IDC_DOWNLOADLIST));
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_CHANNEL), downloadsChannelWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_ITEM), downloadsItemWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_PROGRESS), downloadsProgressWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_PATH), downloadsPathWidth);
+
+ DownloadsUpdated();
+
+ downloadList.SetVirtualCount((int)listContents.size());
+ Downloads_UpdateButtonText(hwndDlg, enqueuedef == 1);
+ Downloads_ManageButtons(hwndDlg);
+ Downloads_DisplayChange(hwndDlg);
+ Downloads_Sort(hwndDlg, downloadsItemSort, downloadsSortAscending);
+ SetTimer(hwndDlg, DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR , 1000, 0);
+}
+
+void Downloads_Timer( HWND hwndDlg, UINT timerId )
+{
+ switch ( timerId )
+ {
+ case DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR:
+ Downloads_UpdateStatusBar( hwndDlg );
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ for ( DownloadListItem *l_content : listContents )
+ {
+ if ( l_content->token )
+ {
+ size_t d = downloadStatus.downloads[ l_content->token ].downloaded;
+ size_t s = downloadStatus.downloads[ l_content->token ].maxSize;
+
+ if ( s )
+ StringCchPrintf( l_content->status, 20, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PERCENT ), (int)( d / ( s / 100 ) ) );
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOADING, l_content->status, 20 );
+ }
+
+ PostMessage( downloadList.getwnd(), LVM_REDRAWITEMS, 0, listContents.size() );
+ }
+ }
+ }
+ break;
+ }
+}
+
+static INT Downloads_GetListSortColumn(HWND hwnd, INT listId, bool *fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL != hItems)
+ {
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL != hHeader)
+ {
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item) &&
+ 0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ if (NULL != fAscending)
+ {
+ *fAscending = (0 != (HDF_SORTUP & item.fmt));
+ }
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+void Downloads_Destroy( HWND hwndDlg )
+{
+ downloadsChannelWidth = downloadList.GetColumnWidth( COL_CHANNEL );
+ downloadsItemWidth = downloadList.GetColumnWidth( COL_ITEM );
+ downloadsProgressWidth = downloadList.GetColumnWidth( COL_PROGRESS );
+ downloadsPathWidth = downloadList.GetColumnWidth( COL_PATH );
+
+ for ( DownloadListItem *l_content : listContents )
+ delete l_content;
+
+ listContents.clear();
+
+ downloadList.setwnd( NULL );
+
+ bool fAscending;
+ downloadsItemSort = Downloads_GetListSortColumn( hwndDlg, IDC_DOWNLOADLIST, &fAscending );
+ downloadsSortAscending = ( -1 != downloadsItemSort ) ? ( FALSE != fAscending ) : true;
+}
+
+void Downloads_Remove( bool del = false, HWND parent = NULL )
+{
+ int d = -1;
+ int r = 0;
+ while ( GetDownload( d ) )
+ {
+ int download = d - r;
+ DownloadListItem *item = listContents[ download ];
+ if ( item->f )
+ {
+ AutoLock lock( downloadedFiles );
+ int j = 0;
+ for ( DownloadList::iterator i = downloadedFiles.begin(); i != downloadedFiles.end(); i++ )
+ {
+ if ( !_wcsicmp( i->path, item->f->path ) )
+ {
+ if ( del )
+ {
+ if ( !downloadedFiles.RemoveAndDelete( j ) )
+ MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_DELETEFAILED ), downloadedFiles.downloadList[ j ].path, 0 );
+ }
+ else
+ downloadedFiles.Remove( j );
+
+ delete item;
+ listContents.erase(listContents.begin() + download);
+ r++;
+ break;
+ }
+
+ ++j;
+ }
+ }
+ else if ( item->token )
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ downloadStatus.downloads[ item->token ].killswitch = 1;
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ }
+ else
+ {
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ }
+ }
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+ Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void Downloads_Delete( HWND parent )
+{
+ wchar_t message[ 256 ] = { 0 };
+ int c = downloadList.GetSelectedCount();
+
+ if ( !c )
+ return;
+ else if ( c == 1 )
+ WASABI_API_LNGSTRINGW_BUF( IDS_PERM_DELETE_ARE_YOU_SURE, message, 256 );
+ else
+ StringCchPrintf( message, 256, WASABI_API_LNGSTRINGW( IDS_PERM_DELETE_THESE_ARE_YOU_SURE ), c );
+
+ if ( MessageBox( NULL, message, WASABI_API_LNGSTRINGW( IDS_DELETION ), MB_ICONWARNING | MB_YESNO ) == IDNO )
+ return;
+
+ Downloads_Remove( true, parent );
+}
+
+void Downloads_CleanUp(HWND hwndDlg)
+{
+ wchar_t titleStr[64] = {0};
+ if ( MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_CLEAR_ALL_FINISHED_DOWNLOADS ), WASABI_API_LNGSTRINGW_BUF( IDS_CLEAN_UP_LIST, titleStr, 64 ), MB_ICONWARNING | MB_YESNO ) == IDNO )
+ return;
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.clear();
+ }
+ DownloadsUpdated();
+}
+
+void Downloads_InfoBox( HWND parent )
+{
+ int download = -1;
+ if ( GetDownload( download ) )
+ {
+ const wchar_t *fn;
+ if ( listContents[ download ]->f )
+ fn = listContents[ download ]->f->path;
+ else
+ fn = listContents[ download ]->path;
+
+ if ( fn )
+ {
+ infoBoxParamW p = { parent, fn };
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW );
+ }
+ }
+}
+
+void Downloads_SelectAll()
+{
+ int l = downloadList.GetCount();
+ for ( int i = 0; i < l; i++ )
+ downloadList.SetSelected( i );
+}
+
+static void exploreItemFolder( HWND hwndDlg )
+{
+ if ( downloadList.GetSelectionMark() >= 0 )
+ {
+ int download = -1;
+ while ( GetDownload( download ) )
+ {
+ wchar_t *file;
+ if ( listContents[ download ]->f )
+ file = listContents[ download ]->f->path;
+ else
+ file = listContents[ download ]->path;
+
+ WASABI_API_EXPLORERFINDFILE->AddFile( file );
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+int we_are_drag_and_dropping = 0;
+
+static void Downloads_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
+{
+ bool fAscending;
+ INT iSort = Downloads_GetListSortColumn(hwnd, IDC_DOWNLOADLIST, &fAscending);
+ fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : true;
+ Downloads_Sort(hwnd, plv->iSubItem, fAscending);
+}
+
+LRESULT DownloadList_Notify( LPNMHDR l, HWND hwndDlg )
+{
+ switch ( l->code )
+ {
+ case LVN_COLUMNCLICK:
+ Downloads_OnColumnClick( hwndDlg, (NMLISTVIEW *)l );
+ break;
+ case NM_DBLCLK:
+ Downloads_Play( ( ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ) ^ ML_ENQDEF_VAL() ) );
+ break;
+ case LVN_BEGINDRAG:
+ we_are_drag_and_dropping = 1;
+ SetCapture( hwndDlg );
+ break;
+ case LVN_ITEMCHANGED:
+ Downloads_ManageButtons( hwndDlg );
+ break;
+ case LVN_GETDISPINFO:
+ NMLVDISPINFO *lpdi = (NMLVDISPINFO *)l;
+ size_t item = lpdi->item.iItem;
+
+ if ( item < 0 || item >= listContents.size() )
+ return 0;
+
+ if ( FALSE == downloadsSortAscending )
+ item = listContents.size() - item - 1;
+
+ DownloadListItem *l = listContents[ item ];
+
+ if ( lpdi->item.mask & LVIF_TEXT )
+ {
+ lpdi->item.pszText[ 0 ] = 0;
+ switch ( lpdi->item.iSubItem )
+ {
+ case COL_CHANNEL:
+ if ( !l->token && l->f )
+ lstrcpyn( lpdi->item.pszText, l->f->channel, lpdi->item.cchTextMax );
+ else
+ lstrcpyn( lpdi->item.pszText, l->channel, lpdi->item.cchTextMax );
+ break;
+ case COL_ITEM:
+ if ( !l->token && l->f )
+ lstrcpyn( lpdi->item.pszText, l->f->item, lpdi->item.cchTextMax );
+ else
+ lstrcpyn( lpdi->item.pszText, l->item, lpdi->item.cchTextMax );
+ break;
+ case COL_PROGRESS:
+ if( !l->token && l->f )
+ WASABI_API_LNGSTRINGW_BUF( IDS_DONE, lpdi->item.pszText, lpdi->item.cchTextMax );
+ else
+ lstrcpyn( lpdi->item.pszText, l->status, lpdi->item.cchTextMax );
+ break;
+ case COL_PATH:
+ if ( !l->token && l->f )
+ lstrcpyn( lpdi->item.pszText, l->f->path, lpdi->item.cchTextMax );
+ else
+ {
+ if ( l->path )
+ lstrcpyn( lpdi->item.pszText, l->path, lpdi->item.cchTextMax );
+ }
+
+ break;
+ }
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+void listbuild( wchar_t **buf, int &buf_size, int &buf_pos, const wchar_t *tbuf )
+{
+ if ( !*buf )
+ {
+ *buf = (wchar_t *)calloc( 4096, sizeof( wchar_t ) );
+ if ( *buf )
+ {
+ buf_size = 4096;
+ buf_pos = 0;
+ }
+ else
+ {
+ buf_size = buf_pos = 0;
+ }
+ }
+ int newsize = buf_pos + lstrlenW( tbuf ) + 1;
+ if ( newsize < buf_size )
+ {
+ size_t old_buf_size = buf_size;
+ buf_size = newsize + 4096;
+ wchar_t *new_buf = (wchar_t *)realloc( *buf, ( buf_size + 1 ) * sizeof( wchar_t ) );
+ if ( new_buf )
+ {
+ *buf = new_buf;
+ }
+ else
+ {
+ new_buf = (wchar_t*)calloc( ( buf_size + 1 ), sizeof( wchar_t ) );
+ if ( new_buf )
+ {
+ memcpy( new_buf, *buf, ( old_buf_size * sizeof( wchar_t ) ) );
+ free( *buf );
+ *buf = new_buf;
+ }
+ else buf_size = (int)old_buf_size;
+ }
+ }
+
+ StringCchCopyW( *buf + buf_pos, buf_size, tbuf );
+ buf_pos = newsize;
+}
+
+wchar_t *getSelectedList()
+{
+ wchar_t *path = NULL;
+ int buf_pos = 0;
+ int buf_size = 0;
+ int download = -1;
+
+ while ( GetDownload( download ) )
+ {
+ if ( listContents[ download ]->f )
+ listbuild( &path, buf_size, buf_pos, listContents[ download ]->f->path );
+ }
+
+ if ( path )
+ path[ buf_pos ] = 0;
+
+ return path;
+}
+
+void SwapPlayEnqueueInMenu( HMENU listMenu )
+{
+ int playPos = -1, enqueuePos = -1;
+ MENUITEMINFOW playItem = { sizeof( MENUITEMINFOW ), 0, }, enqueueItem = { sizeof( MENUITEMINFOW ), 0, };
+
+ int numItems = GetMenuItemCount( listMenu );
+
+ for ( int i = 0; i < numItems; i++ )
+ {
+ UINT id = GetMenuItemID( listMenu, i );
+ if ( id == IDC_PLAY )
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW( listMenu, i, TRUE, &playItem );
+ }
+ else if ( id == IDC_ENQUEUE )
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos = i;
+ GetMenuItemInfoW( listMenu, i, TRUE, &enqueueItem );
+ }
+ }
+
+ playItem.wID = IDC_ENQUEUE;
+ enqueueItem.wID = IDC_PLAY;
+ SetMenuItemInfoW( listMenu, playPos, TRUE, &playItem );
+ SetMenuItemInfoW( listMenu, enqueuePos, TRUE, &enqueueItem );
+}
+
+void SyncMenuWithAccelerators( HWND hwndDlg, HMENU menu )
+{
+ HACCEL szAccel[ 24 ] = { 0 };
+ INT c = WASABI_API_APP->app_getAccelerators( hwndDlg, szAccel, sizeof( szAccel ) / sizeof( szAccel[ 0 ] ), FALSE );
+ AppendMenuShortcuts( menu, szAccel, c, MSF_REPLACE );
+}
+
+void UpdateMenuItems( HWND hwndDlg, HMENU menu )
+{
+ bool swapPlayEnqueue = false;
+ if ( ML_ENQDEF_VAL() )
+ {
+ SwapPlayEnqueueInMenu( menu );
+ swapPlayEnqueue = true;
+ }
+
+ SyncMenuWithAccelerators( hwndDlg, menu );
+ if ( swapPlayEnqueue )
+ SwapPlayEnqueueInMenu( menu );
+}
+
+int IPC_LIBRARY_SENDTOMENU = 0;
+librarySendToMenuStruct s = { 0 };
+
+static void DownloadList_RightClick(HWND hwndDlg, HWND listHwnd, POINTS pts)
+{
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+
+ RECT controlRect, headerRect;
+ if (FALSE == GetClientRect(listHwnd, &controlRect))
+ SetRectEmpty(&controlRect);
+ else
+ MapWindowPoints(listHwnd, HWND_DESKTOP, (POINT*)&controlRect, 2);
+
+ if ( -1 == pt.x && -1 == pt.y )
+ {
+ RECT itemRect;
+ int selected = downloadList.GetNextSelected();
+ if ( selected != -1 ) // if something is selected we'll drop the menu from there
+ {
+ downloadList.GetItemRect( selected, &itemRect );
+ ClientToScreen( listHwnd, (POINT *)&itemRect );
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect( listHwnd, &itemRect );
+
+ HWND hHeader = (HWND)SNDMSG( listHwnd, LVM_GETHEADER, 0, 0L );
+ RECT headerRect;
+ if ( ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) && GetWindowRect( hHeader, &headerRect ) )
+ {
+ itemRect.top += ( headerRect.bottom - headerRect.top );
+ }
+ }
+
+ pt.x = itemRect.left;
+ pt.y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(listHwnd, LVM_GETHEADER, 0, 0L);
+ if ( 0 == ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) || FALSE == GetWindowRect( hHeader, &headerRect ) )
+ {
+ SetRectEmpty( &headerRect );
+ }
+
+ if ( FALSE != PtInRect( &headerRect, pt ) )
+ {
+ return;
+ }
+
+ LVHITTESTINFO hitTest;
+ hitTest.pt = pt;
+ MapWindowPoints( HWND_DESKTOP, listHwnd, &hitTest.pt, 1 );
+
+ int index = ( downloadList.GetNextSelected() != -1 ? ListView_HitTest( listHwnd, &hitTest ) : -1 );
+
+ HMENU baseMenu = WASABI_API_LOADMENU( IDR_MENU1 );
+
+ if ( baseMenu == NULL )
+ return;
+
+ HMENU menu = GetSubMenu( baseMenu, 2 );
+ if ( menu != NULL )
+ {
+ UINT enableExtras = MF_BYCOMMAND | MF_ENABLED;
+ if ( index == -1 )
+ enableExtras |= ( MF_GRAYED | MF_DISABLED );
+
+ EnableMenuItem( menu, IDC_PLAY, enableExtras );
+ EnableMenuItem( menu, IDC_ENQUEUE, enableExtras );
+ EnableMenuItem( menu, IDC_REMOVE, enableExtras );
+ EnableMenuItem( menu, IDC_DELETE, enableExtras );
+ EnableMenuItem( menu, IDC_INFOBOX, enableExtras );
+ EnableMenuItem( menu, ID_DOWNLOADS_EXPLORERITEMFOLDER, enableExtras );
+ EnableMenuItem( menu, 2, MF_BYPOSITION | ( index == -1 ? ( MF_GRAYED | MF_DISABLED ) : MF_ENABLED ) );
+
+ { // send-to menu shit...
+ ZeroMemory( &s, sizeof( s ) );
+ IPC_LIBRARY_SENDTOMENU = SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE );
+ if ( IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU ) == 0xffffffff )
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[ 1 ] = 1;
+ s.build_hMenu = GetSubMenu( menu, 2 );
+ }
+ }
+
+ UpdateMenuItems( hwndDlg, menu );
+
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hwndDlg, NULL );
+ if ( !SendMessage( hwndDlg, WM_COMMAND, r, 0 ) )
+ {
+ s.menu_id = r; // more send to menu shit...
+ if ( s.mode == 2 && SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU ) == 0xffffffff )
+ {
+ s.mode = 3;
+ s.data_type = ML_TYPE_FILENAMESW;
+ wchar_t *path = getSelectedList();
+ if ( path )
+ {
+ s.data = path;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU );
+ free( path );
+ }
+ }
+ }
+
+ if ( s.mode )
+ { // yet more send to menu shit...
+ s.mode = 4;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU ); // cleanup
+ }
+
+ if ( NULL != s.build_hMenu )
+ {
+ DestroyMenu( s.build_hMenu );
+ s.build_hMenu = NULL;
+ }
+ }
+
+ DestroyMenu( baseMenu );
+}
+
+static void Downloads_ContextMenu( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ HWND sourceWindow = (HWND)wParam;
+ if ( sourceWindow == downloadList.getwnd() )
+ DownloadList_RightClick( hwndDlg, sourceWindow, MAKEPOINTS( lParam ) );
+}
+
+BOOL Downloads_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags )
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem( hwndDlg, buttonId );
+ GetWindowRect( buttonHWND, &r );
+ UpdateMenuItems( hwndDlg, menu );
+ MLSkinnedButton_SetDropDownState( buttonHWND, TRUE );
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if ( !( flags & BPM_WM_COMMAND ) )
+ tpmFlags |= TPM_RETURNCMD;
+ int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, tpmFlags, r.left, r.top, hwndDlg, NULL );
+ if ( ( flags & BPM_ECHO_WM_COMMAND ) && x )
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( x, 0 ), 0 );
+ MLSkinnedButton_SetDropDownState( buttonHWND, FALSE );
+ return x;
+}
+
+static void Downloads_Play( HWND hwndDlg, HWND from, UINT idFrom )
+{
+ HMENU listMenu = GetSubMenu( g_context_menus2, 0 );
+ int count = GetMenuItemCount( listMenu );
+ if ( count > 2 )
+ {
+ for ( int i = 2; i < count; i++ )
+ {
+ DeleteMenu( listMenu, 2, MF_BYPOSITION );
+ }
+ }
+
+ Downloads_ButtonPopupMenu( hwndDlg, idFrom, listMenu, BPM_WM_COMMAND );
+}
+
+static BOOL WINAPI DownloadDialog_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITMENUPOPUP: // yet yet more send to menu shit...
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ if (SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ Downloads_ContextMenu(hwndDlg, wParam, lParam);
+ return TRUE;
+
+ case WM_NOTIFYFORMAT:
+ return NFR_UNICODE;
+
+ case WM_INITDIALOG:
+ Downloads_Init(hwndDlg);
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ if (l->idFrom == IDC_DOWNLOADLIST)
+ return DownloadList_Notify(l,hwndDlg);
+ }
+ break;
+
+ case WM_DESTROY:
+ Downloads_Destroy(hwndDlg);
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ Downloads_DisplayChange(hwndDlg);
+ return 0;
+
+ case WM_TIMER:
+ Downloads_Timer(hwndDlg, wParam);
+ break;
+
+ case WM_MOUSEMOVE:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m;
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = p;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDRAG);
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ we_are_drag_and_dropping = 0;
+ ReleaseCapture();
+ POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = p;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDRAG);
+ if (m.result > 0)
+ {
+ m.flags = 0;
+ m.result = 0;
+ wchar_t* path = getSelectedList();
+ if(path)
+ {
+ m.data = path;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDROP);
+ free(path);
+ }
+ }
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_DOWNLOADLIST|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_APP + 104:
+ {
+ Downloads_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ if ( HIWORD( wParam ) == MLBN_DROPDOWN )
+ {
+ Downloads_Play( hwndDlg, (HWND)lParam, LOWORD( wParam ) );
+ }
+ else
+ {
+ bool action;
+ if ( LOWORD( wParam ) == IDC_PLAY )
+ {
+ action = ( HIWORD( wParam ) == 1 ) ? ML_ENQDEF_VAL() == 1 : 0;
+ }
+ else if ( LOWORD( wParam ) == IDC_ENQUEUE )
+ {
+ action = ( HIWORD( wParam ) == 1 ) ? ML_ENQDEF_VAL() != 1 : 1;
+ }
+ else
+ break;
+
+ Downloads_Play( action );
+ }
+ break;
+ case IDC_REMOVE:
+ Downloads_Remove();
+ break;
+ case IDC_DELETE:
+ Downloads_Delete( hwndDlg );
+ break;
+ case IDC_CLEANUP:
+ Downloads_CleanUp( hwndDlg );
+ break;
+ case IDC_INFOBOX:
+ Downloads_InfoBox( hwndDlg );
+ break;
+ case IDC_SELECTALL:
+ Downloads_SelectAll();
+ break;
+ case ID_DOWNLOADS_EXPLORERITEMFOLDER:
+ exploreItemFolder( hwndDlg );
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+HWND CALLBACK DownloadDialog_Create( HWND hParent, OmService *service )
+{
+ return WASABI_API_CREATEDIALOGPARAMW( IDD_DOWNLOADS, hParent, DownloadDialog_DlgProc, (LPARAM)service );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadsDialog.h b/Src/Plugins/Library/ml_wire/DownloadsDialog.h
new file mode 100644
index 00000000..baef5cd8
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadsDialog.h
@@ -0,0 +1,13 @@
+#ifndef NULLSOFT_DOWNLOADSDIALOGH
+#define NULLSOFT_DOWNLOADSDIALOGH
+
+#include "DownloadStatus.h"
+
+class OmService;
+HWND CALLBACK DownloadDialog_Create(HWND hParent, OmService *service);
+
+void DownloadsUpdated();
+void DownloadsUpdated( DownloadToken token, const DownloadedFile *f );
+void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token );
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/DownloadsParse.cpp b/Src/Plugins/Library/ml_wire/DownloadsParse.cpp
new file mode 100644
index 00000000..9f552fab
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadsParse.cpp
@@ -0,0 +1,204 @@
+#include "Main.h"
+#include "DownloadsParse.h"
+#include "Downloaded.h"
+#include "Defaults.h"
+#include "ParseUtil.h"
+#include <wchar.h>
+#include <locale.h>
+
+static __time64_t filetime( const wchar_t *file )
+{
+ if ( !file || !*file )
+ return 0;
+
+ WIN32_FIND_DATA f = { 0 };
+
+ HANDLE h = FindFirstFile( file, &f );
+ if ( h == INVALID_HANDLE_VALUE )
+ return 0;
+
+ FindClose( h );
+ SYSTEMTIME s = { 0 };
+ FileTimeToSystemTime( &f.ftCreationTime, &s );
+
+ tm t = { 0 };
+ t.tm_year = s.wYear - 1900;
+ t.tm_mon = s.wMonth - 1;
+ t.tm_mday = s.wDay;
+ t.tm_hour = s.wHour;
+ t.tm_min = s.wMinute;
+ t.tm_sec = s.wMinute;
+
+ return _mktime64( &t );
+}
+
+static void ReadDownload( const XMLNode *item, bool addToLib = false )
+{
+ DownloadedFile newDownloaded;
+
+ const wchar_t *channel = GetContent( item, L"channel" );
+ newDownloaded.SetChannel( channel );
+
+ const wchar_t *item_str = GetContent( item, L"item" );
+ newDownloaded.SetItem( item_str );
+
+ const wchar_t *url = GetContent( item, L"url" );
+ newDownloaded.SetURL( url );
+
+ const wchar_t *path = GetContent( item, L"path" );
+ newDownloaded.SetPath( path );
+
+ const wchar_t *publishDate = GetContent( item, L"publishDate" );
+
+ if ( publishDate && publishDate[ 0 ] )
+ newDownloaded.publishDate = _wtoi( publishDate );
+ else
+ newDownloaded.publishDate = filetime( newDownloaded.path );
+
+ if ( addToLib )
+ addToLibrary( newDownloaded );
+
+ downloadedFiles.downloadList.push_back( newDownloaded );
+}
+
+static void ReadPreferences( const XMLNode *item )
+{
+ const XMLNode *curNode;
+
+ curNode = item->Get(L"download");
+ if ( curNode )
+ {
+ const wchar_t *prop = curNode->GetProperty( L"downloadpath" );
+ if ( prop )
+ lstrcpyn( defaultDownloadPath, prop, MAX_PATH );
+
+ autoDownload = PropertyIsTrue( curNode, L"autodownload" );
+
+ prop = curNode->GetProperty( L"autoDownloadEpisodes" );
+ if ( prop )
+ autoDownloadEpisodes = _wtoi( prop );
+ needToMakePodcastsView = PropertyIsTrue( curNode, L"needToMakePodcastsView" );
+ }
+
+ curNode = item->Get(L"update");
+ if ( curNode )
+ {
+ const wchar_t *prop = curNode->GetProperty( L"updatetime" );
+ if ( prop )
+ updateTime = _wtoi64( prop );
+
+ autoUpdate = PropertyIsTrue( curNode, L"autoupdate" );
+ updateOnLaunch = PropertyIsTrue( curNode, L"updateonlaunch" );
+ }
+
+ curNode = item->Get(L"subscriptions");
+ if ( curNode )
+ {
+ _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
+ const wchar_t *prop = curNode->GetProperty( L"htmldivider" );
+ if ( prop )
+ htmlDividerPercent = (float)_wtof_l( prop, C_locale );
+
+ prop = curNode->GetProperty( L"channeldivider" );
+ if ( prop && prop[ 0 ] )
+ channelDividerPercent = (float)_wtof_l( prop, C_locale );
+
+ prop = curNode->GetProperty( L"itemtitlewidth" );
+ if ( prop && prop[ 0 ] )
+ itemTitleWidth = _wtoi( prop );
+
+ prop = curNode->GetProperty( L"itemdatewidth" );
+ if ( prop && prop[ 0 ] )
+ itemDateWidth = _wtoi( prop );
+
+ prop = curNode->GetProperty( L"itemmediawidth" );
+ if ( prop && prop[ 0 ] )
+ itemMediaWidth = _wtoi( prop );
+
+ prop = curNode->GetProperty( L"itemsizewidth" );
+ if ( prop && prop[ 0 ] )
+ itemSizeWidth = _wtoi( prop );
+
+ prop = curNode->GetProperty( L"currentitemsort" );
+ if ( prop && prop[ 0 ] )
+ currentItemSort = _wtoi( prop );
+
+ itemSortAscending = !PropertyIsFalse( curNode, L"itemsortascending" );
+
+ channelSortAscending = !PropertyIsFalse( curNode, L"channelsortascending" );
+
+ prop = curNode->GetProperty( L"channelLastSelection" );
+ if ( prop && prop[ 0 ] )
+ channelLastSelection = _wtoi( prop );
+ }
+
+ curNode = item->Get(L"downloadsView");
+ if ( curNode )
+ {
+ const wchar_t *prop = curNode->GetProperty( L"downloadsChannelWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsChannelWidth = _wtoi( prop );
+ if ( downloadsChannelWidth <= 0 )
+ downloadsChannelWidth = DOWNLOADSCHANNELWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsItemWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsItemWidth = _wtoi( prop );
+ if ( downloadsItemWidth <= 0 )
+ downloadsItemWidth = DOWNLOADSITEMWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsProgressWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsProgressWidth = _wtoi( prop );
+ if ( downloadsProgressWidth <= 0 )
+ downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsPathWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsPathWidth = _wtoi( prop );
+ if ( downloadsPathWidth <= 0 )
+ downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
+
+ prop = curNode->GetProperty( L"downloadsItemSort" );
+ if ( prop && prop[ 0 ] )
+ downloadsItemSort = _wtoi( prop );
+
+ downloadsSortAscending = !PropertyIsFalse( curNode, L"downloadsSortAscending" );
+ }
+
+ curNode = item->Get(L"service");
+ if ( curNode )
+ {
+ const wchar_t *prop = curNode->GetProperty( L"url" );
+ if ( prop )
+ lstrcpyn( serviceUrl, prop, MAX_PATH );
+ }
+}
+
+void DownloadsParse::ReadNodes( const wchar_t *url )
+{
+ XMLNode::NodeList::const_iterator itr;
+ const XMLNode *curNode = xmlDOM.GetRoot();
+
+ curNode = curNode->Get( L"winamp:preferences" );
+ if ( curNode )
+ {
+ int version = 1;
+ const wchar_t *prop = curNode->GetProperty( L"version" );
+ if ( prop && prop[ 0 ] )
+ version = _wtoi( prop );
+
+ ReadPreferences( curNode );
+
+ curNode = curNode->Get( L"downloads" );
+ if ( curNode )
+ {
+ const XMLNode::NodeList *downloadsList = curNode->GetList( L"download" );
+ if ( downloadsList )
+ {
+ for ( itr = downloadsList->begin(); itr != downloadsList->end(); itr++ )
+ ReadDownload( *itr, version < 2 );
+ }
+ }
+ }
+}
diff --git a/Src/Plugins/Library/ml_wire/DownloadsParse.h b/Src/Plugins/Library/ml_wire/DownloadsParse.h
new file mode 100644
index 00000000..8f70ceb9
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/DownloadsParse.h
@@ -0,0 +1,13 @@
+#ifndef NULLSOFT_DOWNLOADSPARSEH
+#define NULLSOFT_DOWNLOADSPARSEH
+
+#include "DownloadThread.h"
+
+class DownloadsParse : public DownloadThread
+{
+public:
+ virtual void ReadNodes(const wchar_t *url);
+
+};
+
+#endif
diff --git a/Src/Plugins/Library/ml_wire/ExternalCOM.cpp b/Src/Plugins/Library/ml_wire/ExternalCOM.cpp
new file mode 100644
index 00000000..0586810d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ExternalCOM.cpp
@@ -0,0 +1,85 @@
+#include "main.h"
+#include "./externalCOM.h"
+#include "./util.h"
+#include "./rssCOM.h"
+
+#define DISPTABLE_CLASS ExternalCOM
+
+DISPTABLE_BEGIN()
+ DISPENTRY_ADD(DISPATCH_PODCAST, L"Podcast", OnPodcast)
+DISPTABLE_END
+
+#undef DISPTABLE_CLASS
+
+ExternalCOM::ExternalCOM()
+{}
+
+ExternalCOM::~ExternalCOM()
+{}
+
+HRESULT ExternalCOM::CreateInstance(ExternalCOM **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ *instance = new ExternalCOM();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+STDMETHODIMP_( ULONG ) ExternalCOM::AddRef( void )
+{
+ return _ref.fetch_add( 1 );
+}
+
+STDMETHODIMP_( ULONG ) ExternalCOM::Release( void )
+{
+ if ( 0 == _ref.load() )
+ return _ref.load();
+
+ LONG r = _ref.fetch_sub( 1 );
+ if ( 0 == r )
+ delete( this );
+
+ return r;
+}
+
+STDMETHODIMP ExternalCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (NULL == ppvObject) return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = static_cast<IDispatch*>(this);
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = static_cast<IUnknown*>(this);
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT ExternalCOM::OnPodcast(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ if (NULL != pvarResult)
+ {
+ VariantInit(pvarResult);
+
+ RssCOM *rss;
+ if (SUCCEEDED(RssCOM::CreateInstance(&rss)))
+ {
+ V_VT(pvarResult) = VT_DISPATCH;
+ V_DISPATCH(pvarResult) = rss;
+ }
+ else
+ {
+ V_VT(pvarResult) = VT_NULL;
+ }
+
+ }
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ExternalCOM.h b/Src/Plugins/Library/ml_wire/ExternalCOM.h
new file mode 100644
index 00000000..df792ad6
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ExternalCOM.h
@@ -0,0 +1,40 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_EXTERNAL_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_EXTERNAL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <atomic>
+
+#include "../nu/dispatchTable.h"
+
+class ExternalCOM : public IDispatch
+{
+public:
+ typedef enum
+ {
+ DISPATCH_PODCAST = 777,
+ } DispatchCodes;
+
+protected:
+ ExternalCOM();
+ ~ExternalCOM();
+
+public:
+ static HRESULT CreateInstance(ExternalCOM **instance);
+
+ /* IUnknown*/
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+protected:
+ DISPTABLE_INCLUDE();
+ DISPHANDLER_REGISTER(OnPodcast);
+
+ std::atomic<std::size_t> _ref = 1;
+};
+
+#endif //NULLSOFT_PODCAST_PLUGIN_EXTERNAL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Factory.cpp b/Src/Plugins/Library/ml_wire/Factory.cpp
new file mode 100644
index 00000000..c1ba56b3
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Factory.cpp
@@ -0,0 +1,64 @@
+#include "api__ml_wire.h"
+#include "Factory.h"
+#include "Wire.h"
+
+static const char serviceName[] = "Podcasts";
+
+FOURCC PodcastsFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *PodcastsFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID PodcastsFactory::GetGUID()
+{
+ return api_podcastsGUID;
+}
+
+void *PodcastsFactory::GetInterface( int global_lock )
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &channels;
+}
+
+int PodcastsFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int PodcastsFactory::ReleaseInterface( void *ifc )
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *PodcastsFactory::GetTestString()
+{
+ return 0;
+}
+
+int PodcastsFactory::ServiceNotify( int msg, int param1, int param2 )
+{
+ return 1;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS PodcastsFactory
+START_DISPATCH;
+CB( WASERVICEFACTORY_GETSERVICETYPE, GetServiceType )
+CB( WASERVICEFACTORY_GETSERVICENAME, GetServiceName )
+CB( WASERVICEFACTORY_GETGUID, GetGUID )
+CB( WASERVICEFACTORY_GETINTERFACE, GetInterface )
+CB( WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface )
+CB( WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface )
+CB( WASERVICEFACTORY_GETTESTSTRING, GetTestString )
+CB( WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify )
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_wire/Factory.h b/Src/Plugins/Library/ml_wire/Factory.h
new file mode 100644
index 00000000..dae98439
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Factory.h
@@ -0,0 +1,22 @@
+#ifndef NULLSOFT_ML_WIRE_FACTORY_H
+#define NULLSOFT_ML_WIRE_FACTORY_H
+#include "api__ml_wire.h"
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class PodcastsFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/FeedParse.cpp b/Src/Plugins/Library/ml_wire/FeedParse.cpp
new file mode 100644
index 00000000..f39c7a7f
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/FeedParse.cpp
@@ -0,0 +1,38 @@
+#include "Main.h"
+#include "FeedParse.h"
+
+
+#include "RFCDate.h"
+
+#include "RSSParse.h"
+#include "AtomParse.h"
+#ifdef DEBUG
+#include <iostream>
+static void DisplayNodes(XMLNode &node)
+{
+ XMLNode::NodeMap::iterator nodeItr;
+ for (nodeItr = node.nodes.begin();nodeItr != node.nodes.end(); nodeItr++)
+ {
+
+ for (XMLNode::NodeList::iterator itr = nodeItr->second.begin(); itr != nodeItr->second.end(); itr++)
+ {
+ std::wcerr << L"<" << nodeItr->first << L">" << std::endl;
+ DisplayNodes(**itr);
+ std::wcerr << L"</" << nodeItr->first << L">" << std::endl;
+ }
+
+ }
+}
+#endif
+
+void FeedParse::ReadNodes(const wchar_t *url)
+{
+ const XMLNode *curNode = xmlDOM.GetRoot();
+
+ curNode = curNode->Get(L"rss");
+ if (curNode)
+ {
+ ReadRSS(curNode, sync, loadingOwnFeed, url);
+ return ;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/FeedParse.h b/Src/Plugins/Library/ml_wire/FeedParse.h
new file mode 100644
index 00000000..580c2dd6
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/FeedParse.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_FEEDPARSEH
+#define NULLSOFT_FEEDPARSEH
+
+#include "DownloadThread.h"
+#include "ChannelSync.h"
+class FeedParse : public DownloadThread
+{
+public:
+ FeedParse(ChannelSync *_sync, bool doWinampSpecificTags = false)
+ : sync(_sync), loadingOwnFeed(doWinampSpecificTags)
+ {}
+
+ ~FeedParse()
+ {
+ sync = 0;
+ }
+
+ virtual void ReadNodes(const wchar_t *url);
+private:
+ ChannelSync *sync;
+ bool loadingOwnFeed;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/FeedUtil.cpp b/Src/Plugins/Library/ml_wire/FeedUtil.cpp
new file mode 100644
index 00000000..bf5b5ae8
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/FeedUtil.cpp
@@ -0,0 +1,37 @@
+#include "main.h"
+#include "FeedUtil.h"
+#include "ChannelCheck.h"
+#include "FeedParse.h"
+#include "errors.h"
+#include "./defaults.h"
+
+
+int DownloadFeedInformation(Channel &newFeed)
+{
+ ChannelCheck check;
+ FeedParse downloader(&check, false);
+
+ int ret = downloader.DownloadURL(newFeed.url);
+ if (ret != DOWNLOAD_SUCCESS)
+ return ret;
+
+ if (!check.channel.title || !check.channel.title[0])
+ return DOWNLOAD_NOTRSS;
+
+ newFeed.SetTitle(check.channel.title);
+ if (check.channel.ttl)
+ {
+ newFeed.updateTime = check.channel.ttl * 60;
+ newFeed.autoUpdate = true;
+ }
+ else
+ {
+ newFeed.updateTime = ::updateTime;
+ newFeed.autoUpdate = ::autoUpdate;
+ }
+
+ if (check.channel.url && check.channel.url[0])
+ newFeed.SetURL(check.channel.url);
+
+ return DOWNLOAD_SUCCESS;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/FeedUtil.h b/Src/Plugins/Library/ml_wire/FeedUtil.h
new file mode 100644
index 00000000..466ef98d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/FeedUtil.h
@@ -0,0 +1,8 @@
+#ifndef NULLSOFT_FEEDUTILH
+#define NULLSOFT_FEEDUTILH
+
+#include "Feeds.h"
+
+
+int DownloadFeedInformation(Channel &channel);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Feeds.cpp b/Src/Plugins/Library/ml_wire/Feeds.cpp
new file mode 100644
index 00000000..9e81705d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Feeds.cpp
@@ -0,0 +1,298 @@
+#include "main.h"
+#include "Feeds.h"
+#include "./util.h"
+#include "./defaults.h"
+#include <algorithm>
+#include <shlwapi.h>
+#include "BackgroundDownloader.h"
+#include <strsafe.h>
+
+static bool operator == (const RSS::Item &a, const RSS::Item &b)
+{
+ if(a.guid && a.guid[0] && b.guid && b.guid[0])
+ return !wcscmp(a.guid, b.guid);
+
+ if(a.publishDate && b.publishDate)
+ return a.publishDate == b.publishDate;
+
+ if (a.url && a.url[0] && b.url && b.url[0])
+ return !wcscmp(a.url, b.url);
+
+ return a.url == b.url;
+}
+
+Channel::Channel()
+{
+ Init();
+}
+
+Channel::Channel(const Channel &copy)
+{
+ Init();
+ operator =(copy);
+}
+
+const Channel &Channel::operator =(const Channel &copy)
+{
+ Reset();
+ Init();
+ url = _wcsdup(copy.url);
+ title = _wcsdup(copy.title);
+ link = _wcsdup(copy.link);
+ description = _wcsdup(copy.description);
+ ttl=copy.ttl;
+ updateTime=copy.updateTime;
+ lastUpdate=copy.lastUpdate;
+ autoDownloadEpisodes=copy.autoDownloadEpisodes;
+ autoDownload=copy.autoDownload;
+ autoUpdate=copy.autoUpdate;
+ useDefaultUpdate=copy.useDefaultUpdate;
+ needsRefresh=copy.needsRefresh;
+ items=copy.items;
+ return *this;
+}
+
+Channel::~Channel()
+{
+ Reset();
+}
+
+void Channel::Init()
+{
+ url =0;
+ title = 0;
+ link = 0;
+ description = 0;
+ ttl=0;
+ lastUpdate=0;
+ autoUpdate = ::autoUpdate;
+ updateTime = ::updateTime;
+ autoDownload = ::autoDownload;
+ autoDownloadEpisodes = ::autoDownloadEpisodes;
+ useDefaultUpdate=true;
+ needsRefresh=false;
+}
+
+void Channel::Reset()
+{
+ free(url);
+ free(title);
+ free(link);
+ free(description);
+}
+
+void Channel::UpdateFrom(const Channel &copy)
+{
+ if (copy.url && copy.url[0])
+ SetURL(copy.url);
+
+ SetTitle(copy.title);
+ SetLink(copy.link);
+ SetDescription(copy.description);
+ if (copy.ttl)
+ ttl=copy.ttl;
+
+ ItemList::const_iterator itr;
+
+ for (itr=copy.items.begin();itr!=copy.items.end();itr++)
+ {
+ const RSS::Item &b = *itr;
+ if ( b.url && b.url[0] )
+ {
+ ((RSS::Item*)&b)->downloaded = IsPodcastDownloaded(b.url);
+ }
+ }
+
+ // update to the latest default setting
+ if (useDefaultUpdate)
+ {
+ autoUpdate = ::autoUpdate;
+ updateTime = ::updateTime;
+ autoDownload = ::autoDownload;
+ autoDownloadEpisodes = ::autoDownloadEpisodes;
+ }
+
+ items.clear(); // benski> added for 5.23
+ for (itr=copy.items.begin();itr!=copy.items.end();itr++)
+ {
+ items.insert(items.begin(), *itr);
+ }
+
+ if(autoDownload)
+ {
+ SortByDate();
+ size_t idx = items.size();
+ if (idx)
+ {
+ int episodeCount = 0;
+ do
+ {
+ idx--;
+ const RSS::Item &b = items[idx];
+ if(b.url && b.url[0])
+ {
+ episodeCount++;
+ if (!b.downloaded)
+ {
+ WCHAR szPath[MAX_PATH *2] = {0};
+ if (SUCCEEDED(((RSS::Item*)&b)->GetDownloadFileName(title, szPath, ARRAYSIZE(szPath), TRUE)))
+ {
+ wchar_t* url = urlencode(b.url);
+ downloader.Download(url, szPath, title, b.itemName, b.publishDate);
+ ((RSS::Item*)&b)->downloaded = true;
+ free(url);
+ }
+ }
+
+ }
+ } while (episodeCount<autoDownloadEpisodes && idx);
+ }
+ }
+}
+
+bool Channel::operator == (const Channel &compare)
+{
+ // changed from basing on the title as this allows for podcasts
+ // with the same name to still work instead of being mangled as
+ // was able to happen when this based things on the title value
+ if (!compare.url || !compare.url[0])
+ return false;
+ return !wcscmp(url, compare.url);
+}
+
+bool TitleMediaSort(const RSS::Item &item1, const RSS::Item &item2)
+{
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item1.itemName, -1, item2.itemName, -1));
+}
+
+void Channel::SortByTitle()
+{
+ std::sort(items.begin(), items.end(), TitleMediaSort);
+}
+
+static bool ItemMediaSort(const RSS::Item &item1, const RSS::Item &item2)
+{
+ if (!item1.url || !item1.url[0])
+ return false;
+
+ if (!item2.url || !item2.url[0])
+ return true;
+
+ if (!item2.listened)
+ return false;
+
+ if (item1.listened)
+ return false;
+ return true;
+}
+
+bool ParseDuration(const wchar_t *duration, int *out_hours, int *out_minutes, int *out_seconds);
+static bool ItemMediaTimeSort(const RSS::Item &item1, const RSS::Item &item2)
+{
+ if (!item1.duration || !item1.duration[0])
+ return false;
+
+ if (!item2.duration || !item2.duration[0])
+ return true;
+
+ int h1, h2, m1, m2, s1, s2;
+ if (!ParseDuration(item1.duration, &h1, &m1, &s1))
+ return false;
+
+ if (!ParseDuration(item2.duration, &h2, &m2, &s2))
+ return true;
+
+ if (h1 < h2)
+ return true;
+ else if (h1 > h2)
+ return false;
+
+ if (m1 < m2)
+ return true;
+ else if (m1 > m2)
+ return false;
+
+ if (s1 < s2)
+ return true;
+ else
+ return false;
+}
+
+static bool ItemMediaSizeSort(const RSS::Item &item1, const RSS::Item &item2)
+{
+ if (!item1.size)
+ return false;
+
+ if (!item2.size)
+ return true;
+
+ return item1.size < item2.size;
+}
+
+void Channel::SortByMedia()
+{
+ std::sort(items.begin(), items.end(), ItemMediaSort);
+}
+
+void Channel::SortByMediaTime()
+{
+ std::sort(items.begin(), items.end(), ItemMediaTimeSort);
+}
+
+void Channel::SortByMediaSize()
+{
+ std::sort(items.begin(), items.end(), ItemMediaSizeSort);
+}
+
+bool ItemDateSort(const RSS::Item &item1, const RSS::Item &item2)
+{
+ return (item1.publishDate < item2.publishDate);
+}
+
+void Channel::SortByDate()
+{
+ std::sort(items.begin(), items.end(), ItemDateSort);
+}
+
+int Channel::GetTitle(wchar_t *str, size_t len)
+{
+ if (str && len)
+ {
+ str[0]=0;
+ if (title && title[0])
+ StringCchCopyW(str, len, title);
+ return 0;
+ }
+ return 1;
+}
+
+void Channel::SetURL(const wchar_t *val)
+{
+ free(url);
+ url = _wcsdup(val);
+}
+
+void Channel::SetTitle(const wchar_t *val)
+{
+ free(title);
+ title = _wcsdup(val);
+}
+
+void Channel::SetLink(const wchar_t *val)
+{
+ free(link);
+ link = _wcsdup(val);
+}
+
+void Channel::SetDescription(const wchar_t *val)
+{
+ free(description);
+ description = _wcsdup(val);
+}
+
+#undef CBCLASS
+#define CBCLASS Channel
+START_DISPATCH;
+CB(IFC_PODCAST_GETTITLE, GetTitle)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Feeds.h b/Src/Plugins/Library/ml_wire/Feeds.h
new file mode 100644
index 00000000..a8075627
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Feeds.h
@@ -0,0 +1,50 @@
+#ifndef NULLSOFT_FEEDSH
+#define NULLSOFT_FEEDSH
+
+#include "ifc_podcast.h"
+#include "Item.h"
+#include <vector>
+
+class Channel : public ifc_podcast
+{
+public:
+ typedef std::vector<RSS::Item> ItemList;
+ Channel();
+ Channel(const Channel &copy);
+ const Channel &operator =(const Channel &copy);
+ ~Channel();
+ void SortByTitle(), SortByMedia(), SortByMediaTime(), SortByDate(), SortByMediaSize();
+ bool operator == (const Channel &compare);
+ //void operator = (const Channel &copy);
+ void UpdateFrom(const Channel &copy);
+
+ unsigned int ttl;
+ __time64_t updateTime, lastUpdate;
+ int autoDownloadEpisodes;
+ bool autoUpdate;
+ bool useDefaultUpdate;
+ bool autoDownload;
+ bool needsRefresh;
+ // TODO: std::wstring downloadLocation;
+ ItemList items;
+
+ void SetURL(const wchar_t *val);
+ void SetTitle(const wchar_t *val);
+ void SetLink(const wchar_t *val);
+ void SetDescription(const wchar_t *val);
+
+ wchar_t *url, *title, *link, *description;
+
+public: // ifc_podcast interface
+ int GetTitle(wchar_t *str, size_t len);
+
+private:
+ void Init();
+ void Reset();
+
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/FeedsDialog.h b/Src/Plugins/Library/ml_wire/FeedsDialog.h
new file mode 100644
index 00000000..1409b1ac
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/FeedsDialog.h
@@ -0,0 +1,5 @@
+#ifndef NULLSOFT_FEEDSDIALOGH
+#define NULLSOFT_FEEDSDIALOGH
+
+BOOL CALLBACK FeedsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Item.cpp b/Src/Plugins/Library/ml_wire/Item.cpp
new file mode 100644
index 00000000..37126a9d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Item.cpp
@@ -0,0 +1,174 @@
+#include "Item.h"
+#include "util.h"
+#include "defaults.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+void RSS::Item::Reset()
+{
+ free(itemName);
+ free(url);
+ free(sourceUrl);
+ free(guid);
+ free(description);
+ free(link);
+ free(duration);
+}
+
+void RSS::Item::Init()
+{
+ listened = false;
+ publishDate = 0;
+ generatedDate = true;
+ downloaded=false;
+ itemName=0;
+ url=0;
+ sourceUrl=0;
+ guid=0;
+ description=0;
+ link=0;
+ duration=0;
+ size=0;
+}
+
+RSS::Item::Item()
+{
+ Init();
+}
+
+RSS::Item::~Item()
+{
+ Reset();
+}
+
+RSS::Item::Item(const RSS::Item &copy)
+{
+ Init();
+ operator =(copy);
+}
+
+const RSS::Item &RSS::Item::operator =(const RSS::Item &copy)
+{
+ Reset();
+ Init();
+ listened=copy.listened;
+ publishDate = copy.publishDate;
+ generatedDate = copy.generatedDate;
+ downloaded=copy.downloaded;
+ itemName=_wcsdup(copy.itemName);
+ url=_wcsdup(copy.url);
+ sourceUrl=_wcsdup(copy.sourceUrl);
+ guid=_wcsdup(copy.guid);
+ description=_wcsdup(copy.description);
+ link=_wcsdup(copy.link);
+ duration=wcsdup(copy.duration);
+ size = copy.size;
+ return *this;
+}
+
+HRESULT RSS::Item::GetDownloadFileName(const wchar_t *channelName, wchar_t *buffer, int bufferMax, BOOL fValidatePath) const
+{
+ if (NULL == buffer || NULL == channelName) return E_INVALIDARG;
+ buffer[0] = L'\0';
+
+ WCHAR szBuffer[MAX_PATH] = {0};
+
+ if (FAILED(StringCchCopyN(szBuffer, ARRAYSIZE(szBuffer), channelName, 100)))
+ return E_UNEXPECTED;
+
+ Plugin_CleanDirectory(szBuffer);
+ Plugin_ReplaceBadPathChars(szBuffer);
+
+ if (L'\0' == *szBuffer)
+ StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), L"UnknownChannel");
+
+ if (FALSE == PathCombine(buffer, defaultDownloadPath, szBuffer))
+ return E_FAIL;
+
+ if (FALSE != fValidatePath && FAILED(Plugin_EnsurePathExist(buffer)))
+ return E_FAIL;
+
+ LPWSTR cursor = szBuffer;
+ size_t remaining = ARRAYSIZE(szBuffer);
+
+ tm* time = _localtime64(&publishDate);
+ if(NULL != time && publishDate > 0)
+ {
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"%04d-%02d-%02d - ", time->tm_year+1900, time->tm_mon+1, time->tm_mday);
+ }
+
+ LPWSTR t = cursor;
+ if (FAILED(StringCchCopyNEx(cursor, remaining, itemName, 100, &cursor, &remaining, 0)))
+ return E_UNEXPECTED;
+
+ INT offset = Plugin_CleanDirectory(t);
+ if (0 != offset)
+ {
+ remaining += offset;
+ cursor -= offset;
+ }
+
+ if (t == cursor)
+ StringCchCopyEx(cursor, remaining, L"UnknownItem", &cursor, &remaining, 0);
+ else
+ Plugin_ReplaceBadPathChars(t);
+
+ if (FAILED(Plugin_FileExtensionFromUrl(cursor, (INT)remaining, url, L".mp3")))
+ return E_UNEXPECTED;
+
+ if (FALSE == PathAppend(buffer, szBuffer))
+ return E_FAIL;
+
+ return S_OK;
+}
+
+void RSS::MutableItem::SetLink(const wchar_t *value)
+{
+ free(link);
+ link = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetItemName(const wchar_t *value)
+{
+ free(itemName);
+ itemName = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetURL(const wchar_t *value)
+{
+ free(url);
+ url = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetSourceURL(const wchar_t *value)
+{
+ free(sourceUrl);
+ sourceUrl = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetGUID(const wchar_t *value)
+{
+ free(guid);
+ guid = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetDescription(const wchar_t *value)
+{
+ free(description);
+ description = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetDuration(const wchar_t *value)
+{
+ free(duration);
+ duration = _wcsdup(value);
+}
+
+void RSS::MutableItem::SetSize(const wchar_t * _size)
+{
+ if (_size)
+ size = _wtoi64(_size);
+ else
+ size=0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Item.h b/Src/Plugins/Library/ml_wire/Item.h
new file mode 100644
index 00000000..e7a5e82b
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Item.h
@@ -0,0 +1,50 @@
+#pragma once
+#include <bfc/platform/types.h>
+#include <time.h>
+#include <windows.h>
+
+namespace RSS
+{
+ class Item
+ {
+ public:
+ Item();
+ ~Item();
+ Item(const Item &copy);
+ const Item &operator =(const Item &copy);
+
+ HRESULT GetDownloadFileName(const wchar_t *channelName, wchar_t *buffer, int bufferMax, BOOL fValidatePath) const;
+ bool listened;
+ bool downloaded;
+ __time64_t publishDate;
+ bool generatedDate;
+
+ //protected:
+ wchar_t *itemName;
+ wchar_t *url;
+ wchar_t *sourceUrl;
+ wchar_t *guid;
+ wchar_t *description;
+ wchar_t *link;
+ wchar_t *duration;
+ int64_t size;
+
+ private:
+ void Init();
+ void Reset();
+ };
+
+ class MutableItem : public Item
+ {
+ public:
+ void SetItemName(const wchar_t *value);
+ void SetLink(const wchar_t *value);
+ void SetURL(const wchar_t *value);
+ void SetSourceURL(const wchar_t *value);
+ void SetGUID(const wchar_t *value);
+ void SetDescription(const wchar_t *value);
+ void SetDuration(const wchar_t *value);
+ void SetSize(const wchar_t * _size);
+ };
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/JSAPI2_Creator.cpp b/Src/Plugins/Library/ml_wire/JSAPI2_Creator.cpp
new file mode 100644
index 00000000..9956fd3f
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/JSAPI2_Creator.cpp
@@ -0,0 +1,94 @@
+#include "main.h"
+#include "JSAPI2_Creator.h"
+#include "JSAPI2_PodcastsAPI.h"
+#include "api__ml_wire.h"
+
+IDispatch *JSAPI2_Creator::CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info)
+{
+ if (!_wcsicmp(name, L"Podcasts"))
+ return new JSAPI2::PodcastsAPI(key, info);
+ else
+ return 0;
+}
+
+int JSAPI2_Creator::PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data)
+{
+ if (group && !_wcsicmp(group, L"podcasts"))
+ {
+ const wchar_t *title_str = AGAVE_API_JSAPI2_SECURITY->GetAssociatedName(authorization_key);
+ return AGAVE_API_JSAPI2_SECURITY->SecurityPrompt(parent, title_str, L"This service is trying to subscribe you to a podcast.", JSAPI2::svc_apicreator::AUTHORIZATION_FLAG_GROUP_ONLY);
+ }
+ else
+ return JSAPI2::svc_apicreator::AUTHORIZATION_UNDEFINED;
+}
+
+
+#define CBCLASS JSAPI2_Creator
+START_DISPATCH;
+CB(JSAPI2_SVC_APICREATOR_CREATEAPI, CreateAPI);
+CB(JSAPI2_SVC_APICREATOR_PROMPTFORAUTHORIZATION, PromptForAuthorization);
+END_DISPATCH;
+#undef CBCLASS
+
+static JSAPI2_Creator jsapi2_svc;
+static const char serviceName[] = "Podcast Javascript Objects";
+
+// {EE2C54DB-E609-410a-A962-573BA2F9C3AC}
+static const GUID jsapi2_factory_guid =
+{ 0xee2c54db, 0xe609, 0x410a, { 0xa9, 0x62, 0x57, 0x3b, 0xa2, 0xf9, 0xc3, 0xac } };
+
+FOURCC JSAPI2Factory::GetServiceType()
+{
+ return jsapi2_svc.getServiceType();
+}
+
+const char *JSAPI2Factory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID JSAPI2Factory::GetGUID()
+{
+ return jsapi2_factory_guid;
+}
+
+void *JSAPI2Factory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &jsapi2_svc;
+}
+
+int JSAPI2Factory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int JSAPI2Factory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *JSAPI2Factory::GetTestString()
+{
+ return 0;
+}
+
+int JSAPI2Factory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS JSAPI2Factory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/JSAPI2_Creator.h b/Src/Plugins/Library/ml_wire/JSAPI2_Creator.h
new file mode 100644
index 00000000..fd85a3f0
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/JSAPI2_Creator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class JSAPI2Factory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+class JSAPI2_Creator : public JSAPI2::svc_apicreator
+{
+ IDispatch *CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info);
+ int PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data);
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.cpp b/Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.cpp
new file mode 100644
index 00000000..bab563d1
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.cpp
@@ -0,0 +1,109 @@
+#include "JSAPI2_PodcastsAPI.h"
+#include "../Winamp/JSAPI.h"
+#include "api__ml_wire.h"
+#include "./rssCOM.h"
+
+JSAPI2::PodcastsAPI::PodcastsAPI( const wchar_t *_key, JSAPI::ifc_info *_info )
+{
+ info = _info;
+ key = _key;
+}
+
+enum
+{
+ DISP_PODCASTS_SUBSCRIBE,
+};
+
+#define DISP_TABLE \
+ CHECK_ID(Subscribe, DISP_PODCASTS_SUBSCRIBE)\
+
+#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = id; continue; }
+HRESULT JSAPI2::PodcastsAPI::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ DISP_TABLE
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::PodcastsAPI::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::PodcastsAPI::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::PodcastsAPI::Subscribe(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+
+ if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"podcasts", L"subscribe", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ RssCOM::SubscribeUrl(JSAPI_PARAM(pdispparams, 1).bstrVal, pvarResult);
+ }
+ else
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_FALSE);
+ }
+
+ return S_OK;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str, id) case id: return str(wFlags, pdispparams, pvarResult, puArgErr);
+HRESULT JSAPI2::PodcastsAPI::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ DISP_TABLE
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JSAPI2::PodcastsAPI::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::PodcastsAPI::AddRef(void)
+{
+ return _refCount.fetch_add( 1 );
+}
+
+
+ULONG JSAPI2::PodcastsAPI::Release( void )
+{
+ LONG lRef = _refCount.fetch_sub( 1 );
+ if ( lRef == 0 )
+ delete this;
+
+ return lRef;
+}
diff --git a/Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.h b/Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.h
new file mode 100644
index 00000000..faee324a
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/JSAPI2_PodcastsAPI.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <ocidl.h>
+#include <atomic>
+
+#include "../Winamp/JSAPI_Info.h"
+
+namespace JSAPI2
+{
+ class PodcastsAPI : public IDispatch
+ {
+ public:
+ PodcastsAPI(const wchar_t *_key, JSAPI::ifc_info *info);
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+ private:
+ const wchar_t *key;
+ volatile std::atomic<std::size_t> _refCount = 1;
+ JSAPI::ifc_info *info;
+
+ STDMETHOD (Subscribe)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+
+ };
+}
diff --git a/Src/Plugins/Library/ml_wire/Loader.cpp b/Src/Plugins/Library/ml_wire/Loader.cpp
new file mode 100644
index 00000000..74c944b5
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Loader.cpp
@@ -0,0 +1,100 @@
+#include "main.h"
+#include "api.h"
+#include "../winamp/wa_ipc.h"
+#include "DownloadStatus.h"
+using namespace Nullsoft::Utility;
+static WNDPROC wa_oldWndProc=0;
+
+/* protocol must be all lower case */
+bool ProtocolMatch(const char *file, const char *protocol)
+{
+ size_t protSize = strlen(protocol);
+ for (size_t i=0;i!=protSize;i++)
+ {
+ if (!file[i]
+ || tolower(file[i]) != protocol[i])
+ return false;
+ }
+ return true;
+}
+
+LRESULT CALLBACK LoaderProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+#if 0 // not ready to take links yet... too buggy/weird at this point ...
+ if (uMsg == WM_COPYDATA)
+ {
+ COPYDATASTRUCT *copyData = (COPYDATASTRUCT *)lParam;
+ if (copyData->dwData == IPC_ENQUEUEFILE)
+ {
+ const char *file = (const char *)copyData->lpData;
+ if (ProtocolMatch(file, "feed://"))
+ {
+ Channel newFeed;
+ newFeed.url = AutoWide((const char *)copyData->lpData);
+ if (DownloadFeedInformation(newFeed)==DOWNLOADRSS_SUCCESS)
+ {
+ AutoLock lock(channels);
+ channels.push_back(newFeed);
+ }
+ return 0;
+ }
+ else
+ if (ProtocolMatch(file, "http://"))
+ {
+ // nothing for now, we want to do a head request tho
+ JNL_HTTPGet head;
+ head.connect(file, 0, "HEAD");
+ int ret;
+ do
+ {
+ ret = head.run();
+ Sleep(50);
+ } while (ret != -1 && ret != 1);
+
+ if (ret!=-1)
+ {
+ char *contentType = head.getheader("Content-Type");
+// if (contentType)
+ //MessageBoxA(NULL, contentType, contentType, MB_OK);
+ if (strstr(contentType, "application/rss+xml") == contentType)
+ {
+ MessageBox(NULL, L"woo!", L"woo!", MB_OK);
+ return 0;
+ }
+ if (strstr(contentType, "application/xml") == contentType)
+ {
+ MessageBox(NULL, L"regular xml", L"application/xml", MB_OK);
+ return 0;
+ }
+ if (strstr(contentType, "text/xml") == contentType)
+ {
+ MessageBox(NULL, L"regular xml", L"text/xml", MB_OK);
+ return 0;
+ }
+
+ }
+
+ }
+ }
+ }
+#endif
+ if (wa_oldWndProc)
+ return CallWindowProc(wa_oldWndProc, hwnd, uMsg, wParam, lParam);
+ else
+ return 0;
+}
+
+void BuildLoader(HWND winampWindow)
+{
+ if (IsWindowUnicode(winampWindow))
+ wa_oldWndProc=(WNDPROC) SetWindowLongPtrW(winampWindow,GWLP_WNDPROC,(LONG_PTR)LoaderProc);
+ else
+ wa_oldWndProc=(WNDPROC) SetWindowLongPtrA(winampWindow,GWLP_WNDPROC,(LONG_PTR)LoaderProc);
+}
+
+void DestroyLoader(HWND winampWindow)
+{
+ //if (wa_oldWndProc)
+ // SetWindowLong(winampWindow,GWL_WNDPROC,(LONG)wa_oldWndProc);
+ //wa_oldWndProc=0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Loader.h b/Src/Plugins/Library/ml_wire/Loader.h
new file mode 100644
index 00000000..e11d792f
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Loader.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_LOADERH
+#define NULLSOFT_LOADERH
+
+void BuildLoader(HWND winampWindow);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Main.cpp b/Src/Plugins/Library/ml_wire/Main.cpp
new file mode 100644
index 00000000..37e0ff6b
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Main.cpp
@@ -0,0 +1,469 @@
+#include "main.h"
+#include "Cloud.h"
+#include "DownloadThread.h"
+#include "OPMLParse.h"
+#include "FeedsDialog.h"
+#include "DownloadsParse.h"
+#include "XMLWriter.h"
+#include "FeedParse.h"
+#include "DownloadsDialog.h"
+#include "Preferences.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "Defaults.h"
+#include "Wire.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "RSSCOM.h"
+
+#include "api__ml_wire.h"
+#include "Downloaded.h"
+#include "DownloadStatus.h"
+#include "Factory.h"
+#include "JSAPI2_Creator.h"
+#include "./navigation.h"
+
+#include <strsafe.h>
+
+#include "PCastFactory.h"
+
+Cloud cloud;
+WireManager channelMgr;
+
+int treeId = 0, allId = 0
+#if 0
+ , discoverId = 0
+#endif
+ ;
+MLTREEITEMW downloadsTree;
+wchar_t downloadsStr[64] = {0}, *ml_cfg = 0,
+ feedsXmlFileName[1024] = {0},
+ feedsXmlFileNameBackup[1024] = {0},
+ rssXmlFileName[1024] = {0},
+ rssXmlFileNameBackup[1024] = {0};
+
+ATOM VIEWPROP = 0;
+
+api_downloadManager *WAC_API_DOWNLOADMANAGER = 0;
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+DWORD threadStorage=TLS_OUT_OF_INDEXES;
+extern "C" winampMediaLibraryPlugin plugin =
+ {
+ MLHDR_VER,
+ "nullsoft(ml_wire.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+ };
+
+static prefsDlgRecW preferences;
+
+static wchar_t preferencesName[64] = {0};
+void SaveChannels(ChannelList &channels)
+{
+ // generate a backup of the feeds.xml to cope with it being wiped randomly for some
+ // people or it not being able to be saved in time e.g. during a forced OS shutdown
+ CopyFile(feedsXmlFileName, feedsXmlFileNameBackup, FALSE);
+ SaveChannels(feedsXmlFileName, channels);
+}
+
+void SaveAll(bool rss_only)
+{
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->SetStat(api_stats::PODCAST_COUNT, (int)channels.size());
+
+ if(!rss_only)
+ SaveChannels(channels);
+
+ // generate a backup of the rss.xml to cope with it being wiped randomly for some
+ // people or it not being able to be saved in time e.g. during a forced OS shutdown
+ CopyFile(rssXmlFileName, rssXmlFileNameBackup, FALSE);
+ SaveSettings(rssXmlFileName, downloadedFiles);
+}
+
+static PodcastsFactory podcastsFactory;
+
+HANDLE hMainThread = NULL;
+
+HCURSOR hDragNDropCursor = NULL;
+int winampVersion = 0;
+
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = 0;
+JSAPI2Factory jsapi2Creator;
+
+obj_ombrowser *browserManager = NULL;
+api_application *applicationApi = NULL;
+api_stats *AGAVE_API_STATS = 0;
+api_threadpool *WASABI_API_THREADPOOL = 0;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+
+static PCastFactory pcastFactory;
+
+static void CALLBACK InitTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ KillTimer(hwnd, eventId);
+
+ {
+ Nullsoft::Utility::AutoLock lock (channels LOCKNAME("feeds.xml load!"));
+
+ FeedParse downloader(&channelMgr, true);
+ downloader.DownloadFile(feedsXmlFileName);
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->SetStat(api_stats::PODCAST_COUNT, (int)channels.size());
+ }
+
+ if (updateOnLaunch)
+ {
+ cloud.RefreshAll();
+ }
+
+ cloud.Init();
+ cloud.Pulse();
+}
+
+int Init()
+{
+ hMainThread = GetCurrentThread();
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+ threadStorage = TlsAlloc();
+
+ if ( 0 == VIEWPROP )
+ {
+ VIEWPROP = GlobalAddAtom( L"Nullsoft_PodcastView" );
+ if ( VIEWPROP == 0 )
+ return 1;
+ }
+
+ winampVersion = SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETVERSION );
+ ml_cfg = (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW);
+
+ plugin.service->service_register( &podcastsFactory );
+ plugin.service->service_register( &jsapi2Creator );
+ plugin.service->service_register( &pcastFactory );
+
+ // loader so that we can get the localisation service api for use
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( JSAPI2::api_securityGUID );
+ if ( sf )
+ AGAVE_API_JSAPI2_SECURITY = reinterpret_cast<JSAPI2::api_security*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( OBJ_OmBrowser );
+ if ( sf )
+ OMBROWSERMNGR = reinterpret_cast<obj_ombrowser*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( AnonymousStatsGUID );
+ if ( sf )
+ AGAVE_API_STATS = reinterpret_cast<api_stats*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( ThreadPoolGUID );
+ if ( sf )
+ WASABI_API_THREADPOOL = reinterpret_cast<api_threadpool*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( DownloadManagerGUID );
+ if ( sf )
+ WAC_API_DOWNLOADMANAGER = reinterpret_cast<api_downloadManager*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( ExplorerFindFileApiGUID );
+ if ( sf )
+ WASABI_API_EXPLORERFINDFILE = reinterpret_cast<api_explorerfindfile*>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlWireLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_PLUGIN_NAME ), PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR );
+ plugin.description = (char*)szDescription;
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ RssCOM *rss;
+ if (SUCCEEDED(RssCOM::CreateInstance(&rss)))
+ {
+ DispatchInfo dispatchInfo;
+ dispatchInfo.name = (LPWSTR)rss->GetName();
+ dispatchInfo.dispatch = rss;
+
+ SENDWAIPC(plugin.hwndWinampParent, IPC_ADD_DISPATCH_OBJECT, (WPARAM)&dispatchInfo);
+ rss->Release();
+ }
+
+ BuildDefaultDownloadPath( plugin.hwndWinampParent );
+
+ preferences.hInst = WASABI_API_LNG_HINST;
+ preferences.dlgID = IDD_PREFERENCES;
+ preferences.proc = (void *)PreferencesDialogProc;
+ preferences.name = WASABI_API_LNGSTRINGW_BUF( IDS_PODCAST_DIRECTORY, preferencesName, 64 );
+ preferences.where = 6;
+
+ mediaLibrary.AddPreferences( preferences );
+
+ wchar_t g_path[MAX_PATH] = {0};
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds", g_path, MAX_PATH );
+ CreateDirectoryW( g_path, NULL );
+
+ wchar_t oldxmlFileName[1024] = {0}, oldxmlFileNameBackup[ 1024 ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins\\ml\\rss.xml", oldxmlFileName, 1024 );
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\rss.xml", rssXmlFileName, 1024 );
+ mediaLibrary.BuildPath( L"Plugins\\ml\\rss.xml.backup", oldxmlFileNameBackup, 1024 );
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\rss.xml.backup", rssXmlFileNameBackup, 1024 );
+
+ if ( PathFileExists( oldxmlFileName ) && !PathFileExists(rssXmlFileName))
+ {
+ MoveFile( oldxmlFileName, rssXmlFileName );
+ MoveFile( oldxmlFileNameBackup, rssXmlFileNameBackup );
+ }
+
+ {
+ DownloadsParse downloader;
+ downloader.DownloadFile(rssXmlFileName);
+ }
+
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds.xml", oldxmlFileName, 1024 );
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\feeds.xml", feedsXmlFileName, 1024 );
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds.xml.backup", oldxmlFileNameBackup, 1024 );
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\feeds.xml.backup", feedsXmlFileNameBackup, 1024 );
+
+ if ( PathFileExists( oldxmlFileName ) && !PathFileExists( feedsXmlFileName ) )
+ {
+ MoveFile( oldxmlFileName, feedsXmlFileName );
+ MoveFile( oldxmlFileNameBackup, feedsXmlFileNameBackup );
+ }
+
+ Navigation_Initialize();
+ SetTimer( plugin.hwndLibraryParent, 0x498, 10, InitTimer );
+
+ return 0;
+}
+
+void Quit()
+{
+ // If there are still files downloading, cancel download to remove incomplete downloaded files
+ while ( downloadStatus.CurrentlyDownloading() )
+ {
+ Nullsoft::Utility::AutoLock lock( downloadStatus.statusLock );
+ DownloadToken dltoken = downloadStatus.downloads.begin()->first;
+ WAC_API_DOWNLOADMANAGER->CancelDownload( dltoken );
+ }
+
+ cloud.Quit();
+ CloseDatabase();
+
+ plugin.service->service_deregister( &podcastsFactory );
+ plugin.service->service_deregister( &jsapi2Creator );
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( OBJ_OmBrowser );
+ if ( sf != NULL )
+ sf->releaseInterface( OMBROWSERMNGR );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_APP);
+
+ sf = plugin.service->service_getServiceByGuid(AnonymousStatsGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(AGAVE_API_STATS);
+
+ sf = plugin.service->service_getServiceByGuid(ThreadPoolGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_THREADPOOL);
+
+ sf = plugin.service->service_getServiceByGuid(ExplorerFindFileApiGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_EXPLORERFINDFILE);
+
+ sf = plugin.service->service_getServiceByGuid(DownloadManagerGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WAC_API_DOWNLOADMANAGER);
+
+ if ( VIEWPROP != 0 )
+ {
+ GlobalDeleteAtom(VIEWPROP);
+ VIEWPROP = 0;
+ }
+}
+
+static INT_PTR Podcast_OnContextMenu( INT_PTR param1, HWND hHost, POINTS pts)
+{
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = Navigation_FindService( SERVICE_PODCAST, NULL, NULL);
+
+ HNAVITEM podcastItem = MLNavItem_GetChild( plugin.hwndLibraryParent, myItem);
+ HNAVITEM subscriptionItem = Navigation_FindService( SERVICE_SUBSCRIPTION, podcastItem, NULL);
+
+ if ( hItem != myItem && hItem != subscriptionItem )
+ return FALSE;
+
+ POINT pt;
+ POINTSTOPOINT( pt, pts );
+ if ( pt.x == -1 || pt.y == -1 )
+ {
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if ( MLNavItem_GetRect( plugin.hwndLibraryParent, &itemRect ) )
+ {
+ MapWindowPoints( hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2 );
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU hMenu = WASABI_API_LOADMENU( IDR_MENU1 );
+ int subMenuId = ( hItem == subscriptionItem )? 4 : 3;
+
+ HMENU subMenu = ( NULL != hMenu ) ? GetSubMenu( hMenu, subMenuId ) : NULL;
+ if ( subMenu != NULL )
+ {
+ INT r = Menu_TrackPopup( plugin.hwndLibraryParent, subMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hHost, NULL );
+
+ switch(r)
+ {
+ case ID_NAVIGATION_DIRECTORY:
+ MLNavItem_Select( plugin.hwndLibraryParent, myItem );
+ break;
+ case ID_NAVIGATION_PREFERENCES:
+ SENDWAIPC( plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &preferences );
+ break;
+ case ID_NAVIGATION_HELP:
+ SENDWAIPC( plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8112346487060-Podcast-Directory" );
+ break;
+ case ID_NAVIGATION_REFRESHALL:
+ cloud.RefreshAll();
+ cloud.Pulse();
+ break;
+ }
+ }
+
+ if ( hMenu != NULL )
+ DestroyMenu( hMenu );
+
+ return TRUE;
+}
+
+INT_PTR MessageProc( int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3 )
+{
+ INT_PTR result = 0;
+ if ( Navigation_ProcessMessage( msg, param1, param2, param3, &result ) != FALSE )
+ return result;
+
+ switch ( msg )
+ {
+ case ML_MSG_NOTOKTOQUIT:
+ {
+ if (downloadStatus.CurrentlyDownloading())
+ {
+ wchar_t titleStr[32] = {0};
+ if ( MessageBox( plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW( IDS_CANCEL_DOWNLOADS_AND_QUIT ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRM_QUIT, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) == IDNO )
+ return TRUE;
+ }
+
+ return 0;
+ }
+ case ML_MSG_CONFIG:
+ mediaLibrary.GoToPreferences(preferences._id);
+ return TRUE;
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ return Podcast_OnContextMenu( param1, (HWND)param2, MAKEPOINTS( param3 ) );
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = param1;
+ groupBtn = param2;
+ PostMessage( current_window, WM_APP + 104, param1, param2 );
+ return 0;
+ }
+
+ return FALSE;
+}
+
+
+#define TREE_IMAGE_LOCAL_PODCASTS 108
+
+void addToLibrary(const DownloadedFile& d)
+{
+ itemRecordW item = { 0 };
+
+ item.year = -1;
+ item.track = -1;
+ item.tracks = -1;
+ item.length = -1;
+ item.rating = -1;
+ item.lastplay = -1;
+ item.lastupd = -1;
+ item.filetime = -1;
+ item.filesize = -1;
+ item.bitrate = -1;
+ item.type = -1;
+ item.disc = -1;
+ item.discs = -1;
+ item.bpm = -1;
+ item.playcount = -1;
+ item.filename = _wcsdup( d.path );
+
+ setRecordExtendedItem(&item,L"ispodcast",L"1");
+ setRecordExtendedItem(&item,L"podcastchannel",d.channel);
+
+ wchar_t buf[40] = {0};
+ _i64tow(d.publishDate,buf,10);
+ if(d.publishDate)
+ setRecordExtendedItem(&item,L"podcastpubdate",buf);
+
+ LMDB_FILE_ADD_INFOW fai = {item.filename,-1,-1};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&fai, ML_IPC_DB_ADDORUPDATEFILEW);
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&item, ML_IPC_DB_UPDATEITEMW);
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_DB_SYNCDB);
+ freeRecord(&item);
+
+ if(needToMakePodcastsView) {
+ mlSmartViewInfo m = { sizeof( mlSmartViewInfo ),2,L"Podcasts", L"ispodcast = 1",461315,TREE_IMAGE_LOCAL_PODCASTS,0 };
+ WASABI_API_LNGSTRINGW_BUF( IDS_PODCASTS, m.smartViewName, 128 );
+ SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&m, ML_IPC_SMARTVIEW_ADD );
+ needToMakePodcastsView = false;
+ }
+}
+
+typedef struct
+{
+ const DownloadedFile *d;
+ volatile UINT done;
+} apc_addtolib_waiter;
+
+static VOID CALLBACK apc_addtolib(ULONG_PTR dwParam)
+{
+ apc_addtolib_waiter *w = (apc_addtolib_waiter *)dwParam;
+ addToLibrary(*w->d);
+ w->done=1;
+}
+
+void addToLibrary_thread(const DownloadedFile& d)
+{
+ apc_addtolib_waiter w = { &d, 0 };
+ if ( !QueueUserAPC( apc_addtolib, hMainThread, (ULONG_PTR)&w ) )
+ return;
+
+ while ( !w.done )
+ SleepEx( 5, true );
+}
+
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Main.h b/Src/Plugins/Library/ml_wire/Main.h
new file mode 100644
index 00000000..ea9d9a2d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Main.h
@@ -0,0 +1,69 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include "Wire.h"
+#include "Downloaded.h"
+
+#define PLUGIN_VERSION_MAJOR 1
+#define PLUGIN_VERSION_MINOR 80
+
+#define SERVICE_PODCAST 720
+#define SERVICE_SUBSCRIPTION 721
+#define SERVICE_DOWNLOADS 722
+
+#define BAD_CHANNEL ((size_t)-1)
+#define BAD_ITEM ((size_t)-1)
+
+void SaveChannels(ChannelList &channels);
+void SaveAll(bool rss_only=false);
+void HookTerminate();
+
+void DestroyLoader(HWND);
+void BuildLoader(HWND);
+extern int winampVersion;
+void addToLibrary(const DownloadedFile& d); // call in winamp main thread only
+void addToLibrary_thread(const DownloadedFile& d); // call from any thread
+
+bool AddPodcastData(const DownloadedFile &data);
+bool IsPodcastDownloaded(const wchar_t *url);
+void CloseDatabase();
+
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/AutoLock.h"
+#include "..\..\General\gen_ml/menu.h"
+#include <windows.h>
+#include <shlwapi.h>
+
+extern ATOM VIEWPROP;
+extern winampMediaLibraryPlugin plugin;
+
+#include "../Components/wac_downloadManager/wac_downloadManager_api.h"
+
+#define ML_ENQDEF_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"enqueuedef", 0, ml_cfg))
+#define ML_GROUPBTN_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"groupbtn", 1, ml_cfg))
+extern wchar_t* ml_cfg;
+wchar_t *urlencode(wchar_t *p);
+
+extern HWND current_window;
+extern int groupBtn, enqueuedef, customAllowed;
+extern viewButtons view;
+
+void SwapPlayEnqueueInMenu(HMENU listMenu);
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu);
+void Downloads_UpdateButtonText(HWND hwndDlg, int _enqueuedef);
+void listbuild(wchar_t **buf, int &buf_size, int &buf_pos, const wchar_t *tbuf);
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL Downloads_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0);
+void UpdateMenuItems(HWND hwndDlg, HMENU menu);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/MessageProcessor.cpp b/Src/Plugins/Library/ml_wire/MessageProcessor.cpp
new file mode 100644
index 00000000..6067965c
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/MessageProcessor.cpp
@@ -0,0 +1,7 @@
+#include "main.h"
+#include "MessageProcessor.h"
+
+#define CBCLASS MessageProcessor
+START_DISPATCH;
+CB(API_MESSAGEPROCESSOR_PROCESS_MESSAGE, ProcessMessage)
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/MessageProcessor.h b/Src/Plugins/Library/ml_wire/MessageProcessor.h
new file mode 100644
index 00000000..c48e4b8a
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/MessageProcessor.h
@@ -0,0 +1,35 @@
+#ifndef NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
+#define NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
+
+#include <api/application/api_messageprocessor.h>
+#include "main.h"
+#ifndef WM_FORWARDMSG
+#define WM_FORWARDMSG 0x037F
+#endif
+
+class MessageProcessor : public api_messageprocessor
+{
+public:
+ bool ProcessMessage(MSG *msg)
+ {
+
+ if (msg->message < WM_KEYFIRST || msg->message > WM_KEYLAST)
+ return false;
+
+ HWND hWndCtl = ::GetFocus();
+
+ if (IsChild(browserHWND, hWndCtl))
+ {
+ // find a direct child of the dialog from the window that has focus
+ while(::GetParent(hWndCtl) != browserHWND)
+ hWndCtl = ::GetParent(hWndCtl);
+
+ if (activeBrowser->translateKey(*msg))
+ return true;
+ }
+ return false;
+ }
+protected:
+ RECVS_DISPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/OPMLParse.cpp b/Src/Plugins/Library/ml_wire/OPMLParse.cpp
new file mode 100644
index 00000000..862ec36b
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/OPMLParse.cpp
@@ -0,0 +1,33 @@
+#if 0
+#include "main.h"
+#include "OPMLParse.h"
+
+#ifdef DEBUG
+#include <iostream>
+static void DisplayNodes(XMLNode &node)
+{
+ XMLNode::NodeMap::iterator nodeItr;
+ for (nodeItr = node.nodes.begin();nodeItr != node.nodes.end(); nodeItr++)
+ {
+
+ for (XMLNode::NodeList::iterator itr = nodeItr->second.begin(); itr != nodeItr->second.end(); itr++)
+ {
+ std::wcerr << L"<" << nodeItr->first << L">" << std::endl;
+ DisplayNodes(**itr);
+ std::wcerr << L"</" << nodeItr->first << L">" << std::endl;
+ }
+
+ }
+}
+#endif
+
+void OPMLParse::ReadNodes(const wchar_t *url)
+{
+// DisplayNodes(xmlNode);
+ Alias<XMLNode> curNode;
+ curNode = xmlNode.Get(L"opml");
+ if (!curNode)
+ return;
+
+}
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/OPMLParse.h b/Src/Plugins/Library/ml_wire/OPMLParse.h
new file mode 100644
index 00000000..5921ceef
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/OPMLParse.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_OPMLPARSEH
+#define NULLSOFT_OPMLPARSEH
+
+#include "DownloadThread.h"
+#include "ChannelSync.h"
+class OPMLParse : public DownloadThread
+{
+public:
+ OPMLParse(ChannelSync *_sync)
+ : sync(_sync)
+ {
+
+ }
+
+
+ ~OPMLParse()
+ {
+ sync = 0;
+ }
+
+ virtual void ReadNodes(const wchar_t *url);
+
+private:
+
+ ChannelSync *sync;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/PCastFactory.cpp b/Src/Plugins/Library/ml_wire/PCastFactory.cpp
new file mode 100644
index 00000000..48b22f65
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/PCastFactory.cpp
@@ -0,0 +1,60 @@
+#include "api__ml_wire.h"
+#include ".\pcastfactory.h"
+#include "Wire.h"
+
+static PCastURIHandler PCastUH;
+
+static const char serviceName[] = "PCast";
+
+FOURCC PCastFactory::GetServiceType()
+{
+ return svc_urihandler::getServiceType();
+}
+
+const char *PCastFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+void *PCastFactory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &PCastUH;
+}
+
+int PCastFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int PCastFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *PCastFactory::GetTestString()
+{
+ return NULL;
+}
+
+int PCastFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 0;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS PCastFactory
+START_DISPATCH;
+CB( WASERVICEFACTORY_GETSERVICETYPE, GetServiceType )
+CB( WASERVICEFACTORY_GETSERVICENAME, GetServiceName )
+CB( WASERVICEFACTORY_GETINTERFACE, GetInterface )
+CB( WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface )
+CB( WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface )
+CB( WASERVICEFACTORY_GETTESTSTRING, GetTestString )
+CB( WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify )
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_wire/PCastFactory.h b/Src/Plugins/Library/ml_wire/PCastFactory.h
new file mode 100644
index 00000000..0b6d1fdc
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/PCastFactory.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "api__ml_wire.h"
+
+#include "api/service/waservicefactory.h"
+#include "api/service/services.h"
+
+#include "PCastURIHandler.h"
+
+class PCastFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_wire/PCastURIHandler.cpp b/Src/Plugins/Library/ml_wire/PCastURIHandler.cpp
new file mode 100644
index 00000000..4b32c9c3
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/PCastURIHandler.cpp
@@ -0,0 +1,258 @@
+#include "main.h"
+#include "./pcasturihandler.h"
+#include "./Feeds.h"
+#include "./FeedUtil.h"
+#include "../nu/AutoLock.h"
+#include "./wire.h"
+#include "./errors.h"
+//#include "../Agave/URIHandler/svc_urihandler.h"
+//#include <api/service/waservicefactory.h>
+#include "api__ml_wire.h"
+#include "./cloud.h"
+#include "./SubscriptionView.h"
+#include "./resource.h"
+#include "navigation.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <strsafe.h>
+
+using namespace Nullsoft::Utility;
+
+extern ChannelList channels;
+extern Cloud cloud;
+
+
+static uint8_t quickhex(wchar_t c)
+{
+ int hexvalue = c;
+ if (hexvalue & 0x10)
+ hexvalue &= ~0x30;
+ else
+ {
+ hexvalue &= 0xF;
+ hexvalue += 9;
+ }
+ return hexvalue;
+}
+
+static uint8_t DecodeEscape(const wchar_t *&str)
+{
+ uint8_t a = quickhex(*++str);
+ uint8_t b = quickhex(*++str);
+ str++;
+ return a * 16 + b;
+}
+
+static void DecodeEscapedUTF8(wchar_t *&output, const wchar_t *&input)
+{
+ uint8_t utf8_data[1024] = {0}; // hopefully big enough!!
+ int num_utf8_words=0;
+ bool error=false;
+
+ while (input && *input == '%' && num_utf8_words < sizeof(utf8_data))
+ {
+ if (iswxdigit(input[1]) && iswxdigit(input[2]))
+ {
+ utf8_data[num_utf8_words++]=DecodeEscape(input);
+ }
+ else if (input[1] == '%')
+ {
+ input+=2;
+ utf8_data[num_utf8_words++]='%';
+ }
+ else
+ {
+ error = true;
+ break;
+ }
+ }
+
+ int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, 0, 0);
+ MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, output, len);
+ output += len;
+
+ if (error)
+ {
+ *output++ = *input++;
+ }
+}
+
+static void UrlDecode(const wchar_t *input, wchar_t *output, size_t len)
+{
+ const wchar_t *stop = output+len-4; // give ourself a cushion large enough to hold a full UTF-16 sequence
+ const wchar_t *itr = input;
+ while (itr && *itr)
+ {
+ if (output >= stop)
+ {
+ *output=0;
+ return;
+ }
+
+ switch (*itr)
+ {
+ case '%':
+ DecodeEscapedUTF8(output, itr);
+ break;
+ case '&':
+ *output = 0;
+ return;
+ default:
+ *output++ = *itr++;
+ break;
+ }
+ }
+ *output = 0;
+}
+// first parameter has param name either null or = terminated, second is null terminated
+static bool ParamCompare(const wchar_t *url_param, const wchar_t *param_name)
+{
+ while (url_param && *url_param && *param_name && *url_param!=L'=')
+ {
+ if (*url_param++ != *param_name++)
+ return false;
+ }
+ return true;
+}
+
+static bool get_request_parm(const wchar_t *params, const wchar_t *param_name, wchar_t *value, size_t value_len)
+{
+ size_t param_name_len = wcslen(param_name);
+ const wchar_t *t=params;
+ while (t && *t && *t != L'?') // find start of parameters
+ t++;
+
+ while (t && *t)
+ {
+ t++; // skip ? or &
+ if (ParamCompare(t, param_name))
+ {
+ while (t && *t && *t != L'=' && *t != '&') // find start of value
+ t++;
+ switch(*t)
+ {
+ case L'=':
+ UrlDecode(++t, value, value_len);
+ return true;
+ case 0:
+ case L'&': // no value
+ *value=0;
+ return true;
+ default: // shouldn't get here
+ return false;
+ }
+ }
+ while (t && *t && *t != L'&') // find next parameter
+ t++;
+ }
+
+ return false;
+}
+
+int PCastURIHandler::ProcessFilename(const wchar_t *filename)
+{
+ if (
+ (wcsnicmp(filename, L"pcast://", 8)) == 0 ||
+ (wcsnicmp(filename, L"feed://", 7) == 0) ||
+ (wcsnicmp(filename, L"winamp://Podcast/Subscribe", 26) == 0) ||
+ (wcsnicmp(filename, L"winamp://Podcast/Search", 23) == 0)
+ )
+ {
+ wchar_t *tempFilename = NULL;
+ wchar_t url[1024] = {0};
+ if (wcsnicmp(filename, L"winamp://Podcast/Subscribe", 26) == 0)
+ {
+ // extract/decode and use the url= parameter
+ if (get_request_parm(filename, L"url", url, 1024) && url[0])
+ {
+ tempFilename = wcsdup(url);
+ }
+ else
+ {
+ // did not find a url parameter
+ return NOT_HANDLED;
+ }
+ }
+ else if (wcsnicmp(filename, L"winamp://Podcast/Search", 23) == 0)
+ {
+// TODO: maybe: if (get_request_parm(filename, L"url", url, 1024) && url[0])
+ {
+ HNAVITEM hItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL);
+ MLNavItem_Select(plugin.hwndLibraryParent, hItem);
+ return HANDLED;
+ }
+ /*
+ else
+ {
+ // did not find a url parameter
+ return NOT_HANDLED;
+ }
+ */
+ }
+ else
+ {
+ // Use the full filename
+ tempFilename = wcsdup(filename);
+ }
+
+ // subscription confirmation
+ WCHAR szText[1024] = {0}, szBuffer[1024] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_SUBSCRIPTION_PROMP, szBuffer, ARRAYSIZE(szBuffer));
+ StringCchPrintf(szText, ARRAYSIZE(szText), szBuffer, tempFilename);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_SUBSCRIPTION_HEADER, szBuffer, ARRAYSIZE(szBuffer));
+
+ if (IDYES == MessageBox(plugin.hwndLibraryParent, szText, szBuffer, MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_SETFOREGROUND) )
+ {
+ // add feed to channels, pulse the cloud, refresh the UI pane.
+ Channel newFeed;
+ newFeed.SetURL(tempFilename);
+ if (DownloadFeedInformation(newFeed)==DOWNLOAD_SUCCESS)
+ {
+ channels.channelGuard.Lock();
+ channels.AddChannel(newFeed);
+ channels.channelGuard.Unlock();
+ cloud.Pulse();
+ HWND hView = SubscriptionView_FindWindow();
+ if (NULL != hView)
+ {
+ SubscriptionView_RefreshChannels(hView, TRUE);
+ }
+ else
+ {
+ HNAVITEM myItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL);
+ HNAVITEM podcastItem = MLNavItem_GetChild(plugin.hwndLibraryParent, myItem);
+ HNAVITEM subscriptionItem = Navigation_FindService(SERVICE_SUBSCRIPTION, podcastItem, NULL);
+ MLNavItem_Select(plugin.hwndLibraryParent, subscriptionItem);
+ }
+ }
+ free(tempFilename);
+
+ return HANDLED;
+
+ }
+ else
+ free(tempFilename);
+ }
+ return NOT_HANDLED;
+}
+
+int PCastURIHandler::IsMine(const wchar_t *filename)
+{
+ int i = 0;
+ if (
+ (wcsnicmp(filename, L"pcast://", 8)) == 0 ||
+ (wcsnicmp(filename, L"feed://", 7) == 0) ||
+ (wcsnicmp(filename, L"winamp://Podcast/Subscribe", 26) == 0) ||
+ (wcsnicmp(filename, L"winamp://Podcast/Search", 23) == 0)
+ )
+ return HANDLED;
+ else
+ return NOT_HANDLED;
+}
+
+#define CBCLASS PCastURIHandler
+START_DISPATCH;
+CB(PROCESSFILENAME, ProcessFilename);
+CB(ISMINE, IsMine);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/PCastURIHandler.h b/Src/Plugins/Library/ml_wire/PCastURIHandler.h
new file mode 100644
index 00000000..22bca933
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/PCastURIHandler.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../Agave/URIHandler/svc_urihandler.h"
+
+// {DA5F8547-5D53-49d9-B9EC-C59318D11798}
+static const GUID pcast_uri_handler_guid =
+{ 0xda5f8547, 0x5d53, 0x49d9, { 0xb9, 0xec, 0xc5, 0x93, 0x18, 0xd1, 0x17, 0x98 } };
+
+
+class PCastURIHandler :
+ public svc_urihandler
+{
+public:
+ static const char *getServiceName() { return "PCast URI Handler"; }
+ static GUID getServiceGuid() { return pcast_uri_handler_guid; }
+ int ProcessFilename(const wchar_t *filename);
+ int IsMine(const wchar_t *filename); // just like ProcessFilename but don't actually process
+
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_wire/ParseUtil.cpp b/Src/Plugins/Library/ml_wire/ParseUtil.cpp
new file mode 100644
index 00000000..0a5c8d57
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ParseUtil.cpp
@@ -0,0 +1,43 @@
+#include "ParseUtil.h"
+
+bool PropertyIsTrue( const XMLNode *item, const wchar_t *property )
+{
+ if ( !item )
+ return false;
+
+ const wchar_t *value = item->GetProperty( property );
+ if ( !value )
+ return false;
+
+ return !_wcsicmp( value, L"true" );
+}
+
+bool PropertyIsFalse( const XMLNode *item, const wchar_t *property )
+{
+ if ( !item )
+ return false;
+
+ const wchar_t *value = item->GetProperty( property );
+ if ( !value )
+ return false;
+
+ return !_wcsicmp( value, L"false" );
+}
+
+const wchar_t *GetContent( const XMLNode *item, const wchar_t *tag )
+{
+ const XMLNode *curNode = item->Get( tag );
+ if ( curNode )
+ return curNode->GetContent();
+ else
+ return 0;
+}
+
+const wchar_t *GetProperty( const XMLNode *item, const wchar_t *tag, const wchar_t *property )
+{
+ const XMLNode *curNode = item->Get( tag );
+ if ( curNode )
+ return curNode->GetProperty( property );
+ else
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ParseUtil.h b/Src/Plugins/Library/ml_wire/ParseUtil.h
new file mode 100644
index 00000000..a277d729
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ParseUtil.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../xml/XMLNode.h"
+#include <bfc/platform/types.h>
+bool PropertyIsTrue(const XMLNode *item, const wchar_t *property);
+// not necessarily the opposite of PropertyIsTrue (returns false when field is empty
+bool PropertyIsFalse(const XMLNode *item, const wchar_t *property);
+const wchar_t *GetContent(const XMLNode *item, const wchar_t *tag);
+const wchar_t *GetProperty(const XMLNode *item, const wchar_t *tag, const wchar_t *property); \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Preferences.cpp b/Src/Plugins/Library/ml_wire/Preferences.cpp
new file mode 100644
index 00000000..ec7a20eb
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Preferences.cpp
@@ -0,0 +1,160 @@
+#include "main.h"
+#include "api__ml_wire.h"
+#include "../winamp/wa_ipc.h"
+#include "Defaults.h"
+#include "UpdateTime.h"
+#include "UpdateAutoDownload.h"
+#include "./cloud.h"
+#include <shlobj.h>
+
+extern Cloud cloud;
+
+void Preferences_Init(HWND hwndDlg)
+{
+ WCHAR szBuffer[256] = {0};
+ for (int i = 0;i < Update::TIME_NUMENTRIES;i++)
+ {
+ const wchar_t *str = Update::GetTitle(i, szBuffer, ARRAYSIZE(szBuffer));
+ SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_ADDSTRING, 0, (LPARAM) str);
+ }
+ int selection = Update::GetSelection(updateTime, autoUpdate);
+ SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_SETCURSEL, selection, 0);
+
+ WCHAR adBuffer[256] = {0};
+ for (int i = 0;i < UpdateAutoDownload::AUTODOWNLOAD_NUMENTRIES;i++)
+ {
+ const wchar_t *str = UpdateAutoDownload::GetTitle(i, adBuffer, ARRAYSIZE(adBuffer));
+ SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_ADDSTRING, 0, (LPARAM) str);
+ }
+ selection = UpdateAutoDownload::GetSelection(autoDownloadEpisodes, autoDownload);
+ SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_SETCURSEL, selection, 0);
+
+ SetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath);
+ SetDlgItemText(hwndDlg, IDC_DIRECTORYURL, serviceUrl);
+ CheckDlgButton(hwndDlg, IDC_UPDATEONLAUNCH, updateOnLaunch?BST_CHECKED:BST_UNCHECKED);
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)defaultDownloadPath);
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+void Preferences_Browse(HWND hwndDlg)
+{
+ wchar_t folder[MAX_PATH] = {0};
+ BROWSEINFO browse = {0};
+ lstrcpyn(folder, defaultDownloadPath, MAX_PATH);
+ browse.hwndOwner = hwndDlg;
+ browse.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_FOLDER);
+ browse.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ browse.lpfn = BrowseCallbackProc;
+ LPITEMIDLIST itemList = SHBrowseForFolder(&browse);
+ if (itemList)
+ {
+ SHGetPathFromIDList(itemList, folder);
+ lstrcpyn(defaultDownloadPath, folder, MAX_PATH);
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DOWNLOADLOCATION), folder);
+ LPMALLOC malloc;
+ SHGetMalloc(&malloc);
+ malloc->Free(itemList);
+ }
+}
+
+void Preferences_UpdateOnLaunch( HWND hwndDlg )
+{
+ updateOnLaunch = ( IsDlgButtonChecked( hwndDlg, IDC_UPDATEONLAUNCH ) == BST_CHECKED );
+}
+
+void Preferences_UpdateList(HWND hwndDlg)
+{
+ LRESULT timeSelection;
+ timeSelection = SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_GETCURSEL, 0, 0);
+ if (timeSelection != CB_ERR)
+ {
+ autoUpdate = Update::GetAutoUpdate(timeSelection);
+ updateTime = Update::GetTime(timeSelection);
+ if ( autoUpdate )
+ cloud.Pulse(); // update the waitable timer
+ }
+}
+
+void Preferences_AutoDownloadList(HWND hwndDlg)
+{
+ LRESULT episodeSelection;
+ episodeSelection = SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_GETCURSEL, 0, 0);
+ if (episodeSelection != CB_ERR)
+ {
+ autoDownload = UpdateAutoDownload::GetAutoDownload(episodeSelection);
+ autoDownloadEpisodes = UpdateAutoDownload::GetAutoDownloadEpisodes(episodeSelection);
+ }
+}
+
+BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ Preferences_Init(hwndDlg);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BROWSE:
+ Preferences_Browse(hwndDlg);
+ break;
+ case IDC_UPDATELIST:
+ Preferences_UpdateList(hwndDlg);
+ break;
+ case IDC_AUTODOWNLOADLIST:
+ Preferences_AutoDownloadList(hwndDlg);
+ break;
+ case IDC_UPDATEONLAUNCH:
+ Preferences_UpdateOnLaunch(hwndDlg);
+ break;
+ case IDC_DOWNLOADLOCATION:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ GetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath, MAX_PATH);
+ if (!PathFileExists(defaultDownloadPath))
+ {
+ BuildDefaultDownloadPath(plugin.hwndWinampParent);
+ SetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath);
+ }
+ }
+ break;
+ case IDC_DIRECTORYURL:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ GetDlgItemText(hwndDlg, IDC_DIRECTORYURL, serviceUrl, ARRAYSIZE(serviceUrl));
+ }
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ // ensure we save the changed settings
+ SaveAll(true);
+ break;
+ }
+
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_wire/Preferences.h b/Src/Plugins/Library/ml_wire/Preferences.h
new file mode 100644
index 00000000..c0615aab
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Preferences.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_PREFERENCESH
+#define NULLSOFT_PREFERENCESH
+
+BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+#endif
diff --git a/Src/Plugins/Library/ml_wire/RFCDate.cpp b/Src/Plugins/Library/ml_wire/RFCDate.cpp
new file mode 100644
index 00000000..ab57f9af
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/RFCDate.cpp
@@ -0,0 +1,216 @@
+#include "main.h"
+#include "RFCDate.h"
+#include <strsafe.h>
+
+void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, date_str, (int)len);
+
+}
+
+void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _gmtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ wchar_t rfcDate[64] = {0}, rfcTime[64] = {0};
+ GetDateFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"ddd',' d MMM yyyy ", rfcDate, 64);
+ GetTimeFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"HH':'mm':'ss 'GMT'", rfcTime, 64);
+ StringCchPrintf(data_str,len,L"%s%s",rfcDate,rfcTime);
+}
+
+static int getMonthOfYear(const wchar_t *str);
+static int validateTime(struct tm *tmloc, int gmoffset);
+static const wchar_t *getNextField(const wchar_t *pos);
+static int getGMOffset(const wchar_t *str);
+
+enum
+{
+ DAY_OF_MONTH = 0,
+ MONTH_OF_YEAR,
+ YEAR,
+ TIME,
+ TIMEZONE,
+ DATE_END
+};
+
+__time64_t MakeRFCDate(const wchar_t *date)
+{
+ __time64_t result = 0;
+ const wchar_t *pos = date;
+ tm tmloc = {0};
+ const wchar_t *strmin;
+ const wchar_t *strsec;
+ int gmoffset = 1;
+ int state = DAY_OF_MONTH;
+ tzset();
+ /* skip weekday if present */
+ while (pos && *pos && !iswdigit(*pos)) pos++;
+
+ do
+ {
+ switch (state)
+ {
+ case DAY_OF_MONTH:
+ tmloc.tm_mday = _wtoi(pos);
+ break;
+ case MONTH_OF_YEAR:
+ tmloc.tm_mon = getMonthOfYear(pos);
+ break;
+ case YEAR:
+ {
+ /* TODO: we're only accepting 4-digit dates...*/
+ const wchar_t *test = pos; int numDigits = 0;
+ while (iswdigit(*test) && *test) { test++; numDigits++; }
+ if (numDigits == 2) // let's hope we never have 2 digit years!
+ tmloc.tm_year = _wtoi(pos) + 100; // assume 2 digit years are 20xx
+ else
+ tmloc.tm_year = _wtoi(pos) - 1900;
+ }
+ break;
+ case TIME:
+ strmin = wcschr(pos, L':');
+ strsec = strmin ? wcschr(strmin + 1, L':') : 0;
+
+ tmloc.tm_hour = _wtoi(pos);
+ if (!strmin) return _time64(0);
+ tmloc.tm_min = _wtoi(strmin + 1);
+ if (!strsec) return _time64(0);
+ tmloc.tm_sec = _wtoi(strsec + 1);
+ break;
+ case TIMEZONE:
+ gmoffset = getGMOffset(pos);
+ break;
+ case DATE_END:
+ pos = 0;
+ break;
+ }
+
+ state++;
+ }
+ while ((pos = getNextField(pos)));
+
+ tmloc.tm_isdst = 0; //_daylight;
+
+ if (validateTime(&tmloc, gmoffset))
+ {
+ result = _mktime64(&tmloc) - _timezone;
+ //if (_daylight)
+ }
+
+ return result;
+}
+
+const wchar_t *getNextField(const wchar_t *pos)
+{
+ if (!pos)
+ return 0;
+ while (pos && *pos && !iswspace(*pos)) pos++;
+ while (pos && *pos && iswspace(*pos)) pos++;
+
+ return ((pos && *pos) ? pos : 0);
+}
+
+int validateTime(struct tm *tmloc, int gmoffset)
+{
+ int result = 1;
+
+ if (tmloc->tm_mday < 1 || tmloc->tm_mday > 31 ||
+ tmloc->tm_mon < 0 || tmloc->tm_mon > 11 ||
+ tmloc->tm_year < 0 || tmloc->tm_year > 2000 ||
+ tmloc->tm_hour < 0 || tmloc->tm_hour > 23 ||
+ tmloc->tm_min < 0 || tmloc->tm_min > 59 ||
+ tmloc->tm_sec < 0 || tmloc->tm_sec > 59 ||
+ gmoffset == 1)
+ result = 0;
+
+ return result;
+}
+
+int getMonthOfYear(const wchar_t *str)
+{
+ int mon = -1;
+ /* This is not the most efficient way to determine
+ the month (we could use integer comparisons, for instance)
+ but I don't think this will be a performance bottleneck :)
+ */
+
+ if (!wcsnicmp(str, L"Jan", 3))
+ mon = 0;
+ else if (!wcsnicmp(str, L"Feb", 3))
+ mon = 1;
+ else if (!wcsnicmp(str, L"Mar", 3))
+ mon = 2;
+ else if (!wcsnicmp(str, L"Apr", 3))
+ mon = 3;
+ else if (!wcsnicmp(str, L"May", 3))
+ mon = 4;
+ else if (!wcsnicmp(str, L"Jun", 3))
+ mon = 5;
+ else if (!wcsnicmp(str, L"Jul", 3))
+ mon = 6;
+ else if (!wcsnicmp(str, L"Aug", 3))
+ mon = 7;
+ else if (!wcsnicmp(str, L"Sep", 3))
+ mon = 8;
+ else if (!wcsnicmp(str, L"Oct", 3))
+ mon = 9;
+ else if (!wcsnicmp(str, L"Nov", 3))
+ mon = 10;
+ else if (!wcsnicmp(str, L"Dec", 3))
+ mon = 11;
+
+ return mon;
+}
+
+int getGMOffset(const wchar_t *str)
+{
+ int secs = 0;
+ /* See note in getMonthOfYear() */
+
+ if (!wcsnicmp(str, L"UT", 2) || !wcsnicmp(str, L"GMT", 3))
+ secs = 0;
+ else if (!wcsnicmp(str, L"EDT", 3))
+ secs = -4 * 3600;
+ else if (!wcsnicmp(str, L"PST", 3))
+ secs = -8 * 3600;
+ else if (!wcsnicmp(str, L"EST", 3) || !wcsnicmp(str, L"CDT", 3))
+ secs = -5 * 3600;
+ else if (!wcsnicmp(str, L"MST", 3) || !wcsnicmp(str, L"PDT", 3))
+ secs = -7 * 3600;
+ else if (!wcsnicmp(str, L"CST", 3) || !wcsnicmp(str, L"MDT", 3))
+ secs = -6 * 3600;
+ else if ( (str[0] == L'+' || str[0] == L'-') &&
+ iswdigit(str[1]) && iswdigit(str[2]) &&
+ iswdigit(str[3]) && iswdigit(str[4]))
+ {
+ secs = 3600 * (10 * (str[1] - 48) + str[2] - 48);
+ secs += 60 * (10 * (str[3] - 48) + str[4] - 48);
+
+ if (str[0] == L'-')
+ secs = -secs;
+ }
+ else
+ secs = 1;
+
+ return secs;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/RFCDate.h b/Src/Plugins/Library/ml_wire/RFCDate.h
new file mode 100644
index 00000000..7606272f
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/RFCDate.h
@@ -0,0 +1,7 @@
+#ifndef NULLSOFT_RFCDATEH
+#define NULLSOFT_RFCDATEH
+
+void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len);
+__time64_t MakeRFCDate(const wchar_t *date);
+void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/RSSCOM.cpp b/Src/Plugins/Library/ml_wire/RSSCOM.cpp
new file mode 100644
index 00000000..2db9a2bf
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/RSSCOM.cpp
@@ -0,0 +1,232 @@
+#include "main.h"
+#include "./rssCOM.h"
+#include "./util.h"
+#include "api__ml_wire.h"
+#include "./cloud.h"
+#include "./feedUtil.h"
+#include "./defaults.h"
+#include "./errors.h"
+
+#include "../winamp/jsapi.h"
+
+#include "./rssCOM.h"
+
+#include <strsafe.h>
+
+extern Cloud cloud;
+
+
+#define DISPTABLE_CLASS RssCOM
+
+DISPTABLE_BEGIN()
+ DISPENTRY_ADD(DISPATCH_SUBSCRIBE, L"subscribe", OnSubscribe)
+DISPTABLE_END
+
+#undef DISPTABLE_CLASS
+
+
+
+RssCOM::RssCOM()
+{}
+
+RssCOM::~RssCOM()
+{}
+
+HRESULT RssCOM::CreateInstance(RssCOM **instance)
+{
+ if (NULL == instance) return E_POINTER;
+
+ *instance = new RssCOM();
+ if (NULL == *instance) return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) RssCOM::AddRef(void)
+{
+ return _ref.fetch_add( 1 );
+}
+
+STDMETHODIMP_(ULONG) RssCOM::Release(void)
+{
+ if (0 == _ref.load() )
+ return _ref.load();
+
+ LONG r = _ref.fetch_sub( 1 );
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+STDMETHODIMP RssCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (NULL == ppvObject) return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = static_cast<IDispatch*>(this);
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = static_cast<IUnknown*>(this);
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT RssCOM::OnSubscribe(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+
+ BSTR url;
+
+ JSAPI_GETSTRING(url, pdispparams, 1, puArgErr);
+
+ SubscribeUrl(url, pvarResult);
+
+ return S_OK;
+}
+
+struct SubscribeThreadData
+{
+ LPWSTR url;
+};
+static LPCWSTR FormatLangString(LPWSTR pszBuffer, INT cchBufferMax, LPWSTR pszFormat, INT cchFormatMax, INT formatId, ...)
+{
+ HRESULT hr;
+ va_list argList;
+ va_start(argList, formatId);
+
+ if(NULL != WASABI_API_LNGSTRINGW_BUF(formatId, pszFormat, cchFormatMax))
+ {
+ hr = StringCchVPrintfExW(pszBuffer, cchBufferMax, NULL, NULL, STRSAFE_IGNORE_NULLS, pszFormat, argList);
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ va_end(argList);
+
+ return (SUCCEEDED(hr)) ? pszBuffer : NULL;
+}
+
+static DWORD WINAPI SubscribeThreadProc(void *param)
+{
+ SubscribeThreadData *data = (SubscribeThreadData *)param;
+ Channel newFeed;
+ newFeed.updateTime = updateTime;
+ newFeed.autoUpdate = autoUpdate;
+ newFeed.autoDownload = autoDownload;
+ newFeed.autoDownloadEpisodes = autoDownloadEpisodes;
+ newFeed.SetURL(data->url);
+ newFeed.needsRefresh = true;
+
+ WCHAR szBuffer1[2048] = {0}, szBuffer2[2048] = {0};
+ LPCWSTR pszMessage = NULL, pszTitle = NULL;
+
+ switch (DownloadFeedInformation(newFeed))
+ {
+ case DOWNLOAD_SUCCESS:
+ {
+ Nullsoft::Utility::AutoLock lock (channels LOCKNAME("AddURL"));
+ newFeed.autoDownload = ::autoDownload;
+ if (channels.AddChannel(newFeed))
+ cloud.Pulse();
+ }
+ break;
+
+ case DOWNLOAD_DUPLICATE:
+ pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_ALREADY_SUBSCRIBED, newFeed.title, data->url);
+ break;
+
+ case DOWNLOAD_404:
+ pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_FILE_NOT_FOUND, data->url);
+ break;
+
+ case DOWNLOAD_TIMEOUT:
+ pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_CONNECTION_TIMED_OUT, data->url);
+ break;
+
+ case DOWNLOAD_ERROR_PARSING_XML:
+ pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_ERROR_PARSING_XML, data->url);
+ break;
+
+ case DOWNLOAD_NOTRSS:
+ pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_INVALID_RSS_FEED, data->url);
+ break;
+
+ case DOWNLOAD_CONNECTIONRESET:
+ pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_CONNECTION_RESET, data->url);
+ break;
+
+ case DOWNLOAD_NOHTTP:
+ pszMessage = WASABI_API_LNGSTRINGW_BUF(IDS_NO_JNETLIB, szBuffer1, ARRAYSIZE(szBuffer1));
+ pszTitle = WASABI_API_LNGSTRINGW_BUF(IDS_JNETLIB_MISSING, szBuffer2, ARRAYSIZE(szBuffer2));
+ break;
+
+ case DOWNLOAD_NOPARSER:
+ pszMessage = WASABI_API_LNGSTRINGW_BUF(IDS_NO_EXPAT, szBuffer1, ARRAYSIZE(szBuffer1));
+ pszTitle = WASABI_API_LNGSTRINGW_BUF(IDS_EXPAT_MISSING, szBuffer2, ARRAYSIZE(szBuffer2));
+ break;
+
+
+ }
+
+ if(NULL != pszMessage)
+ {
+ if (NULL == pszTitle)
+ pszTitle = WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SUBSCRIBING_TO_PODCAST, szBuffer2, ARRAYSIZE(szBuffer2));
+
+ MessageBox(plugin.hwndLibraryParent, pszMessage, pszTitle, MB_ICONERROR | MB_OK);
+ }
+
+ Plugin_FreeString(data->url);
+ free(data);
+ return 0;
+}
+
+LPCWSTR RssCOM::GetName()
+{
+ return L"Podcast";
+}
+
+HRESULT RssCOM::SubscribeUrl(BSTR url, VARIANT FAR *result)
+{
+ HRESULT hr;
+ SubscribeThreadData *data = (SubscribeThreadData*)malloc(sizeof(SubscribeThreadData));
+ if (NULL != data)
+ {
+ data->url = Plugin_CopyString(url);
+ DWORD threadId;
+ HANDLE hThread = CreateThread(NULL, NULL, SubscribeThreadProc, (void*)data, NULL, &threadId);
+ if (NULL == hThread)
+ {
+ DWORD error = GetLastError();
+ hr = HRESULT_FROM_WIN32(error);
+ Plugin_FreeString(data->url);
+ free(data);
+ }
+ else
+ {
+ CloseHandle(hThread);
+ hr = S_OK;
+ }
+ }
+ else
+ hr = E_OUTOFMEMORY;
+
+ if (NULL != result)
+ {
+ VariantInit(result);
+ V_VT(result) = VT_BOOL;
+ V_BOOL(result) = (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE);
+ }
+
+ return hr;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/RSSCOM.h b/Src/Plugins/Library/ml_wire/RSSCOM.h
new file mode 100644
index 00000000..39d0e58b
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/RSSCOM.h
@@ -0,0 +1,42 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_RSS_COM_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_RSS_COM_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <atomic>
+
+#include "../nu/dispatchTable.h"
+
+class RssCOM : public IDispatch
+{
+public:
+ typedef enum
+ {
+ DISPATCH_SUBSCRIBE = 0,
+ } DispatchCodes;
+
+protected:
+ RssCOM();
+ ~RssCOM();
+
+public:
+ static HRESULT CreateInstance(RssCOM **instance);
+ static HRESULT SubscribeUrl(BSTR url, VARIANT FAR *result);
+ static LPCWSTR GetName();
+
+ /* IUnknown*/
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+protected:
+ DISPTABLE_INCLUDE();
+ DISPHANDLER_REGISTER(OnSubscribe);
+
+ std::atomic<std::size_t> _ref = 1;
+};
+
+#endif //NULLSOFT_PODCAST_PLUGIN_RSS_COM_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/RSSParse.cpp b/Src/Plugins/Library/ml_wire/RSSParse.cpp
new file mode 100644
index 00000000..bf2d2d3e
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/RSSParse.cpp
@@ -0,0 +1,181 @@
+#include "Main.h"
+#include "RSSParse.h"
+
+#include "RFCDate.h"
+#include "../xml/XMLNode.h"
+#include "Feeds.h"
+#include "Defaults.h"
+#include "ChannelSync.h"
+#include "ParseUtil.h"
+#include <strsafe.h>
+
+static void ReadWinampSpecificItem(const XMLNode *item, RSS::Item &newItem)
+{
+ newItem.listened = PropertyIsTrue(item, L"winamp:listened");
+ newItem.downloaded = PropertyIsTrue(item, L"winamp:downloaded");
+}
+
+static void ReadRSSItem(XMLNode *item, Channel &channel, bool doWinampSpecificTags)
+{
+ RSS::MutableItem newItem;
+ if (doWinampSpecificTags)
+ ReadWinampSpecificItem(item, newItem);
+
+ const wchar_t *pubdate = GetContent(item, L"pubDate");
+ if (pubdate && pubdate[0])
+ {
+ newItem.publishDate = MakeRFCDate(pubdate);
+ if (newItem.publishDate <= 0)
+ {
+ newItem.publishDate = _time64(0);
+ newItem.generatedDate = true;
+ }
+ else
+ newItem.generatedDate = false;
+ }
+ else
+ {
+ newItem.publishDate = _time64(0);
+ newItem.generatedDate = true;
+ }
+
+ const wchar_t *itemName = GetContent(item, L"title");
+ if (itemName && itemName[0])
+ newItem.SetItemName(itemName);
+ else
+ {
+ wchar_t date_str[128] = {0};
+ MakeRFCDateString(newItem.publishDate, date_str, 128);
+ newItem.SetItemName(date_str);
+ }
+
+ newItem.SetLink(GetContent(item, L"link"));
+ newItem.SetURL(GetProperty(item, L"enclosure", L"url"));
+ const wchar_t* src = GetProperty(item, L"source", L"src");
+ if (!src || !src[0])
+ {
+ newItem.SetSourceURL(GetProperty(item, L"source", L"url"));
+ }
+ else
+ {
+ newItem.SetSourceURL(src/*GetProperty(item, L"source", L"src")*/);
+ }
+ newItem.SetSize(GetProperty(item, L"enclosure", L"length"));
+
+ const wchar_t *guid = GetContent(item, L"guid");
+ if (!guid || !guid[0])
+ guid = GetProperty(item, L"enclosure", L"url");
+
+ if (guid && guid[0])
+ {
+ newItem.SetGUID(guid);
+ }
+ else
+ {
+ wchar_t generated_guid[160] = {0};
+ StringCbPrintf(generated_guid, sizeof(generated_guid), L"%s%s", channel.title, newItem.itemName);
+ newItem.SetGUID(generated_guid);
+ }
+
+ const wchar_t *description = GetContent(item, L"description");
+ if (!description || !description[0])
+ description = GetContent(item, L"content:encoded");
+ newItem.SetDescription(description);
+
+ const wchar_t *duration = GetContent(item, L"itunes:duration");
+ if (duration && duration[0])
+ newItem.SetDuration(duration);
+
+ if (newItem.itemName && newItem.itemName[0])
+ {
+ channel.items.push_back(newItem);
+ }
+}
+
+void ReadWinampSpecificChannel(const XMLNode *node, Channel &newChannel)
+{
+ const XMLNode *curNode = 0;
+ const wchar_t *lastupdate = node->GetProperty(L"winamp:lastupdate");
+ if (lastupdate && lastupdate[0])
+ newChannel.lastUpdate = MakeRFCDate(lastupdate);
+
+ const wchar_t *winamp_url = GetContent(node, L"winamp:url");
+ newChannel.SetURL(winamp_url);
+
+ // set to preference value first
+ newChannel.updateTime = updateTime;
+ newChannel.autoUpdate = autoUpdate;
+ newChannel.autoDownload = autoDownload;
+ newChannel.autoDownloadEpisodes = autoDownloadEpisodes;
+
+ curNode = node->Get(L"winamp:update");
+ if (curNode)
+ {
+ newChannel.useDefaultUpdate = PropertyIsTrue(curNode, L"usedefaultupdate");
+ newChannel.needsRefresh = PropertyIsTrue(curNode, L"needsrefresh");
+ if (!newChannel.useDefaultUpdate)
+ {
+ newChannel.updateTime = _wtoi64(curNode->GetProperty(L"updatetime"));
+ newChannel.autoUpdate = PropertyIsTrue(curNode, L"autoupdate");
+ }
+ }
+
+ curNode = node->Get(L"winamp:download");
+ if (curNode)
+ {
+ newChannel.autoDownload = PropertyIsTrue(curNode, L"autodownload");
+ if (newChannel.autoDownload)
+ {
+ const wchar_t *prop = curNode->GetProperty(L"autoDownloadEpisodes");
+ if (prop)
+ newChannel.autoDownloadEpisodes = _wtoi(prop);
+ }
+ }
+}
+
+static void ReadRSSChannel(const XMLNode *node, Channel &newChannel, bool doWinampSpecificTags)
+{
+ XMLNode::NodeList::const_iterator itemItr;
+ if (doWinampSpecificTags)
+ ReadWinampSpecificChannel(node, newChannel);
+
+ const wchar_t *title = GetContent(node, L"title");
+ newChannel.SetTitle(title);
+
+ const wchar_t *link = GetContent(node, L"link");
+ newChannel.SetLink(link);
+
+ const wchar_t *description = GetContent(node, L"description");
+ newChannel.SetDescription(description);
+
+ const wchar_t *ttl = GetContent(node, L"ttl");
+ if (ttl)
+ newChannel.ttl = _wtoi(ttl);
+
+ const XMLNode::NodeList *items = node->GetList(L"item");
+ if (items)
+ {
+ for (itemItr = items->begin(); itemItr != items->end(); itemItr++)
+ ReadRSSItem(*itemItr, newChannel, doWinampSpecificTags);
+ }
+}
+
+void ReadRSS(const XMLNode *rss, ChannelSync *sync, bool doWinampSpecificTags, const wchar_t *url)
+{
+ XMLNode::NodeList::const_iterator itr;
+
+ sync->BeginChannelSync();
+ const XMLNode::NodeList *channelList = rss->GetList(L"channel");
+ if (channelList)
+ {
+ for (itr = channelList->begin(); itr != channelList->end(); itr ++)
+ {
+ Channel newChannel;
+ ReadRSSChannel(*itr, newChannel, doWinampSpecificTags);
+ if (!newChannel.url || !newChannel.url[0])
+ newChannel.SetURL(url);
+ sync->NewChannel(newChannel);
+ }
+ }
+ sync->EndChannelSync();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/RSSParse.h b/Src/Plugins/Library/ml_wire/RSSParse.h
new file mode 100644
index 00000000..9cdf6c79
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/RSSParse.h
@@ -0,0 +1,9 @@
+#ifndef NULLSOFT_RSSPARSEH
+#define NULLSOFT_RSSPARSEH
+
+#include "../xml/XMLNode.h"
+#include "ChannelSync.h"
+
+void ReadRSS(const XMLNode *rss, ChannelSync *sync, bool doWinampSpecificTags, const wchar_t *url);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/TODO.txt b/Src/Plugins/Library/ml_wire/TODO.txt
new file mode 100644
index 00000000..db9d5965
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/TODO.txt
@@ -0,0 +1,51 @@
+to fix
+----
+crash on shutdown
+
+
+/////------------ 1.1 below --------------
+automatically switch to 'custom' if you click on dropdown in "add" or "edit" url dialog
+
+need icon for listened media
+maybe one for read text?
+
+strip whitespace from beginning of titles
+
+multiple-select
+
+Allow for customizing the download location in add/edit url
+
+deletable items (needs to move to a separate 'deleted items' list so we don't re-add them next rss refresh)
+
+drag-n-drop from webpages
+
+once we get an HTTP 200, we should put the downloaded on the 'downloads' list, and be able to update the download percentage status as necessary
+
+BACKGROUND DOWNLOADER
+<<<
+avoid multiple downloads of the same thing
+avoid downlaoding things that have already been downloaded.
+range / if-range to handle download resuming
+save the last modified dates from "Last-Modified" header
+save unfinished downloads to an XML file and read on load
+>>>
+
+UNIFIED DOWNLOAD MANAGER CONCEPT !!!!!
+
+who needs updates
+downloaded file list
+downloads page (to refresh view)
+item object
+podcast page (to refresh view)
+
+
+new way of listing items
+---
+create a common "items" data structure that select channels add their items to.
+When a channel is select, it adds its items.
+When a channel is deselected, it removes its items.
+When a channel is refreshed, it re-adds its items (assuming the item-adder function protects against dupes)
+only potential issue is if a channel somehow "loses" items (or an item's GUID is changes)
+could be fixed by either 1) keeps track of "parent channel" in the items list
+2) rebuilding the entire items list on every channel refresh
+or 3) preventing GUID changes and item deletions (or forcing an item list rebuild if it does occur)
diff --git a/Src/Plugins/Library/ml_wire/UpdateAutoDownload.cpp b/Src/Plugins/Library/ml_wire/UpdateAutoDownload.cpp
new file mode 100644
index 00000000..521a0b83
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/UpdateAutoDownload.cpp
@@ -0,0 +1,57 @@
+#include "main.h"
+#include "api__ml_wire.h"
+#include "UpdateAutoDownload.h"
+
+int UpdateAutoDownload::episodes[] = {0, // AUTODOWNLOAD_NEVER
+ 1, // AUTODOWNLOAD_LAST_ONE
+ 2, // AUTODOWNLOAD_LAST_TWO
+ 3, // AUTODOWNLOAD_LAST_THREE
+ 5, // AUTODOWNLOAD_LAST_FIVE
+};
+
+const wchar_t *UpdateAutoDownload::GetTitle(int position, wchar_t *buffer, int bufferMax)
+{
+ if (NULL == buffer)
+ return NULL;
+
+ INT stringId = IDS_ERROR_FYEO;
+ switch (position)
+ {
+ case AUTODOWNLOAD_NEVER: stringId = IDS_ATD_NEVER; break;
+ case AUTODOWNLOAD_LAST_ONE: stringId = IDS_ATD_LASTONE; break;
+ case AUTODOWNLOAD_LAST_TWO: stringId = IDS_ATD_LASTTWO; break;
+ case AUTODOWNLOAD_LAST_THREE: stringId = IDS_ATD_LASTTHREE; break;
+ case AUTODOWNLOAD_LAST_FIVE: stringId = IDS_ATD_LASTFIVE; break;
+ }
+ return WASABI_API_LNGSTRINGW_BUF(stringId, buffer, bufferMax);
+}
+
+
+bool UpdateAutoDownload::GetAutoDownload(int selection)
+{
+ if (selection == AUTODOWNLOAD_NEVER)
+ return false;
+ else
+ return true;
+}
+
+int UpdateAutoDownload::GetAutoDownloadEpisodes(int selection)
+{
+ if (selection >= 0 && selection < AUTODOWNLOAD_NUMENTRIES)
+ return episodes[selection];
+ else
+ return 0;
+}
+
+int UpdateAutoDownload::GetSelection(int selEpisodes, bool autoDownload)
+{
+ if (!autoDownload)
+ return AUTODOWNLOAD_NEVER;
+
+ for (int i = AUTODOWNLOAD_LAST_ONE;i < AUTODOWNLOAD_NUMENTRIES;i++)
+ if (selEpisodes == episodes[i])
+ return i;
+
+ return AUTODOWNLOAD_LAST_ONE;
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/UpdateAutoDownload.h b/Src/Plugins/Library/ml_wire/UpdateAutoDownload.h
new file mode 100644
index 00000000..61ea9efc
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/UpdateAutoDownload.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_UPDATEAUTODOWNLOADH
+#define NULLSOFT_UPDATEAUTODOWNLOADH
+
+/* This header file is used by FeedsDialog.h
+It provides a set of helper functions to deal with the combo box for auto download
+
+basically - converts between combo box choice and int
+*/
+namespace UpdateAutoDownload
+{
+ enum
+ {
+ AUTODOWNLOAD_NEVER = 0,
+ AUTODOWNLOAD_LAST_ONE,
+ AUTODOWNLOAD_LAST_TWO,
+ AUTODOWNLOAD_LAST_THREE,
+ AUTODOWNLOAD_LAST_FIVE,
+ AUTODOWNLOAD_NUMENTRIES
+ };
+
+ extern int episodes[];
+
+ const wchar_t *GetTitle(int position, wchar_t *buffer, int bufferMax);
+ bool GetAutoDownload(int selection);
+ int GetAutoDownloadEpisodes(int selection);
+ int GetSelection(int selEpisodes, bool autoDownload);
+}
+#endif
diff --git a/Src/Plugins/Library/ml_wire/UpdateTime.cpp b/Src/Plugins/Library/ml_wire/UpdateTime.cpp
new file mode 100644
index 00000000..008a6118
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/UpdateTime.cpp
@@ -0,0 +1,62 @@
+#include "main.h"
+#include "api__ml_wire.h"
+#include "UpdateTime.h"
+
+__time64_t Update::times[] = {0, // TIME_MANUALLY
+60 /* 1 minute */ * 60 /* 1 hour */ * 24 /* 1 day */ * 7 /* 1 week */, // TIME_WEEKLY
+60 /* 1 minute */ * 60 /* 1 hour */ * 24 /* 1 day */, // TIME_DAILY
+60 /* 1 minute */ * 60 /* 1 hour */, // TIME_HOURLY
+};
+
+const wchar_t *Update::GetTitle( int position, wchar_t *buffer, int bufferMax )
+{
+ if ( NULL == buffer )
+ return NULL;
+
+ INT stringId = IDS_ERROR_FYEO;
+ switch ( position )
+ {
+ case TIME_MANUALLY:
+ stringId = IDS_UPD_MANUALLY;
+ break;
+ case TIME_WEEKLY:
+ stringId = IDS_UPD_WEEK;
+ break;
+ case TIME_DAILY:
+ stringId = IDS_UPD_DAY;
+ break;
+ case TIME_HOURLY:
+ stringId = IDS_UPD_HOUR;
+ break;
+ }
+ return WASABI_API_LNGSTRINGW_BUF( stringId, buffer, bufferMax );
+}
+
+bool Update::GetAutoUpdate(int selection)
+{
+ if (selection == TIME_MANUALLY)
+ return false;
+ else
+ return true;
+}
+
+__time64_t Update::GetTime(int selection)
+{
+ if (selection >= 0 && selection < TIME_NUMENTRIES)
+ return times[selection];
+ else
+ return 0;
+}
+
+int Update::GetSelection(__time64_t selTime, bool autoUpdate)
+{
+ if (!autoUpdate)
+ return TIME_MANUALLY;
+
+ for (int i = TIME_WEEKLY;i < TIME_NUMENTRIES;i++)
+ if (selTime >= times[i])
+ return i;
+
+ return TIME_DAILY;
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/UpdateTime.h b/Src/Plugins/Library/ml_wire/UpdateTime.h
new file mode 100644
index 00000000..3edc670d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/UpdateTime.h
@@ -0,0 +1,29 @@
+#ifndef NULLSOFT_UPDATETIMEH
+#define NULLSOFT_UPDATETIMEH
+
+#include <time.h>
+
+/* This header file is used by FeedsDialog.h
+It provides a set of helper functions to deal with the combo box for update time
+
+basically - converts between combo box choice and __time64_t
+*/
+namespace Update
+{
+ enum
+ {
+ TIME_MANUALLY = 0,
+ TIME_WEEKLY,
+ TIME_DAILY,
+ TIME_HOURLY,
+ TIME_NUMENTRIES
+ };
+
+ extern __time64_t times[];
+
+ const wchar_t *GetTitle(int position, wchar_t *buffer, int bufferMax);
+ bool GetAutoUpdate(int selection);
+ __time64_t GetTime(int selection);
+ int GetSelection(__time64_t selTime, bool autoUpdate);
+}
+#endif
diff --git a/Src/Plugins/Library/ml_wire/Util.h b/Src/Plugins/Library/ml_wire/Util.h
new file mode 100644
index 00000000..2966fa1b
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Util.h
@@ -0,0 +1,69 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/trace.h"
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#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))
+
+
+#if defined (_WIN64)
+ #define MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+#else
+ #define MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWL_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+#endif
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+
+LPWSTR Plugin_MallocString(size_t cchLen);
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
+void Plugin_FreeString(LPWSTR pszString);
+LPWSTR Plugin_CopyString(LPCWSTR pszSource);
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen);
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
+void Plugin_FreeAnsiString(LPSTR pszString);
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
+void Plugin_FreeResString(LPWSTR pszResource);
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
+
+void Plugin_SafeRelease(IUnknown *pUnk);
+
+
+HRESULT Plugin_FileExtensionFromUrl(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension);
+void Plugin_ReplaceBadPathChars(LPWSTR pszPath);
+INT Plugin_CleanDirectory(LPWSTR pszPath);
+
+HRESULT Plugin_EnsurePathExist(LPCWSTR pszDirectory);
+
+#endif // NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/WantsDownloadStatus.h b/Src/Plugins/Library/ml_wire/WantsDownloadStatus.h
new file mode 100644
index 00000000..39bf1e5d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/WantsDownloadStatus.h
@@ -0,0 +1,14 @@
+#ifndef NULLSOFT_WANTSDOWNLOADSTATUSH
+#define NULLSOFT_WANTSDOWNLOADSTATUSH
+
+class WantsDownloadStatus
+{
+public:
+ virtual void DownloadStarted() {}
+ virtual void UpdateDownloadStatus(size_t downloaded, size_t totalBytes) {}
+ virtual void DownloadDone() {}
+ virtual void DownloadFailed() {}
+
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/Wire.cpp b/Src/Plugins/Library/ml_wire/Wire.cpp
new file mode 100644
index 00000000..e8069f19
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Wire.cpp
@@ -0,0 +1,99 @@
+#include "Main.h"
+
+#include "./subscriptionView.h"
+#include "FeedsDialog.h"
+#include "Feeds.h"
+
+#include <algorithm>
+
+
+ChannelList channels;
+
+//CategoryIndex sourceByCategory;
+using namespace Nullsoft::Utility;
+//LockGuard /*feedGuard, */channelGuard, categoryGuard;
+
+void WireManager::BeginChannelSync()
+{
+ // TODO something better than this =)
+ // like making 'touched' flags and delete untouched ones
+ //sources.clear();
+ //sourceByCategory.clear();
+}
+
+void WireManager::NewChannel(const Channel &newChannel)
+{
+ AutoLock lock (channels LOCKNAME("NewChannel"));
+ for (ChannelList::iterator itr=channels.begin();itr!=channels.end(); itr++)
+ {
+ if (*itr == newChannel)
+ {
+ itr->UpdateFrom(newChannel);
+ return;
+ }
+ }
+ channels.push_back(newChannel);
+
+}
+
+void WireManager::EndChannelSync()
+{
+ //SubscriptionView_RefreshChannels(NULL, TRUE);
+}
+
+bool ChannelTitleSort(Channel &channel1, Channel &channel2)
+{
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, channel1.title, -1, channel2.title, -1));
+}
+
+void ChannelList::SortByTitle()
+{
+ AutoLock lock (channelGuard LOCKNAME("SortByTitle"));
+ std::sort(channelList.begin(), channelList.end(), ChannelTitleSort);
+}
+
+void ChannelList::push_back(const Channel &channel)
+{
+ channelList.push_back(channel);
+}
+
+bool ChannelList::AddChannel(Channel &channel)
+{
+ ChannelList::iterator found;
+ for (found=channels.begin(); found!=channels.end(); found++)
+ {
+ if (!wcscmp(found->url, channel.url))
+ break;
+ }
+
+ if (found == channels.end()){
+ channel.needsRefresh=true;
+ push_back(channel);
+ SaveChannels(*this);
+ return true;
+ }
+
+ return false;
+}
+
+size_t ChannelList::GetNumPodcasts()
+{
+ return channels.size();
+}
+
+ifc_podcast *ChannelList::EnumPodcast(size_t i)
+{
+ if (i < channels.size())
+ return &channels[i];
+ else
+ return 0;
+}
+
+#undef CBCLASS
+#define CBCLASS ChannelList
+START_DISPATCH;
+CB(API_PODCASTS_GETNUMPODCASTS, GetNumPodcasts)
+CB(API_PODCASTS_ENUMPODCAST, EnumPodcast)
+END_DISPATCH;
+#undef CBCLASS
+
diff --git a/Src/Plugins/Library/ml_wire/Wire.h b/Src/Plugins/Library/ml_wire/Wire.h
new file mode 100644
index 00000000..8049be62
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/Wire.h
@@ -0,0 +1,62 @@
+#ifndef NULLSOFT_WIREH
+#define NULLSOFT_WIREH
+
+#include "Feeds.h"
+#include "../nu/AutoLock.h"
+#include "api_podcasts.h"
+#include <vector>
+
+class ChannelList : public api_podcasts
+{
+public:
+
+ ChannelList() : channelGuard( "Feed Guard" ) {}
+
+ typedef std::vector<Channel> ChannelContainer;
+ typedef ChannelContainer::iterator iterator;
+
+ size_t size() { return channelList.size(); }
+ bool empty() { return channelList.empty(); }
+
+ void push_back( const Channel &channel );
+ bool AddChannel( Channel &channel );
+
+ iterator begin() { return channelList.begin(); }
+
+ iterator end() { return channelList.end(); }
+
+ void RemoveChannel( int index ) { channelList.erase( channelList.begin() + index ); }
+
+ Channel &operator[]( size_t index ) { return channelList[ index ]; }
+
+ ChannelContainer channelList;
+
+ operator Nullsoft::Utility::LockGuard &( ) { return channelGuard; }
+
+ void SortByTitle();
+
+ Nullsoft::Utility::LockGuard channelGuard;
+
+public: // api_podcasts interface
+ size_t GetNumPodcasts();
+ ifc_podcast *EnumPodcast( size_t i );
+
+protected:
+ RECVS_DISPATCH;
+};
+
+extern ChannelList channels;
+//extern CategoryIndex sourceByCategory;
+#include "ChannelSync.h"
+
+class WireManager : public ChannelSync
+{
+public:
+ void BeginChannelSync();
+ void NewChannel( const Channel &newChannel );
+ void EndChannelSync();
+};
+
+extern WireManager channelMgr;
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/XMLWriter.cpp b/Src/Plugins/Library/ml_wire/XMLWriter.cpp
new file mode 100644
index 00000000..831db94e
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/XMLWriter.cpp
@@ -0,0 +1,272 @@
+#include "Main.h"
+#include "Feeds.h"
+#include "RFCDate.h"
+#include "Downloaded.h"
+#include "../nu/AutoLock.h"
+#include "Defaults.h"
+#include "../Agave/Language/api_language.h"
+
+using namespace Nullsoft::Utility;
+
+static void XMLWriteString(FILE *fp, const wchar_t *str)
+{
+ for (const wchar_t *itr = str; *itr; itr=CharNextW(itr))
+ {
+ switch (*itr)
+ {
+ case '<': fputws(L"&lt;", fp); break;
+ case '>': fputws(L"&gt;", fp); break;
+ case '&': fputws(L"&amp;", fp); break;
+ case '\"': fputws(L"&quot;", fp); break;
+ case '\'': fputws(L"&apos;", fp); break;
+ default: fputwc(*itr, fp); break;
+ }
+ }
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, const wchar_t *value)
+{
+ fwprintf(fp, L" %s=\"", property);
+ XMLWriteString(fp, value);
+ fputws(L"\"", fp);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, int value)
+{
+ fwprintf(fp, L" %s=\"%i\"", property, value);
+}
+
+static void InstaPropI64(FILE *fp, const wchar_t *property, int64_t value)
+{
+ fwprintf(fp, L" %s=\"%I64d\"", property, value);
+}
+
+static void InstaPropTime(FILE *fp, const wchar_t *property, __time64_t value)
+{
+ fwprintf(fp, L" %s=\"%I64u\"", property, value);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, float value)
+{
+ _fwprintf_l(fp, L" %s=\"%3.3f\"", WASABI_API_LNG->Get_C_NumericLocale(), property, value);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, bool value)
+{
+ fwprintf(fp, L" %s=\"", property);
+ if (value)
+ fputws(L"true\"", fp);
+ else
+ fputws(L"false\"", fp);
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, const wchar_t *content)
+{
+ if (content && !((INT_PTR)content < 65536) && *content)
+ {
+ fwprintf(fp, L"<%s>", tag);
+ XMLWriteString(fp, content);
+ fwprintf(fp, L"</%s>\r\n", tag);
+ }
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, unsigned int uint)
+{
+ fwprintf(fp, L"<%s>%u</%s>", tag, uint, tag);
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, __time64_t uint)
+{
+ fwprintf(fp, L"<%s>%I64u</%s>", tag, uint, tag);
+}
+
+static void BuildXMLString(FILE *fp, const RSS::Item &item)
+{
+ fputws(L"<item", fp);
+ InstaProp(fp, L"winamp:listened", item.listened);
+ InstaProp(fp, L"winamp:downloaded", item.downloaded);
+ fputws(L">\r\n", fp);
+
+ InstaTag(fp, L"title", item.itemName);
+
+ if (item.url && item.url[0])
+ {
+ fputws(L"<enclosure", fp);
+ InstaProp(fp, L"url", item.url);
+ InstaPropI64(fp, L"length", item.size);
+ fputws(L"/>\r\n", fp);
+ }
+
+ InstaTag(fp, L"guid", item.guid);
+ InstaTag(fp, L"description", item.description);
+ InstaTag(fp, L"link", item.link);
+
+ if (item.duration && item.duration[0])
+ {
+ InstaTag(fp, L"itunes:duration", item.duration);
+ }
+
+ if (item.publishDate)
+ {
+ wchar_t date_str[128] = {0};
+ MakeRFCDateString(item.publishDate, date_str, 128);
+ InstaTag(fp, L"pubDate", date_str);
+ }
+
+ if (item.sourceUrl && item.sourceUrl[0])
+ {
+ fputws(L"<source",fp);
+ InstaProp(fp, L"src", item.sourceUrl);
+ fputws(L"/>\r\n", fp);
+ }
+
+ fputws(L"</item>\r\n", fp);
+}
+
+static void BuildXMLString(FILE *fp, Channel &channel)
+{
+ fputws(L"<channel", fp);
+
+ wchar_t date_str[128] = {0};
+ MakeRFCDateString(channel.lastUpdate, date_str, 128);
+
+ InstaProp(fp, L"winamp:lastupdate", date_str);
+ fputws(L">\r\n", fp);
+
+ // write update settings for this channel
+ fputws(L"<winamp:update", fp);
+ InstaProp(fp, L"usedefaultupdate", channel.useDefaultUpdate);
+ InstaProp(fp, L"needsrefresh", channel.needsRefresh);
+
+
+ if (!channel.useDefaultUpdate)
+ {
+ InstaProp(fp, L"autoupdate", channel.autoUpdate);
+ InstaPropTime(fp, L"updatetime", channel.updateTime);
+ }
+ fputws(L"/>\r\n", fp);
+
+ if (!channel.useDefaultUpdate)
+ {
+ fputws(L"<winamp:download", fp);
+ InstaProp(fp, L"autodownload", channel.autoDownload);
+
+ if (channel.autoDownload)
+ {
+ InstaProp(fp, L"autoDownloadEpisodes", channel.autoDownloadEpisodes);
+ }
+ fputws(L"/>\r\n", fp);
+ }
+
+ InstaTag(fp, L"winamp:url", channel.url);
+ InstaTag(fp, L"title", channel.title);
+ InstaTag(fp, L"link", channel.link);
+ InstaTag(fp, L"description", channel.description);
+
+ if (channel.ttl)
+ InstaTag(fp, L"ttl", channel.ttl);
+ fputws(L"\r\n", fp);
+
+ Channel::ItemList::iterator itemItr;
+ for (itemItr = channel.items.begin();itemItr != channel.items.end(); itemItr++)
+ BuildXMLString(fp, *itemItr);
+
+ fputws(L"</channel>\r\n", fp);
+}
+
+void SaveChannels(const wchar_t *fileName, ChannelList &channels)
+{
+ FILE *fp = _wfopen(fileName, L"wb");
+ if (fp)
+ {
+ ChannelList::iterator itr;
+ wchar_t BOM = 0xFEFF;
+ fwrite(&BOM, sizeof(BOM), 1, fp);
+ fputws(L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n", fp);
+ fputws(L"<rss version=\"2.0\" xmlns:winamp=\"http://www.winamp.com\">\r\n", fp);
+
+ for (itr = channels.begin();itr != channels.end();itr++)
+ BuildXMLString(fp, *itr);
+
+ fputws(L"</rss>", fp);
+ fclose(fp);
+ }
+}
+
+static void BuildXMLPreferences(FILE *fp)
+{
+ fputws(L"<download", fp);
+ InstaProp(fp, L"downloadpath", defaultDownloadPath);
+ InstaProp(fp, L"autodownload", autoDownload);
+ InstaProp(fp, L"autoDownloadEpisodes", autoDownloadEpisodes);
+ InstaProp(fp, L"needToMakePodcastsView", needToMakePodcastsView);
+ fputws(L"/>\r\n", fp);
+
+ fputws(L"<update", fp);
+ InstaPropTime(fp, L"updatetime", updateTime);
+ InstaProp(fp, L"autoupdate", autoUpdate);
+ InstaProp(fp, L"updateonlaunch", updateOnLaunch);
+ fputws(L"/>\r\n", fp);
+
+ fputws(L"<subscriptions", fp);
+ InstaProp(fp, L"htmldivider", htmlDividerPercent);
+ InstaProp(fp, L"channeldivider", channelDividerPercent);
+ InstaProp(fp, L"itemtitlewidth", itemTitleWidth);
+ InstaProp(fp, L"itemdatewidth", itemDateWidth);
+ InstaProp(fp, L"itemmediawidth", itemMediaWidth);
+ InstaProp(fp, L"itemsizewidth", itemSizeWidth);
+ InstaProp(fp, L"currentitemsort", currentItemSort);
+ InstaProp(fp, L"itemsortascending", itemSortAscending);
+ InstaProp(fp, L"channelsortascending", channelSortAscending);
+ InstaProp(fp, L"channelLastSelection", channelLastSelection);
+ fputws(L"/>\r\n", fp);
+
+ fputws(L"<downloadsView", fp);
+ InstaProp(fp, L"downloadsChannelWidth", downloadsChannelWidth);
+ InstaProp(fp, L"downloadsItemWidth", downloadsItemWidth);
+ InstaProp(fp, L"downloadsProgressWidth", downloadsProgressWidth);
+ InstaProp(fp, L"downloadsPathWidth", downloadsPathWidth);
+
+ InstaProp(fp, L"downloadsItemSort", downloadsItemSort);
+ InstaProp(fp, L"downloadsSortAscending", downloadsSortAscending);
+ fputws(L"/>\r\n", fp);
+
+ fputws(L"<service", fp);
+ InstaProp(fp, L"url", serviceUrl);
+ fputws(L"/>\r\n", fp);
+}
+
+static void BuildXMLDownloads(FILE *fp, DownloadList &downloads)
+{
+ fputws(L"<downloads>", fp);
+ AutoLock lock (downloads);
+ DownloadList::const_iterator itr;
+ for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
+ {
+ fputws(L"<download>", fp);
+ InstaTag(fp, L"channel", itr->channel);
+ InstaTag(fp, L"item", itr->item);
+ InstaTag(fp, L"url", itr->url);
+ InstaTag(fp, L"path", itr->path);
+ InstaTag(fp, L"publishDate", itr->publishDate);
+ fputws(L"</download>\r\n", fp);
+ }
+
+ fputws(L"</downloads>", fp);
+}
+
+void SaveSettings(const wchar_t *fileName, DownloadList &downloads)
+{
+ FILE *fp = _wfopen(fileName, L"wb");
+ if (fp)
+ {
+ wchar_t BOM = 0xFEFF;
+ fwrite(&BOM, sizeof(BOM), 1, fp);
+ fputws(L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n", fp);
+ fputws(L"<winamp:preferences xmlns:winamp=\"http://www.winamp.com\" version=\"2\">\r\n", fp);
+ BuildXMLPreferences(fp);
+ BuildXMLDownloads(fp, downloads);
+ fputws(L"</winamp:preferences>", fp);
+ fclose(fp);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/XMLWriter.h b/Src/Plugins/Library/ml_wire/XMLWriter.h
new file mode 100644
index 00000000..b2ed586a
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/XMLWriter.h
@@ -0,0 +1,8 @@
+#ifndef NULLSOFT_XMLWRITERH
+#define NULLSOFT_XMLWRITERH
+#include "Wire.h"
+#include "Downloaded.h"
+void SaveChannels(const wchar_t *fileName, ChannelList &channels);
+
+void SaveSettings(const wchar_t *fileName, DownloadList &downloads);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/api__ml_wire.h b/Src/Plugins/Library/ml_wire/api__ml_wire.h
new file mode 100644
index 00000000..9f261664
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/api__ml_wire.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_ML_WIRE_API_H
+#define NULLSOFT_ML_WIRE_API_H
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_security;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_security
+
+#include "../Agave/Language/api_language.h"
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../omBrowser/obj_ombrowser.h"
+extern obj_ombrowser *browserManager;
+#define OMBROWSERMNGR browserManager
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include "../nu/threadpool/api_threadpool.h"
+extern api_threadpool *threadPoolApi;
+#define WASABI_API_THREADPOOL threadPoolApi
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#endif // !NULLSOFT_ML_WIRE_API_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/api_podcasts.h b/Src/Plugins/Library/ml_wire/api_podcasts.h
new file mode 100644
index 00000000..5085a0b2
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/api_podcasts.h
@@ -0,0 +1,46 @@
+#ifndef NULLSOFT_API_ML_WIRE_PODCASTS_H
+#define NULLSOFT_API_ML_WIRE_PODCASTS_H
+
+#include <bfc/dispatch.h>
+
+class ifc_podcastsubscribecallback;
+class ifc_podcast;
+
+class api_podcasts : public Dispatchable
+{
+protected:
+ api_podcasts() {}
+ ~api_podcasts() {}
+
+public:
+ size_t GetNumPodcasts();
+ ifc_podcast *EnumPodcast( size_t i );
+
+ // TODO: locking mechanism like api_playlists
+ // TODO: Remove playlists
+ // TODO: int Subscribe(const wchar_t *url, ifc_podcastsubscribecallback *callback);
+ /* TODO: method to download/parse a podcast channel w/o adding it to the list
+ maybe as part of a separate class? */
+
+ enum
+ {
+ API_PODCASTS_GETNUMPODCASTS = 0,
+ API_PODCASTS_ENUMPODCAST = 1,
+ };
+};
+
+inline size_t api_podcasts::GetNumPodcasts()
+{
+ return _call( API_PODCASTS_GETNUMPODCASTS, (size_t)0 );
+}
+
+inline ifc_podcast *api_podcasts::EnumPodcast( size_t i )
+{
+ return _call( API_PODCASTS_ENUMPODCAST, (ifc_podcast *)0, i );
+}
+
+// {4D2E9987-D955-45f0-A06F-371405E8B961}
+static const GUID api_podcastsGUID =
+{ 0x4d2e9987, 0xd955, 0x45f0, { 0xa0, 0x6f, 0x37, 0x14, 0x5, 0xe8, 0xb9, 0x61 } };
+
+#endif // !NULLSOFT_API_ML_WIRE_PODCASTS_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/channelEditor.cpp b/Src/Plugins/Library/ml_wire/channelEditor.cpp
new file mode 100644
index 00000000..d00bfa8b
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/channelEditor.cpp
@@ -0,0 +1,687 @@
+#include "main.h"
+#include "./util.h"
+#include "./channelEditor.h"
+#include "api__ml_wire.h"
+#include "./defaults.h"
+#include "./updateTime.h"
+#include "./UpdateAutoDownload.h"
+#include "./errors.h"
+#include "./feeds.h"
+#include "./feedUtil.h"
+#include "./cloud.h"
+#include "./subscriptionView.h"
+
+
+#include <strsafe.h>
+
+using namespace Nullsoft::Utility;
+extern Cloud cloud;
+
+typedef struct __CHANNELEDITOR
+{
+ HWND hOwner;
+ size_t iChannel;
+ UINT flags;
+ BOOL enableAutoDownload;
+ BOOL enableAutoUpdate;
+ __time64_t updateTime;
+ INT numOfAutoDownloadEpisodes;
+} CHANNELEDITOR;
+
+
+#define GetEditor(__hwnd) ((CHANNELEDITOR*)GetPropW((__hwnd), MAKEINTATOM(VIEWPROP)))
+
+static INT_PTR CALLBACK ChannelEditor_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+INT_PTR ChannelEditor_Show(HWND hOwner, size_t channelIndex, UINT flags)
+{
+ if (0 == VIEWPROP)
+ return 0;
+
+ if (NULL == hOwner)
+ hOwner = plugin.hwndLibraryParent;
+
+ if (0 == (CEF_CREATENEW & flags) && BAD_CHANNEL == channelIndex)
+ return 0;
+
+ CHANNELEDITOR editor;
+ ZeroMemory(&editor, sizeof(editor));
+
+ editor.hOwner = hOwner;
+ editor.iChannel = channelIndex;
+ editor.flags = flags;
+
+ INT_PTR result = WASABI_API_DIALOGBOXPARAMW(IDD_ADDURL, hOwner, ChannelEditor_DialogProc, (LPARAM)&editor);
+ return result;
+}
+
+
+static BOOL ChannelEditor_SetUpdateTime(HWND hwnd, __time64_t updateTime, BOOL enableAutoUpdate)
+{
+ HWND hCombo = GetDlgItem(hwnd, IDC_UPDATELIST);
+ if (NULL == hCombo) return FALSE;
+
+ INT selectedVal = Update::GetSelection(updateTime, (FALSE != enableAutoUpdate));
+ INT iCount = (INT)SNDMSG(hCombo, CB_GETCOUNT, 0, 0L);
+ INT iSelect = CB_ERR;
+ for (INT i = 0; i < iCount && CB_ERR == iSelect; i++)
+ {
+ if (selectedVal == (INT)SNDMSG(hCombo, CB_GETITEMDATA, i, 0L))
+ iSelect = i;
+ }
+
+ if (CB_ERR == iSelect)
+ return FALSE;
+
+ if (iSelect != (INT)SNDMSG(hCombo, CB_GETCURSEL, 0, 0L) &&
+ CB_ERR == SNDMSG(hCombo, CB_SETCURSEL, iSelect, 0))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL ChannelEditor_SetAutoDownload(HWND hwnd, INT numOfEpisodes, BOOL enableAutoDownload)
+{
+ HWND hCombo = GetDlgItem(hwnd, IDC_AUTODOWNLOADLIST);
+ if (NULL == hCombo) return FALSE;
+
+ INT selectedVal = UpdateAutoDownload::GetSelection(numOfEpisodes, (FALSE != enableAutoDownload));
+ INT iCount = (INT)SNDMSG(hCombo, CB_GETCOUNT, 0, 0L);
+ INT iSelect = CB_ERR;
+ for (INT i = 0; i < iCount && CB_ERR == iSelect; i++)
+ {
+ if (selectedVal == (INT)SNDMSG(hCombo, CB_GETITEMDATA, i, 0L))
+ iSelect = i;
+ }
+
+ if (CB_ERR == iSelect)
+ return FALSE;
+
+ if (iSelect != (INT)SNDMSG(hCombo, CB_GETCURSEL, 0, 0L) &&
+ CB_ERR == SNDMSG(hCombo, CB_SETCURSEL, iSelect, 0))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL ChannelEditor_InitCombobox(HWND hwnd)
+{
+ HWND hCombo = GetDlgItem(hwnd, IDC_UPDATELIST);
+ HWND hAutoDownloadCombo = GetDlgItem(hwnd, IDC_AUTODOWNLOADLIST);
+ if ( NULL == hCombo || NULL == hAutoDownloadCombo ) return FALSE;
+
+ WCHAR szBuffer[256] = {0};
+ INT iPos = 0;
+
+ SNDMSG(hCombo, WM_SETREDRAW, FALSE, 0L);
+
+ for (INT i = 0; i < Update::TIME_NUMENTRIES; i++)
+ {
+ if (NULL != Update::GetTitle(i, szBuffer, ARRAYSIZE(szBuffer)))
+ {
+ iPos = (INT)SNDMSG(hCombo, CB_ADDSTRING, 0, (LPARAM)szBuffer);
+ if (CB_ERR != iPos)
+ {
+ SNDMSG(hCombo, CB_SETITEMDATA, (WPARAM)iPos, (LPARAM)i);
+ }
+ }
+ }
+
+ SNDMSG(hCombo, WM_SETREDRAW, TRUE, 0L);
+
+ SNDMSG(hAutoDownloadCombo, WM_SETREDRAW, FALSE, 0L);
+
+ for (INT i = 0; i < UpdateAutoDownload::AUTODOWNLOAD_NUMENTRIES; i++)
+ {
+ if (NULL != UpdateAutoDownload::GetTitle(i, szBuffer, ARRAYSIZE(szBuffer)))
+ {
+ iPos = (INT)SNDMSG(hAutoDownloadCombo, CB_ADDSTRING, 0, (LPARAM)szBuffer);
+ if (CB_ERR != iPos)
+ {
+ SNDMSG(hAutoDownloadCombo, CB_SETITEMDATA, (WPARAM)iPos, (LPARAM)i);
+ }
+ }
+ }
+
+ SNDMSG(hAutoDownloadCombo, WM_SETREDRAW, TRUE, 0L);
+
+ return TRUE;
+}
+
+static BOOL ChannelEditor_EnableUserSettings(HWND hwnd, BOOL fEnable)
+{
+ BOOL result;
+ UINT activeButton = (FALSE == fEnable) ? IDC_USEDEFAULTS : IDC_USECUSTOM;
+
+ result = CheckRadioButton(hwnd, IDC_USEDEFAULTS, IDC_USECUSTOM, activeButton);
+
+ if (FALSE != result)
+ {
+ SendDlgItemMessage(hwnd, activeButton, BM_CLICK, 0, 0L);
+ }
+ return result;
+}
+
+#if 0
+static BOOL ChannelEditor_EnableAutoDownload(HWND hwnd, BOOL fEnable)
+{
+ BOOL result;
+ result = CheckDlgButton(hwnd, IDC_AUTODOWNLOAD, (FALSE != fEnable) ? BST_CHECKED : BST_UNCHECKED);
+ return result;
+}
+#endif
+
+static BOOL ChannelEditor_SetUrl(HWND hwnd, LPCWSTR pszUrl)
+{
+ HWND hEdit = GetDlgItem(hwnd, IDC_EDITURL);
+ if (NULL == hEdit) return FALSE;
+
+ return SNDMSG(hEdit, WM_SETTEXT, 0, (LPARAM)pszUrl);
+}
+static void ChannelEditor_UpdateTitle(HWND hwnd, BOOL fModified)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return;
+
+ if (0 != (CEF_CREATENEW & editor->flags))
+ return;
+
+
+
+ WCHAR szBuffer[256] = {0};
+ size_t remaining = ARRAYSIZE(szBuffer);
+ LPWSTR cursor = szBuffer;
+
+ if (FALSE != fModified)
+ {
+ StringCchCopyEx(cursor, remaining, L"*", &cursor, &remaining, 0);
+ }
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_EDIT_CHANNEL, cursor, remaining);
+ INT cchLen = lstrlen(cursor);
+ cursor += cchLen;
+ remaining -= cchLen;
+
+ AutoLock lock (channels);
+ LPCWSTR title = NULL;
+ if (editor->iChannel < channels.size())
+ {
+ Channel *channel = &channels[editor->iChannel];
+ title = channel->title;
+ if (NULL != title && L'\0' != title)
+ {
+ StringCchPrintf(cursor, remaining, L": %s", title);
+ }
+ }
+
+ SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)szBuffer);
+}
+
+static void ChannelEditor_UpdateModifiedState(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return;
+
+ if (0 != (CEF_CREATENEW & editor->flags))
+ return;
+
+ AutoLock lock (channels);
+
+ if (editor->iChannel >= channels.size())
+ return;
+
+ BOOL fModified = FALSE;
+
+ Channel *channel = &channels[editor->iChannel];
+
+ HWND hControl;
+
+ if (FALSE == fModified && (NULL != (hControl = GetDlgItem(hwnd, IDC_USECUSTOM))) &&
+ (BST_CHECKED == SNDMSG(hControl, BM_GETCHECK, 0, 0L)) != (false == channel->useDefaultUpdate))
+ {
+ fModified = TRUE;
+ }
+
+ if (false == channel->useDefaultUpdate)
+ {
+ if (editor->enableAutoDownload != (BOOL)channel->autoDownload ||
+ editor->enableAutoUpdate != (BOOL)channel->autoUpdate ||
+ editor->updateTime != channel->updateTime ||
+ editor->numOfAutoDownloadEpisodes != channel->autoDownloadEpisodes)
+ {
+ fModified = TRUE;
+ }
+ }
+
+ hControl = GetDlgItem(hwnd, IDOK);
+ if (NULL != hControl) EnableWindow(hControl, fModified);
+
+ ChannelEditor_UpdateTitle(hwnd, fModified);
+}
+
+static BOOL ChannelEditor_Reposition(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return FALSE;
+
+ RECT rectEditor, rectOwner;
+
+ if (0 != (CEF_CENTEROWNER & editor->flags))
+ {
+ if (FALSE == GetWindowRect(editor->hOwner, &rectOwner) ||
+ FALSE == GetWindowRect(hwnd, &rectEditor))
+ {
+ return FALSE;
+ }
+
+ LONG x = rectOwner.left + ((rectOwner.right - rectOwner.left) - (rectEditor.right - rectEditor.left))/2;
+ LONG y = rectOwner.top + ((rectOwner.bottom - rectOwner.top) - (rectEditor.bottom - rectEditor.top))/2;
+ SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+ }
+
+
+
+ return TRUE;
+}
+
+static INT_PTR ChannelEditor_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
+{
+
+ CHANNELEDITOR *editor = (CHANNELEDITOR*)lParam;
+ if (NULL == editor || FALSE == SetProp(hwnd, MAKEINTATOM(VIEWPROP), editor))
+ {
+ EndDialog(hwnd, 0);
+ return 0;
+ }
+
+ ChannelEditor_InitCombobox(hwnd);
+
+ editor->enableAutoDownload = autoDownload;
+ editor->enableAutoUpdate = autoUpdate;
+ editor->updateTime = updateTime;
+ editor->numOfAutoDownloadEpisodes = autoDownloadEpisodes;
+
+ if (0 != (CEF_CREATENEW & editor->flags))
+ {
+ ChannelEditor_SetUrl(hwnd, L"");
+ ChannelEditor_EnableUserSettings(hwnd, FALSE);
+ }
+ else
+ {
+ AutoLock lock (channels);
+ if (editor->iChannel >= channels.size())
+ {
+ EndDialog(hwnd, 0);
+ return 0;
+ }
+ Channel *channel = &channels[editor->iChannel];
+
+ HWND hControl;
+
+ hControl = GetDlgItem(hwnd, IDC_EDITURL);
+ if (NULL != hControl) SNDMSG(hControl, EM_SETREADONLY, TRUE, 0L);
+
+ hControl = GetDlgItem(hwnd, IDOK);
+ if (NULL != hControl)
+ {
+ WCHAR szBuffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SAVE, szBuffer, ARRAYSIZE(szBuffer));
+ SNDMSG(hControl, WM_SETTEXT, 0, (LPARAM)szBuffer);
+ }
+
+ if (false == channel->useDefaultUpdate)
+ {
+ editor->enableAutoDownload = channel->autoDownload;
+ editor->enableAutoUpdate = channel->autoUpdate;
+ editor->updateTime = channel->updateTime;
+ editor->numOfAutoDownloadEpisodes = channel->autoDownloadEpisodes;
+ }
+
+ ChannelEditor_SetUrl(hwnd, channel->url);
+ ChannelEditor_EnableUserSettings(hwnd, !channel->useDefaultUpdate);
+ }
+
+ ChannelEditor_Reposition(hwnd);
+
+ return TRUE;
+}
+
+static void ChannelEditor_OnDestroy(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ RemoveProp(hwnd, MAKEINTATOM(VIEWPROP));
+}
+
+static void ChannelEditor_UpdateUserSettings(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return;
+
+ HWND hControl;
+ FALSE;
+
+ hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
+ BOOL enableUser = (NULL != hControl && BST_CHECKED == (INT)SNDMSG(hControl, BM_GETCHECK, 0, 0L));
+
+ if (FALSE == enableUser)
+ {
+ ChannelEditor_SetUpdateTime(hwnd, updateTime, autoUpdate);
+ ChannelEditor_SetAutoDownload(hwnd, autoDownloadEpisodes, autoDownload);
+ }
+ else
+ {
+ ChannelEditor_SetUpdateTime(hwnd, editor->updateTime, editor->enableAutoUpdate);
+ ChannelEditor_SetAutoDownload(hwnd, editor->numOfAutoDownloadEpisodes, editor->enableAutoDownload);
+ }
+
+ const INT szControl[] = { IDC_UPDATELIST, IDC_AUTODOWNLOADLIST, IDC_STATIC_UPDATEEVERY, IDC_STATIC_AUTODOWNLOAD, };
+ for (INT i = 0; i < ARRAYSIZE(szControl); i++)
+ {
+ hControl = GetDlgItem(hwnd, szControl[i]);
+ if (NULL != hControl) EnableWindow(hControl, enableUser);
+ }
+
+ ChannelEditor_UpdateModifiedState(hwnd);
+
+}
+
+static INT_PTR ChannelEditor_SaveChannel(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return 0;
+
+ AutoLock lock (channels);
+ if (editor->iChannel >= channels.size())
+ return 0;
+
+ HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
+ if (NULL == hControl) return 0;
+
+ Channel *channel = &channels[editor->iChannel];
+
+ if (BST_CHECKED == SNDMSG(hControl, BM_GETCHECK, 0, 0L))
+ {
+ channel->useDefaultUpdate = false;
+ channel->autoDownload = (FALSE != editor->enableAutoDownload);
+ channel->updateTime = editor->updateTime;
+ channel->autoUpdate = (FALSE != editor->enableAutoUpdate);
+ channel->autoDownloadEpisodes = editor->numOfAutoDownloadEpisodes;
+ }
+ else
+ {
+ channel->useDefaultUpdate = true;
+ channel->autoDownload = autoDownload;
+ channel->updateTime = updateTime;
+ channel->autoUpdate = autoUpdate;
+ channel->autoDownloadEpisodes = autoDownloadEpisodes;
+ }
+
+ if (channel->autoUpdate)
+ cloud.Pulse();
+ return
+ IDOK;
+}
+
+static DWORD AddUrlThread(void *vBuffer, HWND hwndDlg)
+{
+ Channel newFeed;
+ newFeed.autoUpdate = false; // TODO check defaults
+ newFeed.autoDownload = false; // leave this as false
+ newFeed.SetURL((const wchar_t *)vBuffer);
+ delete vBuffer;
+
+ int downloadError = DownloadFeedInformation(newFeed);
+ if (downloadError != DOWNLOAD_SUCCESS)
+ return downloadError;
+
+ // TODO check defaults;
+ if (IsDlgButtonChecked(hwndDlg, IDC_USECUSTOM) == BST_CHECKED)
+ {
+ LRESULT timeSelection;
+ timeSelection = SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_GETCURSEL, 0, 0);
+ if (timeSelection != CB_ERR)
+ {
+ newFeed.autoUpdate = Update::GetAutoUpdate(timeSelection);
+ newFeed.updateTime = Update::GetTime(timeSelection);
+ }
+ LRESULT episodesSelection;
+ episodesSelection = SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_GETCURSEL, 0, 0);
+ if (episodesSelection != CB_ERR)
+ {
+ newFeed.autoDownload = UpdateAutoDownload::GetAutoDownload(episodesSelection);
+ newFeed.autoDownloadEpisodes = UpdateAutoDownload::GetAutoDownloadEpisodes(episodesSelection);
+ }
+ newFeed.useDefaultUpdate = false;
+ }
+ else
+ {
+ newFeed.useDefaultUpdate = true;
+ newFeed.autoUpdate = autoUpdate;
+ newFeed.updateTime = updateTime;
+ newFeed.autoDownloadEpisodes = autoDownloadEpisodes;
+ newFeed.autoDownload = autoDownload;
+ }
+
+ //newFeed.title += L" ";
+ AutoLock lock (channels LOCKNAME("AddURL"));
+ if (!channels.AddChannel(newFeed))
+ {
+ wchar_t error_msg[1024] = {0}, titleStr[64] = {0};
+ StringCchPrintf( error_msg, 1024, WASABI_API_LNGSTRINGW( IDS_CHANNEL_ALREADY_PRESENT ), newFeed.title, newFeed.url );
+ MessageBox( hwndDlg, error_msg, WASABI_API_LNGSTRINGW_BUF( IDS_DUPLICATE_CHANNEL, titleStr, 64 ), MB_OK );
+
+ return DOWNLOAD_DUPLICATE;
+ }
+ else
+ {
+ cloud.Pulse(); // TODO why?
+ HWND hView = SubscriptionView_FindWindow();
+ if (NULL != hView)
+ {
+ SubscriptionView_RefreshChannels(hView, TRUE);
+ }
+
+ }
+ return DOWNLOAD_SUCCESS; // success
+}
+
+static INT ChannelEditor_AddUrl(HWND hwnd)
+{
+ size_t length = GetWindowTextLength(GetDlgItem(hwnd, IDC_EDITURL)) + 1;
+ wchar_t *buffer = new wchar_t[length];
+ ZeroMemory(buffer,sizeof(wchar_t)*length);
+ GetDlgItemText(hwnd, IDC_EDITURL, buffer, (int)length);
+ return AddUrlThread((void *)buffer, hwnd);
+}
+
+static INT_PTR ChannelEditor_CreateChannel(HWND hwnd)
+{
+ INT result = ChannelEditor_AddUrl(hwnd);
+ if (DOWNLOAD_SUCCESS == result)
+ return IDOK;
+
+ INT messageId = IDS_ERROR_ADDING_URL;
+ INT titleId = IDS_ERROR_ADDING_URL;
+
+ switch (result)
+ {
+ case DOWNLOAD_ERROR_PARSING_XML: messageId = IDS_ERROR_PARSING_XML_FROM_SERVER; break;
+ case DOWNLOAD_NOTRSS: messageId = IDS_LINK_HAS_NO_RSS_INFO; break;
+ case DOWNLOAD_404: messageId = IDS_INVALID_LINK; break;
+ case DOWNLOAD_NOHTTP: messageId = IDS_NO_JNETLIB; titleId = IDS_JNETLIB_MISSING; break;
+ case DOWNLOAD_NOPARSER: messageId = IDS_NO_EXPAT; titleId = IDS_EXPAT_MISSING; break;
+ case DOWNLOAD_CONNECTIONRESET: messageId = IDS_CONNECTION_RESET_BY_PEER; break;
+ }
+
+ if(result != DOWNLOAD_DUPLICATE)
+ {
+ WCHAR szMessage[512] = {0}, szTitle[128] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(messageId, szMessage, ARRAYSIZE(szMessage));
+ WASABI_API_LNGSTRINGW_BUF(titleId, szTitle, ARRAYSIZE(szTitle));
+ MessageBox(hwnd, szMessage, szTitle, MB_OK | MB_ICONERROR);
+ }
+
+ return IDIGNORE;
+}
+
+static void ChannelEditor_OnCancel(HWND hwnd)
+{
+ EndDialog(hwnd, IDCANCEL);
+}
+
+static void ChannelEditor_OnOk(HWND hwnd)
+{
+ INT_PTR result = 0;
+
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL != editor)
+ {
+ if (0 == (CEF_CREATENEW & editor->flags))
+ {
+ result = ChannelEditor_SaveChannel(hwnd);
+ }
+ else
+ {
+ result = ChannelEditor_CreateChannel(hwnd);
+ }
+ }
+
+ if (IDIGNORE != result)
+ {
+ EndDialog(hwnd, result);
+ }
+}
+
+#if 0
+static void ChannelEditor_OnAutoDownloadChanged(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return;
+
+ HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
+ if (NULL == hControl || BST_CHECKED != SNDMSG(hControl, BM_GETCHECK, 0, 0L)) return;
+
+ hControl = GetDlgItem(hwnd, IDC_AUTODOWNLOAD);
+ if (NULL == hControl) return;
+
+ editor->enableAutoDownload = (BST_CHECKED == SNDMSG(hControl, BM_GETCHECK, 0, 0L));
+
+ ChannelEditor_UpdateModifiedState(hwnd);
+}
+#endif
+
+static void ChannelEditor_OnUrlChange(HWND hwnd)
+{
+ WCHAR szBuffer[4096] = {0};
+
+ HWND hControl = GetDlgItem(hwnd, IDC_EDITURL);
+ if (NULL == hControl || 0 == (INT)SNDMSG(hControl, WM_GETTEXT, (WPARAM)ARRAYSIZE(szBuffer), (LPARAM)szBuffer))
+ szBuffer[0] = L'\0';
+
+ LPCWSTR p = szBuffer;
+ while (L' ' == *p && L'\0' != *p) p++;
+ BOOL enableButton = (L'\0' != *p);
+
+ hControl = GetDlgItem(hwnd, IDOK);
+ if (NULL != hControl)
+ {
+ EnableWindow(hControl, enableButton);
+ }
+}
+
+static void ChannelEditor_OnUpdateTimeChange(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return;
+
+ HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
+ if (NULL == hControl || BST_CHECKED != SNDMSG(hControl, BM_GETCHECK, 0, 0L)) return;
+
+ hControl = GetDlgItem(hwnd, IDC_UPDATELIST);
+ if (NULL == hControl) return;
+
+ INT iSelected = (INT)SNDMSG(hControl, CB_GETCURSEL, 0, 0L);
+ if (CB_ERR == iSelected) return;
+
+ INT selectedVal = (INT)SNDMSG(hControl, CB_GETITEMDATA, iSelected, 0L);
+ if (selectedVal < 0 || selectedVal >= Update::TIME_NUMENTRIES) return;
+
+ editor->updateTime = Update::GetTime(selectedVal);
+ editor->enableAutoUpdate = Update::GetAutoUpdate(selectedVal);
+
+ ChannelEditor_UpdateModifiedState(hwnd);
+}
+
+static void ChannelEditor_OnUpdateAutoDownloadChange(HWND hwnd)
+{
+ CHANNELEDITOR *editor = GetEditor(hwnd);
+ if (NULL == editor) return;
+
+ HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
+ if (NULL == hControl || BST_CHECKED != SNDMSG(hControl, BM_GETCHECK, 0, 0L)) return;
+
+ hControl = GetDlgItem(hwnd, IDC_AUTODOWNLOADLIST);
+ if (NULL == hControl) return;
+
+ INT iSelected = (INT)SNDMSG(hControl, CB_GETCURSEL, 0, 0L);
+ if (CB_ERR == iSelected) return;
+
+ INT selectedVal = (INT)SNDMSG(hControl, CB_GETITEMDATA, iSelected, 0L);
+ if (selectedVal < 0 || selectedVal >= UpdateAutoDownload::AUTODOWNLOAD_NUMENTRIES) return;
+
+ editor->numOfAutoDownloadEpisodes = UpdateAutoDownload::GetAutoDownloadEpisodes(selectedVal);
+ editor->enableAutoDownload = UpdateAutoDownload::GetAutoDownload(selectedVal);
+
+ ChannelEditor_UpdateModifiedState(hwnd);
+}
+
+static void ChannelEditor_OnCommand(HWND hwnd, UINT commandId, UINT eventId, HWND hControl)
+{
+ switch(commandId)
+ {
+ case IDCANCEL: ChannelEditor_OnCancel(hwnd); break;
+ case IDOK: ChannelEditor_OnOk(hwnd); break;
+ case IDC_USEDEFAULTS:
+ case IDC_USECUSTOM:
+ switch(eventId)
+ {
+ case BN_CLICKED: ChannelEditor_UpdateUserSettings(hwnd); break;
+ }
+ break;
+ case IDC_EDITURL:
+ switch(eventId)
+ {
+ case EN_CHANGE: ChannelEditor_OnUrlChange(hwnd); break;
+ }
+ break;
+ case IDC_UPDATELIST:
+ switch(eventId)
+ {
+ case CBN_SELCHANGE: ChannelEditor_OnUpdateTimeChange(hwnd); break;
+ }
+ break;
+ case IDC_AUTODOWNLOADLIST:
+ switch(eventId)
+ {
+ case CBN_SELCHANGE: ChannelEditor_OnUpdateAutoDownloadChange(hwnd); break;
+ }
+ break;
+ }
+}
+
+static INT_PTR CALLBACK ChannelEditor_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return ChannelEditor_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: ChannelEditor_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: ChannelEditor_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_wire/channelEditor.h b/Src/Plugins/Library/ml_wire/channelEditor.h
new file mode 100644
index 00000000..25a0c32a
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/channelEditor.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_CHANNEL_EDITOR_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_CHANNEL_EDITOR_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#define CEF_CREATENEW 0x00000001
+#define CEF_CENTEROWNER 0x00000002
+
+INT_PTR ChannelEditor_Show(HWND hOwner, size_t channelIndex, UINT flags);
+
+#endif //NULLSOFT_PODCAST_PLUGIN_CHANNEL_EDITOR_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/date.c b/Src/Plugins/Library/ml_wire/date.c
new file mode 100644
index 00000000..bbe876c6
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/date.c
@@ -0,0 +1 @@
+#include "time.h"
diff --git a/Src/Plugins/Library/ml_wire/date.h b/Src/Plugins/Library/ml_wire/date.h
new file mode 100644
index 00000000..58204571
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/date.h
@@ -0,0 +1,20 @@
+#ifndef _OD_DATE_
+#define _OD_DATE_
+
+#include <time.h>
+
+
+
+/* These functions all work with "Internet"(RFC 822) format Date/Time strings only */
+/* An example of an RFC 822 format Date/Time string is "Thu, 28 Aug 2003 21:30:47 EDT" */
+
+/* converts the RFC 822 format date string into UTC Calendar time */
+time_t getDateSecs(char *date);
+
+/* returns a string that represents the given UTC Calendar time value as an
+ RFC 822 format string. The buffer is user-supplied and must be at least
+ 30 bytes in size. */
+char *getDateStr(time_t tmval, char *buffer, int gmt);
+
+#endif /*_OD_DATE_*/
+
diff --git a/Src/Plugins/Library/ml_wire/db.cpp b/Src/Plugins/Library/ml_wire/db.cpp
new file mode 100644
index 00000000..d46b2a7d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/db.cpp
@@ -0,0 +1,171 @@
+#include <shlwapi.h>
+
+#include "api__ml_wire.h"
+#include "Downloaded.h"
+#include "../nde/nde_c.h"
+#include "../nu/AutoLock.h"
+
+/* DB Schema
+ChannelTitle
+ItemUrl
+ItemTitle
+PublishDate
+Length
+Filename
+*/
+
+static Nullsoft::Utility::LockGuard dbcs;
+static nde_table_t table = 0;
+static nde_database_t db = 0;
+using namespace Nullsoft::Utility;
+
+enum
+{
+ DB_ID_CHANNELTITLE = 0,
+ DB_ID_ITEMURL = 1,
+ DB_ID_ITEMTITLE = 2,
+ DB_ID_PUBLISHDATE = 3,
+ DB_ID_LENGTH = 4,
+ DB_ID_FILENAME = 5
+};
+
+static bool OpenDatabase()
+{
+ AutoLock lock(dbcs);
+ if (!db)
+ db = NDE_CreateDatabase();
+
+ return true;
+}
+
+void CloseDatabase()
+{
+ AutoLock lock( dbcs );
+ if ( db )
+ {
+ if ( table )
+ NDE_Database_CloseTable( db, table );
+
+ NDE_DestroyDatabase( db );
+ }
+
+ db = 0;
+}
+
+static void CreateFields(nde_table_t table)
+{
+ // create defaults
+ NDE_Table_NewColumnW(table, DB_ID_CHANNELTITLE, L"channeltitle", FIELD_STRING);
+ NDE_Table_NewColumnW(table, DB_ID_ITEMURL, L"itemurl", FIELD_STRING);
+ NDE_Table_NewColumnW(table, DB_ID_ITEMTITLE, L"itemtitle", FIELD_STRING);
+ NDE_Table_NewColumnW(table, DB_ID_PUBLISHDATE, L"publishdate", FIELD_DATETIME);
+ NDE_Table_NewColumnW(table, DB_ID_LENGTH, L"length", FIELD_INTEGER);
+ NDE_Table_NewColumnW(table, DB_ID_FILENAME, L"filename", FIELD_FILENAME);
+ NDE_Table_PostColumns(table);
+ NDE_Table_AddIndexByIDW(table, DB_ID_ITEMURL, L"itemurl");
+}
+
+static bool OpenTable()
+{
+ AutoLock lock( dbcs );
+ if ( !OpenDatabase() )
+ return false;
+
+ if ( !table )
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t tablePath[ MAX_PATH ] = { 0 }, indexPath[ MAX_PATH ] = { 0 };
+
+ PathCombineW( tablePath, inidir, L"plugins" );
+ PathAppendW( tablePath, L"podcasts.dat" );
+ PathCombineW( indexPath, inidir, L"plugins" );
+ PathAppendW( indexPath, L"podcasts.idx" );
+
+ table = NDE_Database_OpenTable( db, tablePath, indexPath, NDE_OPEN_ALWAYS, NDE_CACHE );
+ if ( table )
+ CreateFields( table );
+ }
+
+ return !!table;
+}
+
+static void db_add( nde_scanner_t s, unsigned char id, wchar_t *data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_StringField_SetString( f, data );
+ }
+}
+
+static void db_add_int( nde_scanner_t s, unsigned char id, int data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_IntegerField_SetValue( f, data );
+ }
+}
+
+static void db_add_time( nde_scanner_t s, unsigned char id, time_t data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_IntegerField_SetValue( f, static_cast<int>( data ) );
+ }
+}
+
+bool AddPodcastData( const DownloadedFile &data )
+{
+ AutoLock lock( dbcs );
+ if ( !OpenTable() )
+ return false;
+
+ nde_scanner_t s = NDE_Table_CreateScanner( table );
+ if ( s )
+ {
+ NDE_Scanner_New( s );
+ db_add( s, DB_ID_CHANNELTITLE, data.channel );
+ db_add( s, DB_ID_ITEMURL, data.url );
+ db_add( s, DB_ID_ITEMTITLE, data.item );
+ db_add_time( s, DB_ID_PUBLISHDATE, data.publishDate );
+ db_add_int( s, DB_ID_LENGTH, (int)data.totalSize );
+ db_add( s, DB_ID_FILENAME, data.path );
+ NDE_Scanner_Post( s );
+ NDE_Table_DestroyScanner( table, s );
+ NDE_Table_Sync( table );
+
+ return true;
+ }
+
+ return false;
+}
+
+bool IsPodcastDownloaded( const wchar_t *url )
+{
+ AutoLock lock( dbcs );
+ if ( !OpenTable() )
+ return false;
+
+ nde_scanner_t s = NDE_Table_CreateScanner( table );
+ if ( s )
+ {
+ if ( NDE_Scanner_LocateString( s, DB_ID_ITEMURL, FIRST_RECORD, url ) )
+ {
+ NDE_Table_DestroyScanner( table, s );
+ return true;
+ }
+
+ NDE_Table_DestroyScanner( table, s );
+ }
+
+ return false;
+}
+
+void CompactDatabase()
+{
+ AutoLock lock( dbcs );
+ if ( OpenTable() )
+ NDE_Table_Compact( table );
+}
diff --git a/Src/Plugins/Library/ml_wire/errors.h b/Src/Plugins/Library/ml_wire/errors.h
new file mode 100644
index 00000000..0e83b151
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/errors.h
@@ -0,0 +1,17 @@
+#ifndef NULLSOFT_ML_WIRE_ERRORS_H
+#define NULLSOFT_ML_WIRE_ERRORS_H
+
+enum
+{
+ DOWNLOAD_SUCCESS = 0,
+ DOWNLOAD_404,
+ DOWNLOAD_TIMEOUT,
+ DOWNLOAD_NOTRSS,
+ DOWNLOAD_DUPLICATE,
+ DOWNLOAD_NOHTTP,
+ DOWNLOAD_NOPARSER,
+ DOWNLOAD_CONNECTIONRESET,
+ DOWNLOAD_ERROR_PARSING_XML,
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ifc_article.h b/Src/Plugins/Library/ml_wire/ifc_article.h
new file mode 100644
index 00000000..d8887552
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ifc_article.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_ML_WIRE_IFC_ARTICLE_H
+#define NULLSOFT_ML_WIRE_IFC_ARTICLE_H
+
+#include <bfc/dispatch.h>
+
+class ifc_article
+{
+protected:
+ ifc_article() {}
+ ~ifc_article() {}
+public:
+
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ifc_podcast.h b/Src/Plugins/Library/ml_wire/ifc_podcast.h
new file mode 100644
index 00000000..bc8c9b62
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ifc_podcast.h
@@ -0,0 +1,39 @@
+#ifndef NULLSOFT_ML_WIRE_IFC_PODCAST_H
+#define NULLSOFT_ML_WIRE_IFC_PODCAST_H
+
+#include <bfc/dispatch.h>
+class ifc_podcast : public Dispatchable
+{
+protected:
+ ifc_podcast() {}
+ ~ifc_podcast() {}
+public:
+ //int GetUrl(wchar_t *str, size_t len);
+ int GetTitle(wchar_t *str, size_t len);
+ //int GetLink(wchar_t *str, size_t len);
+ //int GetDescription(wchar_t *str, size_t len);
+ size_t GetNumArticles();
+ // TODO: ifc_article *EnumArticle(size_t i);
+
+ enum
+ {
+ IFC_PODCAST_GETURL = 0,
+ IFC_PODCAST_GETTITLE = 1,
+ IFC_PODCAST_GETLINK = 2,
+ IFC_PODCAST_GETDESCRIPTION = 3,
+ IFC_PODCAST_GETNUMARTICLES = 4,
+ };
+};
+
+inline int ifc_podcast::GetTitle(wchar_t *str, size_t len)
+{
+ return _call(IFC_PODCAST_GETTITLE, (int)1, str, len);
+}
+
+
+inline size_t ifc_podcast::GetNumArticles()
+{
+ return _call(IFC_PODCAST_GETNUMARTICLES, (size_t)0);
+}
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/layout.cpp b/Src/Plugins/Library/ml_wire/layout.cpp
new file mode 100644
index 00000000..722dc71f
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/layout.cpp
@@ -0,0 +1,215 @@
+#include "./layout.h"
+
+#include <windows.h>
+
+BOOL Layout_Initialize( HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout )
+{
+ if ( NULL == itemList || NULL == layout )
+ return FALSE;
+
+ LAYOUTITEM *item;
+
+ for ( INT i = 0; i < itemCount; i++ )
+ {
+ item = &layout[ i ];
+ item->hwnd = GetDlgItem( hwnd, itemList[ i ] );
+ if ( item->hwnd == NULL )
+ continue;
+
+ if ( FALSE == GetWindowRect( item->hwnd, &item->rect ) )
+ SetRectEmpty( &item->rect );
+ else
+ MapWindowPoints( HWND_DESKTOP, hwnd, (POINT *)&item->rect, 2 );
+
+ item->cx = item->rect.right - item->rect.left;
+ item->cy = item->rect.bottom - item->rect.top;
+ item->x = item->rect.left;
+ item->y = item->rect.top;
+ item->flags = 0;
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_Perform( HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw )
+{
+ HDWP hdwp, hdwpTemp;
+ hdwp = BeginDeferWindowPos( layoutCount );
+ if ( hdwp == NULL )
+ return FALSE;
+
+ UINT baseFlags = SWP_NOACTIVATE | SWP_NOZORDER;
+ if ( fRedraw == FALSE )
+ baseFlags |= ( SWP_NOREDRAW | SWP_NOCOPYBITS );
+
+ LAYOUTITEM *item;
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( item->hwnd == NULL )
+ continue;
+
+ UINT flags = baseFlags | ( item->flags & ~( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) );
+ if ( item->x == item->rect.left && item->y == item->rect.top )
+ flags |= SWP_NOMOVE;
+
+ if ( item->cx == ( item->rect.right - item->rect.left ) && item->cy == ( item->rect.bottom - item->rect.top ) )
+ flags |= SWP_NOSIZE;
+
+ if ( ( SWP_HIDEWINDOW & item->flags ) != 0 )
+ {
+ UINT windowStyle = GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( ( WS_VISIBLE & windowStyle ) != 0 )
+ {
+ SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE );
+ if ( FALSE != fRedraw )
+ {
+ RedrawWindow( hwnd, &item->rect, NULL, RDW_INVALIDATE | RDW_ERASE );
+ }
+ }
+ }
+
+ if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED ) & flags ) )
+ {
+ hdwpTemp = DeferWindowPos( hdwp, item->hwnd, NULL, item->x, item->y, item->cx, item->cy, flags );
+ if ( hdwpTemp == NULL )
+ break;
+
+ hdwp = hdwpTemp;
+ }
+ }
+
+ BOOL result = ( hdwp != NULL ) ? EndDeferWindowPos( hdwp ) : FALSE;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( NULL != item->hwnd && 0 != ( SWP_SHOWWINDOW & item->flags ) )
+ {
+ UINT windowStyle = GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( 0 == ( WS_VISIBLE & windowStyle ) )
+ {
+ SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle | WS_VISIBLE );
+ if ( FALSE != fRedraw )
+ RedrawWindow( item->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN );
+ }
+ }
+ }
+
+ return result;
+}
+
+static void Layout_SetItemVisibility( const RECT *rect, LAYOUTITEM *item )
+{
+ if ( NULL == item || NULL == item->hwnd )
+ return;
+
+ BOOL outsider = ( item->cx <= 0 || item->cy <= 0 ||
+ item->x >= rect->right || item->y >= rect->bottom ||
+ ( item->x + item->cx ) < rect->left || ( item->y + item->cy ) < rect->top );
+
+ UINT windowStyle = GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( 0 == ( WS_VISIBLE & windowStyle ) )
+ {
+ if ( !outsider )
+ {
+ item->flags |= SWP_SHOWWINDOW;
+ }
+ }
+ else
+ {
+ if ( outsider )
+ {
+ item->flags |= SWP_HIDEWINDOW;
+ }
+ }
+}
+
+BOOL Layout_SetVisibility( const RECT *rect, LAYOUTITEM *layout, INT layoutCount )
+{
+ if ( NULL == rect || NULL == layout )
+ return FALSE;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ Layout_SetItemVisibility( rect, &layout[ i ] );
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_SetVisibilityEx( const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout )
+{
+ if ( NULL == rect || NULL == indexList || NULL == layout )
+ return FALSE;
+
+ for ( INT i = 0; i < indexCount; i++ )
+ {
+ Layout_SetItemVisibility( rect, &layout[ indexList[ i ] ] );
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_GetValidRgn( HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount )
+{
+ if ( NULL == validRgn )
+ return FALSE;
+
+ SetRectRgn( validRgn, 0, 0, 0, 0 );
+
+ if ( NULL == layout )
+ return FALSE;
+
+ HRGN rgn = CreateRectRgn( 0, 0, 0, 0 );
+ if ( NULL == rgn )
+ return FALSE;
+
+ LAYOUTITEM *item;
+ LONG l, t, r, b;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( NULL != item->hwnd && 0 == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) & item->flags ) )
+ {
+ l = item->x + parrentOffset.x;
+ t = item->y + parrentOffset.y;
+ r = l + item->cx;
+ b = t + item->cy;
+ if ( 0 != ( SWP_NOREDRAW & item->flags ) || ( l == item->rect.left && t == item->rect.top && r == item->rect.right && b == item->rect.bottom ) )
+ {
+ if ( NULL != validRect )
+ {
+ if ( l < validRect->left )
+ l = validRect->left;
+
+ if ( t < validRect->top )
+ t = validRect->top;
+
+ if ( r > validRect->right )
+ r = validRect->right;
+
+ if ( b > validRect->bottom )
+ b = validRect->bottom;
+ }
+
+ if ( l < r && t < b )
+ {
+ SetRectRgn( rgn, l, t, r, b );
+ CombineRgn( validRgn, validRgn, rgn, RGN_OR );
+
+ if ( NULLREGION != GetUpdateRgn( item->hwnd, rgn, FALSE ) )
+ {
+ OffsetRgn( rgn, parrentOffset.x, parrentOffset.y );
+ CombineRgn( validRgn, validRgn, rgn, RGN_DIFF );
+ }
+ }
+ }
+ }
+ }
+
+ DeleteObject( rgn );
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/layout.h b/Src/Plugins/Library/ml_wire/layout.h
new file mode 100644
index 00000000..9af73396
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/layout.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef struct __LAYOUTITEM
+{
+ HWND hwnd;
+ LONG x;
+ LONG y;
+ LONG cx;
+ LONG cy;
+ UINT flags;
+ RECT rect;
+} LAYOUTITEM;
+
+
+#define LI_GET_R(__li) ((__li).x + (__li).cx)
+#define LI_GET_B(__li) ((__li).y + (__li).cy)
+#define LI_EXPAND_W(__li, __delta) { (__li).cx += (__delta); }
+#define LI_EXPAND_H(__li, __delta) { (__li).cy += (__delta); }
+#define LI_SHIFT_L(__li, __delta) { (__li).x += (__delta); }
+#define LI_SHIFT_T(__li, __delta) { { (__li).y += (__delta); }
+#define LI_SET_L(__li, __val) { (__li).x = (__val); }
+#define LI_SET_T(__li, __val) { (__li).y = (__val); }
+#define LI_SET_W(__li, __val) { (__li).cx = (__val); }
+#define LI_SET_H(__li, __val) { (__li).cy = (__val); }
+#define LI_SET_R(__li, __val) { (__li).cx = ((__val) - (__li).x); }
+#define LI_SET_B(__li, __val) { (__li).cy = ((__val) - (__li).y); }
+
+BOOL Layout_Initialize(HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout);
+BOOL Layout_SetVisibilityEx(const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout);
+BOOL Layout_SetVisibility(const RECT *rect, LAYOUTITEM *layout, INT layoutCount);
+BOOL Layout_Perform(HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw);
+
+BOOL Layout_GetValidRgn(HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount);
+
+
+#endif //NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/ml_podcast.rc b/Src/Plugins/Library/ml_wire/ml_podcast.rc
new file mode 100644
index 00000000..d75df578
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ml_podcast.rc
@@ -0,0 +1,396 @@
+// 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\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_PODCAST DIALOGEX 0, 0, 285, 277
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_CHANNELLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,0,122,181,WS_EX_CLIENTEDGE
+ CONTROL "",IDC_VDIV,"Static",SS_BLACKFRAME,122,0,5,182
+ CONTROL "",IDC_ITEMLIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,127,0,156,181,WS_EX_CLIENTEDGE
+ PUSHBUTTON "Directory",IDC_FINDNEW,2,183,41,11
+ PUSHBUTTON "Add",IDC_ADD,45,183,35,11
+ PUSHBUTTON "Edit",IDC_EDIT,83,183,35,11
+ PUSHBUTTON "Delete",IDC_DELETE,121,183,35,11
+ PUSHBUTTON "Update",IDC_REFRESH,159,183,35,11
+ CONTROL "",IDC_HDIV,"Static",SS_BLACKFRAME,0,194,285,5
+ CONTROL "",IDC_EPISODE_INFO,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,198,285,10,WS_EX_CLIENTEDGE
+ CONTROL "",IDC_DESCRIPTION,"Static",SS_BLACKRECT,0,208,283,55
+ PUSHBUTTON "Play",IDC_PLAY,0,266,37,11
+ PUSHBUTTON "Enqueue",IDC_ENQUEUE,39,266,37,11
+ PUSHBUTTON "Download",IDC_DOWNLOAD,78,266,40,11
+ PUSHBUTTON "Visit site",IDC_VISIT,120,266,40,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_WORDELLIPSIS | WS_GROUP,164,266,120,11
+END
+
+IDD_ADDURL DIALOGEX 0, 0, 270, 90
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Add RSS Subscription"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ EDITTEXT IDC_EDITURL,4,7,262,14,ES_AUTOHSCROLL
+ CONTROL "Use default settings",IDC_USEDEFAULTS,"Button",BS_AUTORADIOBUTTON,8,25,88,10
+ CONTROL "Use custom settings",IDC_USECUSTOM,"Button",BS_AUTORADIOBUTTON,114,25,93,10
+ LTEXT "Update Every:",IDC_STATIC_UPDATEEVERY,8,42,48,8,WS_DISABLED
+ COMBOBOX IDC_UPDATELIST,59,40,70,95,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Download New Episodes:",IDC_STATIC_AUTODOWNLOAD,8,58,81,8,WS_DISABLED
+ COMBOBOX IDC_AUTODOWNLOADLIST,93,56,122,30,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Add",IDOK,162,73,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,216,73,50,13
+END
+
+IDD_DOWNLOADS DIALOGEX 0, 0, 266, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_DOWNLOADLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,0,264,79
+ CONTROL "Play",IDC_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,81,37,11
+ CONTROL "Enqueue",IDC_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,40,81,37,11
+ CONTROL "Remove",IDC_REMOVE,"Button",BS_OWNERDRAW | WS_TABSTOP,80,81,37,11
+ CONTROL "Clean up",IDC_CLEANUP,"Button",BS_OWNERDRAW | WS_TABSTOP,120,81,37,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,163,81,102,11
+END
+
+IDD_PREFERENCES DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Podcasts",IDC_STATIC,0,0,272,246
+ GROUPBOX "Subscription Updates",IDC_STATIC,5,11,260,53
+ LTEXT "Update every:",IDC_STATICUPDATEEVERY,11,28,50,9,SS_CENTERIMAGE
+ COMBOBOX IDC_UPDATELIST,65,27,94,131,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Update on launch",IDC_UPDATEONLAUNCH,"Button",BS_AUTOCHECKBOX | BS_LEFT | WS_TABSTOP,181,28,72,10
+ LTEXT "Download New Episodes:",IDC_STATICAUTODOWNLOAD,11,47,84,9
+ COMBOBOX IDC_AUTODOWNLOADLIST,99,45,112,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "Podcast Download Location",IDC_STATIC,5,67,260,34
+ EDITTEXT IDC_DOWNLOADLOCATION,11,81,198,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BROWSE,209,81,50,13
+ GROUPBOX "'Podcast Directory' Service",IDC_STATIC,5,105,260,72
+ LTEXT "This allows you to specify an alternative Podcast Directory service to display in the 'Podcast Directory' node. Leave blank to reset to the default service.",IDC_STATIC,11,116,248,18
+ EDITTEXT IDC_DIRECTORYURL,11,138,248,13,ES_AUTOHSCROLL
+ LTEXT "Note: To subscribe to feeds when using an alternative Podcast Directory service, the service will need to use the available Winamp Javascript API.",IDC_STATIC,11,155,248,18,WS_DISABLED
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_ADDURL, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 266
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 86
+ END
+
+ IDD_PREFERENCES, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 246
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_MENU1 MENU
+BEGIN
+ POPUP "Podcast"
+ BEGIN
+ MENUITEM "Update podcast\tF5", IDC_REFRESH
+ MENUITEM "Edit podcast...\tF2", IDC_EDIT
+ MENUITEM "Delete podcast\tDel", IDC_DELETE
+ MENUITEM SEPARATOR
+ MENUITEM "Visit site\tF7", IDC_VISIT
+ MENUITEM SEPARATOR
+ MENUITEM "Add podcast...\tInsert", IDC_ADD
+ MENUITEM "Update all\tShift+F5", IDC_REFRESHALL
+ END
+ POPUP "Episode"
+ BEGIN
+ MENUITEM "Play media", IDC_PLAY
+ MENUITEM "Enqueue media", IDC_ENQUEUE
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_Menu
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Download media", IDC_DOWNLOAD
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", IDC_SELECTALL
+ MENUITEM "Explore media folder\tCtrl+F", ID_DOWNLOADS_EXPLORERITEMFOLDER
+ MENUITEM "Visit site\tF7", IDC_VISIT
+ MENUITEM SEPARATOR
+ MENUITEM "Update podcast\tF5", IDC_REFRESH
+ END
+ POPUP "Downloads"
+ BEGIN
+ MENUITEM "Play file\tEnter", IDC_PLAY
+ MENUITEM "Enqueue file\tShift+Enter", IDC_ENQUEUE
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_Menu
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", IDC_SELECTALL
+ MENUITEM "View file info...\tAlt+3", IDC_INFOBOX
+ MENUITEM "Explore item folder\tCtrl+F", ID_DOWNLOADS_EXPLORERITEMFOLDER
+ MENUITEM SEPARATOR
+ MENUITEM "Remove from list\tDel", IDC_REMOVE
+ MENUITEM "Delete file\tShift+Del", IDC_DELETE
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+ POPUP "SubscriptionNavigation"
+ BEGIN
+ MENUITEM "Directory", ID_NAVIGATION_DIRECTORY
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Update all", ID_NAVIGATION_REFRESHALL
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_DOWNLOAD_ACCELERATORS ACCELERATORS
+BEGIN
+ "F", ID_DOWNLOADS_EXPLORERITEMFOLDER, VIRTKEY, CONTROL, NOINVERT
+ VK_RETURN, IDC_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+ VK_DELETE, IDC_DELETE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ "3", IDC_INFOBOX, VIRTKEY, ALT, NOINVERT
+ VK_RETURN, IDC_PLAY, VIRTKEY, NOINVERT
+ VK_DELETE, IDC_REMOVE, VIRTKEY, NOINVERT
+ "A", IDC_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ VK_F7, IDC_VISIT, VIRTKEY, NOINVERT
+ VK_F5, IDC_REFRESH, VIRTKEY, NOINVERT
+ "D", IDC_DOWNLOAD, VIRTKEY, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Podcasts v%d.%02d"
+ 65535 "{1FF327B2-A41D-4c67-A58A-EB09BA1470D3}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PODCASTS "Podcasts"
+ IDS_DOWNLOADING_KB_COMPLETE "Downloading %u files, %ukb complete."
+ IDS_DOWNLOADING_KB_PROGRESS
+ "Downloading %u files, %ukb of %ukb complete (%d%%)."
+ IDS_SUBSCRIPTIONS "Subscriptions"
+ IDS_DOWNLOADS "Downloads"
+ IDS_DOWNLOADING "Downloading..."
+ IDS_CHOOSE_FOLDER "Choose folder to store downloaded media."
+ IDS_ALREADY_SUBSCRIBED "You are already subscribed to %s.\n%s"
+ IDS_FILE_NOT_FOUND "Cannot connect to %s\nFile not Found."
+ IDS_CONNECTION_TIMED_OUT "Cannot connect to %s\nConnection timed out."
+ IDS_ERROR_PARSING_XML "Subscription to %s failed.\nError parsing XML data from server."
+ IDS_INVALID_RSS_FEED "Subscription to %s failed.\nDoes not appear to be a valid RSS feed."
+ IDS_NO_JNETLIB "HTTP Downloader library is missing.\nPlease reinstall Winamp."
+ IDS_JNETLIB_MISSING "JNetLib missing"
+ IDS_NO_EXPAT "XML Parsing library is missing.\nPlease reinstall Winamp."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_EXPAT_MISSING "Expat missing"
+ IDS_CONNECTION_RESET "Subscription to %s failed.\nConnection reset by peer."
+ IDS_ERROR_SUBSCRIBING_TO_PODCAST "Error Subscribing to Podcast"
+ IDS_UPD_MANUALLY "Manually"
+ IDS_UPD_WEEK "Week"
+ IDS_UPD_DAY "Day"
+ IDS_UPD_12HRS "12 hours"
+ IDS_UPD_6HRS "6 hours"
+ IDS_UPD_3HRS "3 hours"
+ IDS_UPD_HOUR "Hour"
+ IDS_UPD_30MINS "30 minutes"
+ IDS_ERROR_FYEO "ERROR ! ! YOU SHOULDN'T SEE THIS"
+ IDS_WDAY_SUN "Sun"
+ IDS_WDAY_MON "Mon"
+ IDS_WDAY_TUE "Tue"
+ IDS_WDAY_WED "Wed"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_WDAY_THU "Thu"
+ IDS_WDAY_FRI "Fri"
+ IDS_WDAY_SAT "Sat"
+ IDS_MONTH_JAN "Jan"
+ IDS_MONTH_FEB "Feb"
+ IDS_MONTH_MAR "Mar"
+ IDS_MONTH_APR "Apr"
+ IDS_MONTH_MAY "May"
+ IDS_MONTH_JUN "Jun"
+ IDS_MONTH_JUL "Jul"
+ IDS_MONTH_AUG "Aug"
+ IDS_MONTH_SEP "Sep"
+ IDS_MONTH_OCT "Oct"
+ IDS_MONTH_NOV "Nov"
+ IDS_MONTH_DEC "Dec"
+ IDS_RECEIVING_UPDATES_FOR "Retrieving updates for "
+END
+
+STRINGTABLE
+BEGIN
+ IDS_GOT_NEW_ITEMS_FOR "Got new items for "
+ IDS_CHANNEL_ALREADY_PRESENT "Podcast Already Present:\n%s\n%s"
+ IDS_DUPLICATE_CHANNEL "Duplicate Podcast"
+ IDS_ERROR_ADDING_URL "Error adding URL"
+ IDS_ERROR_PARSING_XML_FROM_SERVER "Error parsing XML data from server"
+ IDS_LINK_HAS_NO_RSS_INFO "Link does not contain valid RSS information"
+ IDS_INVALID_LINK "Invalid link (404 or timeout)"
+ IDS_CONNECTION_RESET_BY_PEER "Connection reset by peer."
+ IDS_DOWNLOADING_PERCENT "Downloading %d%%"
+ IDS_CHANNEL "Podcast"
+ IDS_ITEM "Episode"
+ IDS_PROGRESS "Progress"
+ IDS_PATH "Path"
+ IDS_PERM_DELETE_ARE_YOU_SURE
+ "This will permanently delete this file, are you sure?"
+ IDS_PERM_DELETE_THESE_ARE_YOU_SURE
+ "This will permanently delete these %d files, are you sure?"
+ IDS_DELETION "Deletion"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLEAR_ALL_FINISHED_DOWNLOADS
+ "This will clear all finished downloads, are you sure?\n\nTip: You can view all your downloaded podcasts in Local Media/Podcasts"
+ IDS_CLEAN_UP_LIST "Clean Up List"
+ IDS_DONE "Done"
+ IDS_SAVE "Save"
+ IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION
+ "The media library feature you are attempting to use requires an internet connection. Please make sure you are connected to the internet and try again."
+ IDS_ADD_TO_DOWNLOADS "Added to downloads."
+ IDS_NO_MEDIA_TO_DOWNLOAD "No media to download."
+ IDS_TEXT_ARTICLE "Text article"
+ IDS_DATE_ADDED "Date Added"
+ IDS_MEDIA_PRESENT "Media Present"
+ IDS_SURE_YOU_WANT_TO_REMOVE_THIS
+ "Are you sure you want to remove %s\n(%s)"
+ IDS_CONFIRM "Confirm"
+ IDS_CANCEL_DOWNLOADS_AND_QUIT
+ "You are currently downloading podcasts\nAre you sure you want to cancel these downloads and quit?"
+ IDS_CONFIRM_QUIT "Confirm Quit"
+ IDS_ARTICLE_WITH_MEDIA "Article with media"
+ IDS_PODCAST_DIRECTORY "Podcast Directory"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DELETEFAILED "Delete Failed"
+ IDS_SORRY "Sorry"
+ IDS_EDIT_CHANNEL "Edit Podcast"
+ IDS_ATD_NEVER "Never"
+ IDS_ATD_LASTONE "Most recent episode"
+ IDS_ATD_LASTTWO "Last 2 episodes"
+ IDS_ATD_LASTTHREE "Last 3 episodes"
+ IDS_ATD_LASTFIVE "Last 5 episodes"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PODCAST_SUBSCRIPTION_HEADER "Winamp Podcast Subscription"
+ IDS_PODCAST_SUBSCRIPTION_PROMP
+ "Are you sure that you want to subscribe to this podcast?\n\n%s"
+ IDS_MEDIA_TIME "Time"
+ IDS_EPISODE_INFO "Episode Info"
+ IDS_MEDIA_SIZE "Size"
+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_wire/ml_wire.sln b/Src/Plugins/Library/ml_wire/ml_wire.sln
new file mode 100644
index 00000000..2c3763b8
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ml_wire.sln
@@ -0,0 +1,87 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_wire", "ml_wire.vcxproj", "{2BF2E8A5-18E0-47B4-822C-FF17077926FD}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
+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
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|Win32.ActiveCfg = Debug|Win32
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|Win32.Build.0 = Debug|Win32
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|x64.ActiveCfg = Debug|x64
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|x64.Build.0 = Debug|x64
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|Win32.ActiveCfg = Release|Win32
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|Win32.Build.0 = Release|Win32
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|x64.ActiveCfg = Release|x64
+ {2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_wire/ml_wire.vcxproj b/Src/Plugins/Library/ml_wire/ml_wire.vcxproj
new file mode 100644
index 00000000..8562012d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ml_wire.vcxproj
@@ -0,0 +1,423 @@
+<?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>{2BF2E8A5-18E0-47B4-822C-FF17077926FD}</ProjectGuid>
+ <RootNamespace>ml_wire</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;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_DEBUG;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <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;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_DEBUG;WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <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>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;IGNORE_API_GRACENOTE;NDEBUG;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>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>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;IGNORE_API_GRACENOTE;NDEBUG;WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;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>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <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>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\WAT\WAT.vcxproj">
+ <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\Alias.h" />
+ <ClInclude Include="..\..\..\nu\ChildSizer.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\xml\XMLDOM.h" />
+ <ClInclude Include="..\..\..\xml\XMLNode.h" />
+ <ClInclude Include="api__ml_wire.h" />
+ <ClInclude Include="api_podcasts.h" />
+ <ClInclude Include="BackgroundDownloader.h" />
+ <ClInclude Include="ChannelCheck.h" />
+ <ClInclude Include="channelEditor.h" />
+ <ClInclude Include="ChannelRefresher.h" />
+ <ClInclude Include="ChannelSync.h" />
+ <ClInclude Include="Cloud.h" />
+ <ClInclude Include="Defaults.h" />
+ <ClInclude Include="Downloaded.h" />
+ <ClInclude Include="DownloadsDialog.h" />
+ <ClInclude Include="DownloadsParse.h" />
+ <ClInclude Include="DownloadStatus.h" />
+ <ClInclude Include="DownloadThread.h" />
+ <ClInclude Include="errors.h" />
+ <ClInclude Include="ExternalCOM.h" />
+ <ClInclude Include="Factory.h" />
+ <ClInclude Include="FeedParse.h" />
+ <ClInclude Include="Feeds.h" />
+ <ClInclude Include="FeedUtil.h" />
+ <ClInclude Include="ifc_article.h" />
+ <ClInclude Include="ifc_podcast.h" />
+ <ClInclude Include="Item.h" />
+ <ClInclude Include="JSAPI2_Creator.h" />
+ <ClInclude Include="JSAPI2_PodcastsAPI.h" />
+ <ClInclude Include="layout.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="navigation.h" />
+ <ClInclude Include="OPMLParse.h" />
+ <ClInclude Include="ParseUtil.h" />
+ <ClInclude Include="PCastFactory.h" />
+ <ClInclude Include="PCastURIHandler.h" />
+ <ClInclude Include="Preferences.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="RFCDate.h" />
+ <ClInclude Include="RSSCOM.h" />
+ <ClInclude Include="RSSParse.h" />
+ <ClInclude Include="service.h" />
+ <ClInclude Include="subscriptionView.h" />
+ <ClInclude Include="UpdateAutoDownload.h" />
+ <ClInclude Include="UpdateTime.h" />
+ <ClInclude Include="Util.h" />
+ <ClInclude Include="WantsDownloadStatus.h" />
+ <ClInclude Include="Wire.h" />
+ <ClInclude Include="XMLWriter.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp" />
+ <ClCompile Include="..\..\..\xml\XMLNode.cpp" />
+ <ClCompile Include="BackgroundDownloader.cpp" />
+ <ClCompile Include="channelEditor.cpp" />
+ <ClCompile Include="ChannelRefresher.cpp" />
+ <ClCompile Include="Cloud.cpp" />
+ <ClCompile Include="db.cpp" />
+ <ClCompile Include="Defaults.cpp" />
+ <ClCompile Include="Downloaded.cpp" />
+ <ClCompile Include="DownloadsDialog.cpp" />
+ <ClCompile Include="DownloadsParse.cpp" />
+ <ClCompile Include="DownloadStatus.cpp" />
+ <ClCompile Include="DownloadThread.cpp" />
+ <ClCompile Include="ExternalCOM.cpp" />
+ <ClCompile Include="Factory.cpp" />
+ <ClCompile Include="FeedParse.cpp" />
+ <ClCompile Include="Feeds.cpp" />
+ <ClCompile Include="FeedUtil.cpp" />
+ <ClCompile Include="item.cpp" />
+ <ClCompile Include="JSAPI2_Creator.cpp" />
+ <ClCompile Include="JSAPI2_PodcastsAPI.cpp" />
+ <ClCompile Include="layout.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="navigation.cpp" />
+ <ClCompile Include="OPMLParse.cpp" />
+ <ClCompile Include="ParseUtil.cpp" />
+ <ClCompile Include="PCastFactory.cpp" />
+ <ClCompile Include="PCastURIHandler.cpp" />
+ <ClCompile Include="Preferences.cpp" />
+ <ClCompile Include="RFCDate.cpp" />
+ <ClCompile Include="RSSCOM.cpp" />
+ <ClCompile Include="RSSParse.cpp" />
+ <ClCompile Include="service.cpp" />
+ <ClCompile Include="subscriptionView.cpp" />
+ <ClCompile Include="UpdateAutoDownload.cpp" />
+ <ClCompile Include="UpdateTime.cpp" />
+ <ClCompile Include="util.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="Wire.cpp" />
+ <ClCompile Include="XMLWriter.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_podcast.rc" />
+ <ResourceCompile Include="png.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="DESIGN.txt" />
+ <Text Include="TODO.txt" />
+ </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_wire/ml_wire.vcxproj.filters b/Src/Plugins/Library/ml_wire/ml_wire.vcxproj.filters
new file mode 100644
index 00000000..afe62d41
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/ml_wire.vcxproj.filters
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="BackgroundDownloader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="channelEditor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ChannelRefresher.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Cloud.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="db.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Defaults.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Downloaded.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadsDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadsParse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadStatus.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadThread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ExternalCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Factory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FeedParse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Feeds.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FeedUtil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="item.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Creator.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_PodcastsAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="layout.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="navigation.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="OPMLParse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ParseUtil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PCastFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PCastURIHandler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="XMLWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Preferences.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RFCDate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RSSCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RSSParse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="service.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="subscriptionView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="UpdateAutoDownload.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="UpdateTime.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Wire.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLNode.cpp">
+ <Filter>Source Files\XML</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp">
+ <Filter>Source Files\XML</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_wire.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_podcasts.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="BackgroundDownloader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ChannelCheck.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="channelEditor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ChannelRefresher.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ChannelSync.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Cloud.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Defaults.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Downloaded.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadsDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadsParse.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadStatus.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadThread.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="errors.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ExternalCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Factory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FeedParse.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Feeds.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FeedUtil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ifc_article.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ifc_podcast.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Item.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Creator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_PodcastsAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="layout.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="navigation.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="OPMLParse.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ParseUtil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PCastFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PCastURIHandler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Preferences.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RFCDate.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RSSCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RSSParse.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="service.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="subscriptionView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="UpdateAutoDownload.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="UpdateTime.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="WantsDownloadStatus.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Wire.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="XMLWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\Alias.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ChildSizer.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\xml\XMLNode.h">
+ <Filter>Header Files\xml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\xml\XMLDOM.h">
+ <Filter>Header Files\xml</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_podcast.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="png.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="DESIGN.txt" />
+ <Text Include="TODO.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{04424438-0f00-45d3-957e-f60ad4e3addc}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{ab23d144-c22c-4693-98c5-bc8255a28f57}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{8ca72875-5c2c-4336-984e-9aa6e1b15b2f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{d8fe42ef-d543-46b2-9045-646ded85494d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{e6bd8d09-5297-423e-943c-22b3f0982d40}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\XML">
+ <UniqueIdentifier>{59f272b6-8c65-4db5-95b5-bca0712a07a8}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{d47a4f2b-363d-4281-aa4f-d76fdb3a22f6}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{df263acf-3409-47f8-9bee-6090095c081c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\xml">
+ <UniqueIdentifier>{b38287f4-c45a-41f8-9787-fe36ed08cb08}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/navigation.cpp b/Src/Plugins/Library/ml_wire/navigation.cpp
new file mode 100644
index 00000000..b0cc9621
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/navigation.cpp
@@ -0,0 +1,420 @@
+#include <strsafe.h>
+
+#include "main.h"
+#include "./navigation.h"
+#include "./util.h"
+#include "./resource.h"
+#include "api__ml_wire.h"
+#include "./service.h"
+#include "./subscriptionView.h"
+#include "./downloadsDialog.h"
+#include "../omBrowser/browserView.h"
+#include "../winamp/wa_ipc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "./Defaults.h"
+
+
+#define NAVITEM_PREFIX L"podcast_svc_"
+
+#define E_NAVITEM_UNKNOWN E_NOINTERFACE
+
+static Nullsoft::Utility::LockGuard navigationLock;
+
+static INT Navigation_RegisterIcon( HWND hLibrary, INT iconIndex, LPCWSTR pszImage )
+{
+ HMLIMGLST hmlilNavigation = MLNavCtrl_GetImageList( hLibrary );
+ if ( hmlilNavigation == NULL )
+ return -1;
+
+ MLIMAGESOURCE mlis;
+ ZeroMemory( &mlis, sizeof( MLIMAGESOURCE ) );
+
+ mlis.cbSize = sizeof( MLIMAGESOURCE );
+ mlis.hInst = NULL;
+ mlis.bpp = 24;
+ mlis.lpszName = pszImage;
+ mlis.type = SRC_TYPE_PNG;
+ mlis.flags = ISF_FORCE_BPP | ISF_PREMULTIPLY | ISF_LOADFROMFILE;
+
+ MLIMAGELISTITEM item;
+ ZeroMemory( &item, sizeof( MLIMAGELISTITEM ) );
+ item.cbSize = sizeof( MLIMAGELISTITEM );
+ item.hmlil = hmlilNavigation;
+ item.filterUID = MLIF_FILTER3_UID;
+ item.pmlImgSource = &mlis;
+
+ if (iconIndex >= 0)
+ {
+ INT count = MLImageList_GetImageCount( hLibrary, item.hmlil );
+ if (iconIndex < count)
+ {
+ item.mlilIndex = iconIndex;
+
+ return (FALSE != MLImageList_Replace(hLibrary, &item)) ? iconIndex : -1;
+ }
+ }
+
+
+ return MLImageList_Add( hLibrary, &item );
+}
+
+static HNAVITEM Navigation_CreateItem( HWND hLibrary, HNAVITEM hParent, OmService *service )
+{
+ if ( hLibrary == NULL || service == NULL )
+ return NULL;
+
+ WCHAR szName[ 256 ] = { 0 }, szInvariant[ 64 ] = { 0 };
+ if ( FAILED( service->GetName( szName, ARRAYSIZE( szName ) ) ) )
+ return NULL;
+
+ if ( FAILED( StringCchPrintf( szInvariant, ARRAYSIZE( szInvariant ), NAVITEM_PREFIX L"%u", service->GetId() ) ) )
+ return NULL;
+
+ NAVINSERTSTRUCT nis = { 0 };
+ nis.hInsertAfter = NULL;
+ nis.hParent = hParent;
+
+ WCHAR szIcon[ 512 ] = { 0 };
+ INT iIcon = ( SUCCEEDED( service->GetIcon( szIcon, ARRAYSIZE( szIcon ) ) ) ) ? Navigation_RegisterIcon( hLibrary, -1, szIcon ) : -1;
+
+ nis.item.cbSize = sizeof( NAVITEM );
+ nis.item.mask = NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT | NIMF_PARAM | NIMF_IMAGE | NIMF_IMAGESEL;
+
+ nis.item.id = 0;
+ nis.item.pszText = szName;
+ nis.item.pszInvariant = szInvariant;
+ nis.item.lParam = (LPARAM)service;
+
+ nis.item.style = 0;
+
+ UINT serviceFlags = service->GetFlags();
+
+ if ( ( OmService::flagRoot & serviceFlags ) != 0 )
+ nis.item.style |= ( NIS_HASCHILDREN | NIS_DEFAULTIMAGE );
+
+ nis.item.styleMask = nis.item.style;
+
+ nis.item.iImage = iIcon;
+ nis.item.iSelectedImage = iIcon;
+
+ HNAVITEM hItem = MLNavCtrl_InsertItem( hLibrary, &nis );
+ if ( hItem != NULL )
+ service->AddRef();
+
+ return hItem;
+}
+
+static HNAVITEM Navigation_GetMessageItem( INT msg, INT_PTR param1 )
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+ HNAVITEM hItem = ( msg < ML_MSG_NAVIGATION_FIRST ) ? MLNavCtrl_FindItemById( hLibrary, param1 ) : (HNAVITEM)param1;
+
+ return hItem;
+}
+
+static BOOL Navigation_CheckInvariantName( LPCWSTR pszInvarian )
+{
+ INT cchInvariant = ( NULL != pszInvarian ) ? lstrlen( pszInvarian ) : 0;
+ INT cchPrefix = ARRAYSIZE( NAVITEM_PREFIX ) - 1;
+
+ return ( cchInvariant > cchPrefix &&
+ CompareString( CSTR_INVARIANT, 0, NAVITEM_PREFIX, cchPrefix, pszInvarian, cchPrefix ) == CSTR_EQUAL );
+}
+
+static HRESULT Navigation_GetServiceInt( HWND hLibrary, HNAVITEM hItem, OmService **service )
+{
+ WCHAR szBuffer[ 64 ] = { 0 };
+
+ if ( service == NULL )
+ return E_POINTER;
+
+ *service = NULL;
+
+ if ( NULL == hLibrary || NULL == hItem )
+ return E_INVALIDARG;
+
+ NAVITEM itemInfo = {0};
+ itemInfo.cbSize = sizeof( NAVITEM );
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE( szBuffer );
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT;
+
+ if ( FALSE == MLNavItem_GetInfo( hLibrary, &itemInfo ) )
+ return E_FAIL;
+
+ if ( FALSE == Navigation_CheckInvariantName( szBuffer ) )
+ return E_NAVITEM_UNKNOWN;
+
+ *service = (OmService *)itemInfo.lParam;
+ (*service)->AddRef();
+
+ return S_OK;
+}
+
+HRESULT Navigation_GetService( HNAVITEM hItem, OmService **service )
+{
+ return Navigation_GetServiceInt( plugin.hwndLibraryParent, hItem, service );
+}
+
+static HRESULT Navigation_CreateView( HNAVITEM hItem, HWND hParent, HWND *hView )
+{
+ if ( NULL == hView )
+ return E_POINTER;
+
+ *hView = NULL;
+
+ if ( hItem == NULL || hParent == NULL )
+ return E_INVALIDARG;
+
+ HRESULT hr;
+
+ OmService *service;
+ hr = Navigation_GetServiceInt( plugin.hwndLibraryParent, hItem, &service );
+ if ( SUCCEEDED( hr ) )
+ {
+ if ( service->GetId() == SERVICE_PODCAST )
+ service->SetUrl( serviceUrl[ 0 ] ? serviceUrl : L"https://client.winamp.com/podcasts" );
+
+ hr = service->CreateView( hParent, hView );
+ service->Release();
+ }
+
+ return hr;
+}
+
+static void Navigation_OnDestroy()
+{
+ if ( OMBROWSERMNGR != NULL )
+ OMBROWSERMNGR->Finish();
+}
+
+HNAVITEM Navigation_FindService( UINT serviceId, HNAVITEM hStart, OmService **serviceOut )
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+
+ INT cchPrefix = ARRAYSIZE( NAVITEM_PREFIX ) - 1;
+
+ WCHAR szBuffer[256] = {0};
+ NAVITEM itemInfo = {0};
+
+ itemInfo.cbSize = sizeof( itemInfo );
+ itemInfo.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
+ itemInfo.cchInvariantMax = ARRAYSIZE( szBuffer );
+ itemInfo.pszInvariant = szBuffer;
+
+ if ( hStart == NULL )
+ hStart = MLNavCtrl_GetFirst( hLibrary );
+
+ itemInfo.hItem = hStart;
+ while ( itemInfo.hItem != NULL )
+ {
+ if ( FALSE != MLNavItem_GetInfo( hLibrary, &itemInfo ) &&
+ CSTR_EQUAL == CompareString( CSTR_INVARIANT, NORM_IGNORECASE, itemInfo.pszInvariant, cchPrefix,
+ NAVITEM_PREFIX, cchPrefix ) )
+ {
+ OmService *service = (OmService *)itemInfo.lParam;
+ if ( service != NULL && service->GetId() == serviceId )
+ {
+ if ( serviceOut != NULL )
+ {
+ *serviceOut = service;
+ service->AddRef();
+ }
+
+ return itemInfo.hItem;
+ }
+ }
+
+ itemInfo.hItem = MLNavItem_GetNext( hLibrary, itemInfo.hItem );
+ }
+
+ if ( serviceOut != NULL )
+ *serviceOut = NULL;
+
+ return NULL;
+}
+
+int downloadsViewLoaded = -1;
+
+static int Navigation_CheckDownloadsView()
+{
+ if ( downloadsViewLoaded == -1 )
+ {
+ pluginMessage p = { ML_MSG_DOWNLOADS_VIEW_LOADED, 0, 0, 0 };
+ downloadsViewLoaded = SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p );
+ }
+
+ return downloadsViewLoaded;
+}
+
+HRESULT Navigation_ShowService( UINT serviceId, INT showMode )
+{
+ if ( serviceId == SERVICE_DOWNLOADS && Navigation_CheckDownloadsView() )
+ return S_OK;
+
+ Nullsoft::Utility::AutoLock lock3( navigationLock );
+
+ HNAVITEM hRoot = Navigation_FindService( SERVICE_PODCAST, NULL, NULL );
+ if( hRoot == NULL )
+ return E_UNEXPECTED;
+
+ switch ( serviceId )
+ {
+ case SERVICE_SUBSCRIPTION:
+ if ( SHOWMODE_AUTO == showMode )
+ return E_INVALIDARG;
+ break;
+
+ case SERVICE_DOWNLOADS:
+ if ( SHOWMODE_AUTO == showMode )
+ {
+ Nullsoft::Utility::AutoLock lock1( downloadedFiles.downloadedLock );
+ Nullsoft::Utility::AutoLock lock2( downloadStatus.statusLock );
+ showMode = ( 0 != downloadedFiles.downloadList.size() || 0 != downloadStatus.downloads.size() ) ? SHOWMODE_SHOW : SHOWMODE_HIDE;
+ }
+ break;
+ default:
+ return E_INVALIDARG;
+ }
+
+ if ( showMode != SHOWMODE_HIDE && showMode != SHOWMODE_SHOW )
+ return E_INVALIDARG;
+
+ HWND hLibrary = plugin.hwndLibraryParent;
+
+ MLNavCtrl_BeginUpdate( hLibrary, NUF_LOCK_TOP );
+
+ OmService *service;
+ HNAVITEM hItem = MLNavItem_GetChild( hLibrary, hRoot );
+ hItem = Navigation_FindService( serviceId, hItem, &service );
+
+ HRESULT hr = S_OK;
+
+ if ( showMode == SHOWMODE_HIDE )
+ {
+ if ( hItem == NULL )
+ hr = S_FALSE;
+ else if ( MLNavCtrl_DeleteItem( hLibrary, hItem ) == FALSE )
+ hr = E_FAIL;
+ }
+ else
+ {
+ if ( hItem != NULL )
+ hr = S_FALSE;
+ else
+ {
+ switch ( serviceId )
+ {
+ case SERVICE_SUBSCRIPTION:
+ hr = OmService::CreateLocal( SERVICE_SUBSCRIPTION, MAKEINTRESOURCE( IDS_SUBSCRIPTIONS ), MAKEINTRESOURCE( IDR_SUBSCRIPTION_ICON ), SubscriptionView_Create, &service );
+ break;
+ case SERVICE_DOWNLOADS:
+ hr = OmService::CreateLocal( SERVICE_DOWNLOADS, MAKEINTRESOURCE( IDS_DOWNLOADS ), MAKEINTRESOURCE( IDR_DOWNLOAD_ICON ), DownloadDialog_Create, &service );
+ break;
+ default:
+ hr = E_UNEXPECTED;
+ break;
+ }
+
+ if ( SUCCEEDED( hr ) )
+ {
+ if ( Navigation_CreateItem( hLibrary, hRoot, service ) == NULL )
+ hr = E_FAIL;
+ }
+ }
+ }
+
+ if ( service != NULL )
+ service->Release();
+
+ MLNavCtrl_EndUpdate( hLibrary );
+
+ return hr;
+}
+
+BOOL Navigation_Initialize( void )
+{
+ HNAVITEM hParent = NULL;
+ OmService *service = NULL;
+ HWND hLibrary = plugin.hwndLibraryParent;
+
+ MLNavCtrl_BeginUpdate( hLibrary, NUF_LOCK_TOP );
+
+ if ( SUCCEEDED( OmService::CreateRemote( SERVICE_PODCAST, MAKEINTRESOURCE( IDS_PODCAST_DIRECTORY ),
+ MAKEINTRESOURCE( IDR_DISCOVER_ICON ),
+ ( serviceUrl[ 0 ] ? serviceUrl : L"https://client.winamp.com/podcasts" ), &service ) ) )
+ {
+ service->SetFlags( OmService::flagRoot, OmService::flagRoot );
+ hParent = Navigation_CreateItem( hLibrary, hParent, service );
+ service->Release();
+ }
+
+ if ( hParent != NULL )
+ {
+ Navigation_ShowService( SERVICE_SUBSCRIPTION, SHOWMODE_SHOW );
+ //Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+ }
+
+ MLNavCtrl_EndUpdate( hLibrary );
+
+ return TRUE;
+}
+
+static void Navigation_OnDeleteItem( HNAVITEM hItem )
+{
+ if ( hItem == NULL )
+ return;
+
+ HWND hLibrary = plugin.hwndLibraryParent;
+ if ( hLibrary == NULL )
+ return;
+
+ WCHAR szBuffer[64] = {0};
+ NAVITEM itemInfo = {0};
+
+ itemInfo.cbSize = sizeof( itemInfo );
+ itemInfo.hItem = hItem;
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.cchInvariantMax = ARRAYSIZE( szBuffer );
+ itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT | NIMF_IMAGE;
+
+ if ( MLNavItem_GetInfo( hLibrary, &itemInfo ) != FALSE && Navigation_CheckInvariantName( szBuffer ) != FALSE )
+ {
+ OmService *service = (OmService *)itemInfo.lParam;
+
+ itemInfo.mask = NIMF_PARAM;
+ itemInfo.lParam = 0L;
+
+ MLNavItem_SetInfo( hLibrary, &itemInfo );
+
+ service->Release();
+ }
+}
+
+
+BOOL Navigation_ProcessMessage( INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result )
+{
+ if (msg < ML_MSG_TREE_BEGIN || msg > ML_MSG_TREE_END)
+ return FALSE;
+
+ switch ( msg )
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ {
+ HWND hView = NULL;
+ HRESULT hr = Navigation_CreateView( Navigation_GetMessageItem( msg, param1 ), (HWND)param2, &hView );
+ *result = ( SUCCEEDED( hr ) ) ? (INT_PTR)hView : NULL;
+
+ return TRUE;
+ }
+ case ML_MSG_NAVIGATION_ONDESTROY:
+ Navigation_OnDestroy();
+ return TRUE;
+ case ML_MSG_NAVIGATION_ONDELETE:
+ Navigation_OnDeleteItem( Navigation_GetMessageItem( msg, param1 ) );
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/Src/Plugins/Library/ml_wire/navigation.h b/Src/Plugins/Library/ml_wire/navigation.h
new file mode 100644
index 00000000..1258739d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/navigation.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_NAVIGATION_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_NAVIGATION_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef LPVOID HNAVITEM;
+class OmService;
+
+
+BOOL Navigation_Initialize(void);
+BOOL Navigation_ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result);
+
+#define SHOWMODE_HIDE ((INT)0)
+#define SHOWMODE_SHOW ((INT)1)
+#define SHOWMODE_AUTO ((INT)-1)
+
+HRESULT Navigation_ShowService(UINT serviceId, INT showMode);
+HNAVITEM Navigation_FindService(UINT serviceId, HNAVITEM hStart, OmService **serviceOut);
+
+#endif //NULLSOFT_PODCAST_PLUGIN_NAVIGATION_HEADER
diff --git a/Src/Plugins/Library/ml_wire/png.rc b/Src/Plugins/Library/ml_wire/png.rc
new file mode 100644
index 00000000..ebe3b645
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/png.rc
@@ -0,0 +1,15 @@
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Data
+//
+IDR_DISCOVER_ICON RCDATA
+".\\resources\\discoverIcon.png"
+IDR_DOWNLOAD_ICON RCDATA
+".\\resources\\downloadIcon.png"
+IDR_SUBSCRIPTION_ICON RCDATA
+".\\resources\\subscriptionIcon.png"
+IDR_MEDIA_ICON RCDATA
+".\\resources\\mediaIcon.png"
+IDR_TEXT_ICON RCDATA
+".\\resources\\textIcon.png" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/resource.h b/Src/Plugins/Library/ml_wire/resource.h
new file mode 100644
index 00000000..7c41d736
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/resource.h
@@ -0,0 +1,220 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_podcast.rc
+//
+#define IDS_NULLSOFT_PODCAST 0
+#define IDS_PODCASTS 1
+#define IDS_DOWNLOADING_KB_COMPLETE 2
+#define IDS_DOWNLOADING_KB_PROGRESS 3
+#define IDS_SUBSCRIPTIONS 4
+#define IDS_DOWNLOADS 5
+#define IDS_DOWNLOADING 6
+#define IDS_CHOOSE_FOLDER 7
+#define IDS_ALREADY_SUBSCRIBED 8
+#define IDD_PODCAST 9
+#define IDS_FILE_NOT_FOUND 9
+#define IDS_CONNECTION_TIMED_OUT 10
+#define IDS_ERROR_PARSING_XML 11
+#define IDS_INVALID_RSS_FEED 12
+#define IDS_NO_JNETLIB 13
+#define IDS_JNETLIB_MISSING 14
+#define IDS_NO_EXPAT 15
+#define IDS_EXPAT_MISSING 16
+#define IDS_CONNECTION_RESET 17
+#define IDS_ERROR_SUBSCRIBING_TO_PODCAST 18
+#define IDS_UPD_MANUALLY 19
+#define IDS_UPD_WEEK 20
+#define IDS_UPD_DAY 21
+#define IDS_UPD_12HRS 22
+#define IDS_UPD_6HRS 23
+#define IDS_UPD_3HRS 24
+#define IDS_UPD_HOUR 25
+#define IDS_UPD_30MINS 26
+#define IDS_ERROR_FYEO 27
+#define IDS_WDAY_SUN 28
+#define IDS_WDAY_MON 29
+#define IDS_WDAY_TUE 30
+#define IDS_WDAY_WED 31
+#define IDS_WDAY_THU 32
+#define IDS_WDAY_FRI 33
+#define IDS_WDAY_SAT 34
+#define IDS_MONTH_JAN 35
+#define IDS_MONTH_FEB 36
+#define IDS_MONTH_MAR 37
+#define IDS_MONTH_APR 38
+#define IDS_MONTH_MAY 39
+#define IDS_MONTH_JUN 40
+#define IDS_MONTH_JUL 41
+#define IDS_MONTH_AUG 42
+#define IDS_MONTH_SEP 43
+#define IDS_MONTH_OCT 44
+#define IDS_MONTH_NOV 45
+#define IDS_MONTH_DEC 46
+#define IDS_RECEIVING_UPDATES_FOR 47
+#define IDS_GOT_NEW_ITEMS_FOR 48
+#define IDS_CHANNEL_ALREADY_PRESENT 49
+#define IDS_DUPLICATE_CHANNEL 50
+#define IDS_ERROR_ADDING_URL 51
+#define IDS_ERROR_PARSING_XML_FROM_SERVER 52
+#define IDS_LINK_HAS_NO_RSS_INFO 53
+#define IDS_INVALID_LINK 54
+#define IDS_CONNECTION_RESET_BY_PEER 55
+#define IDS_DOWNLOADING_PERCENT 56
+#define IDS_CHANNEL 57
+#define IDS_ITEM 58
+#define IDS_PROGRESS 59
+#define IDS_PATH 60
+#define IDS_PERM_DELETE_ARE_YOU_SURE 61
+#define IDS_PERM_DELETE_THESE_ARE_YOU_SURE 62
+#define IDS_DELETION 63
+#define IDS_CLEAR_ALL_FINISHED_DOWNLOADS 64
+#define IDS_CLEAN_UP_LIST 65
+#define IDS_DONE 66
+#define IDS_SAVE 67
+#define IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION 68
+#define IDS_ADD_TO_DOWNLOADS 69
+#define IDS_NO_MEDIA_TO_DOWNLOAD 70
+#define IDS_TEXT_ARTICLE 71
+#define IDS_DATE_ADDED 72
+#define IDS_MEDIA_PRESENT 73
+#define IDS_SURE_YOU_WANT_TO_REMOVE_THIS 74
+#define IDS_CONFIRM 75
+#define IDS_CANCEL_DOWNLOADS_AND_QUIT 76
+#define IDS_CONFIRM_QUIT 77
+#define IDS_ARTICLE_WITH_MEDIA 78
+#define IDS_PODCASTS_DIRECTORY 79
+#define IDS_PODCAST_DIRECTORY 79
+#define IDS_DELETEFAILED 80
+#define IDS_STRING81 81
+#define IDS_SORRY 81
+#define IDS_EDIT_CHANNEL 82
+#define IDS_ATD_NEVER 83
+#define IDS_ATD_LASTONE 84
+#define IDS_ATD_LASTTWO 85
+#define IDS_ATD_LASTTHREE 86
+#define IDS_ATD_LASTFIVE 87
+#define IDD_PREFERENCES 104
+#define IDD_ADDURL 105
+#define IDD_DOWNLOADS 107
+#define IDB_ICON_TEXT 110
+#define IDB_BITMAP1 111
+#define IDB_ICON_MEDIA 111
+#define IDR_MENU1 112
+#define IDB_BITMAP2 114
+#define IDB_TREEIMAGE_DOWNLOAD 114
+#define IDB_BITMAP3 115
+#define IDB_TREEIMAGE_SUBSCRIPTION 115
+#define IDB_BITMAP4 116
+#define IDB_TREEIMAGE_DISCOVER 116
+#define IDS_PODCAST_SUBSCRIPTION_HEADER 120
+#define IDS_PODCAST_SUBSCRIPTION_PROMP 121
+#define IDR_VIEW_DOWNLOAD_ACCELERATORS 122
+#define IDS_MEDIA_TIME 125
+#define IDS_EPISODE_INFO 126
+#define IDS_MEDIA_SIZE 127
+#define IDS_BYTES 128
+#define IDS_KIB 129
+#define IDS_MIB 130
+#define IDS_GIB 131
+#define IDC_CUSTOM 1000
+#define IDC_CHANNELLIST 1002
+#define IDC_CHANNELLIST2 1003
+#define IDC_EPISODE_INFO 1003
+#define IDC_ITEMLIST 1006
+#define IDC_DESCRIPTION 1008
+#define IDC_FEEDLIST 1009
+#define IDC_NEW 1010
+#define IDC_DELETE 1011
+#define IDC_REFRESH 1012
+#define IDC_EDITDESCRIPTION 1013
+#define IDC_EDITURL 1014
+#define IDC_UPDATETIME 1017
+#define IDC_AUTODOWNLOAD 1018
+#define IDC_DEFAULTUPDATETIME 1019
+#define IDC_DEFAULTAUTODOWNLOAD 1020
+#define IDC_APPLYDEFAULTS 1021
+#define IDC_LOCATION 1022
+#define IDC_LOCATIONBROWSE 1024
+#define IDC_ADDURLEDIT 1027
+#define IDC_ADDURLBUTTON 1028
+#define IDC_LIST1 1029
+#define IDC_DOWNLOADLIST 1029
+#define IDC_STATICDESCRIPTION 1030
+#define IDC_STATICURL 1031
+#define IDC_STATICUPDATE 1032
+#define IDC_DOWNLOADGROUP 1033
+#define IDC_STATICAUTODOWNLOAD 1035
+#define IDC_FEEDSGROUP 1036
+#define IDC_DEFAULTSGROUP 1037
+#define IDC_STATICUPDATEEVERY 1038
+#define IDC_STATICDEFAULTAUTODOWNLOAD 1039
+#define IDC_ADD 1044
+#define IDC_EDIT 1045
+#define IDC_PLAY 1047
+#define IDC_ADDURL 1047
+#define IDC_ENQUEUE 1048
+#define IDC_USEDEFAULTS 1048
+#define IDC_VISIT 1049
+#define IDC_USECUSTOM 1049
+#define IDC_DOWNLOAD 1050
+#define IDC_CUSTOMGROUP 1050
+#define IDC_BUTTON1 1051
+#define IDC_CANCEL 1051
+#define IDC_BROWSE 1051
+#define IDC_ENQUEUE2 1051
+#define IDC_FINDNEW 1051
+#define IDC_UPDATELIST 1052
+#define IDC_VDIV 1054
+#define IDC_HDIV1 1055
+#define IDC_HDIV 1055
+#define IDC_BROWSER 1057
+#define IDC_STATUS 1058
+#define IDC_EDIT1 1059
+#define IDC_DOWNLOADLOCATION 1059
+#define IDC_SETTINGSBOX 1060
+#define IDC_CLEANUP 1060
+#define IDC_CHECK1 1062
+#define IDC_UPDATEONLAUNCH 1062
+#define IDC_STATIC_UPDATEEVERY 1064
+#define IDC_PLAYACTION 1065
+#define IDC_ENQUEUEACTION 1066
+#define IDC_AUTODOWNLOADLIST 1067
+#define IDC_STATIC_AUTODOWNLOAD 1068
+#define IDC_DIRECTORYURL 1069
+#define IDR_DISCOVER_ICON 20000
+#define IDR_DOWNLOAD_ICON 20001
+#define IDR_SUBSCRIPTION_ICON 20002
+#define IDR_MEDIA_ICON 20003
+#define IDR_TEXT_ICON 20004
+#define IDM_CHANNELS 40006
+#define IDC_REFRESHALL 40008
+#define ID_PLAYMEDIA_IDC 40018
+#define IDC_REMOVE 40024
+#define ID_DOWNLOADS_SENDTO 40025
+#define ID_Menu 40026
+#define IDC_INFOBOX 40028
+#define IDC_SELECTALL 40030
+#define ID_DOWNLOADS_SENDTO40031 40031
+#define ID_NAVIGATION_PREFERENCES 40032
+#define ID_NAVIGATION_HELP 40033
+#define ID_DOWNLOADS_EXPLORERITEMFOLDER 40034
+#define IDC_EXPLORERITEMFOLDER 40034
+#define ID_ENQUEUE 40036
+#define ID_SUBSCRIPTIONNAVIGATION_PREFERENCE 40043
+#define ID_SUBSCRIPTIONNAVIGATION_DIRECTORY 40044
+#define ID_NAVIGATION_DIRECTORY 40045
+#define ID_SUBSCRIPTIONNAVIGATION_HELP 40046
+#define ID_NAVIGATION_REFRESHALL 40047
+#define ID_EPISODE_SELECTALL 40048
+#define IDS_PLUGIN_NAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 133
+#define _APS_NEXT_COMMAND_VALUE 40052
+#define _APS_NEXT_CONTROL_VALUE 1068
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_wire/resources/discoverIcon.png b/Src/Plugins/Library/ml_wire/resources/discoverIcon.png
new file mode 100644
index 00000000..6028b8bd
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/resources/discoverIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_wire/resources/downloadIcon.png b/Src/Plugins/Library/ml_wire/resources/downloadIcon.png
new file mode 100644
index 00000000..fadb028f
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/resources/downloadIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_wire/resources/mediaIcon.png b/Src/Plugins/Library/ml_wire/resources/mediaIcon.png
new file mode 100644
index 00000000..1f6313e2
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/resources/mediaIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_wire/resources/subscriptionIcon.png b/Src/Plugins/Library/ml_wire/resources/subscriptionIcon.png
new file mode 100644
index 00000000..f37b47fa
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/resources/subscriptionIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_wire/resources/textIcon.png b/Src/Plugins/Library/ml_wire/resources/textIcon.png
new file mode 100644
index 00000000..f6e15aef
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/resources/textIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_wire/service.cpp b/Src/Plugins/Library/ml_wire/service.cpp
new file mode 100644
index 00000000..80d7b71c
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/service.cpp
@@ -0,0 +1,273 @@
+#include "main.h"
+#include "./service.h"
+#include "api__ml_wire.h"
+#include "./util.h"
+#include "./resource.h"
+#include "./externalCOM.h"
+
+#include "../winamp/wa_ipc.h"
+#include <strsafe.h>
+
+#define IS_INVALIDISPATCH(__disp) (((IDispatch *)1) == (__disp) || NULL == (__disp))
+
+OmService::OmService( UINT nId ) : id( nId )
+{}
+
+OmService::~OmService()
+{
+ Plugin_FreeResString( name );
+ Plugin_FreeResString( url );
+ Plugin_FreeResString( icon );
+}
+
+
+HRESULT OmService::CreateRemote( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, OmService **instance )
+{
+ if ( instance == NULL )
+ return E_POINTER;
+
+ *instance = NULL;
+
+ if ( nId == 0 || pszName == NULL )
+ return E_INVALIDARG;
+
+ OmService *service = new OmService( nId );
+ if ( service == NULL )
+ return E_OUTOFMEMORY;
+
+ service->SetName( pszName );
+ service->SetIcon( pszIcon );
+ service->SetUrl( pszUrl );
+
+ *instance = service;
+
+ return S_OK;
+}
+
+HRESULT OmService::CreateLocal( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, SVCWNDCREATEPROC windowCreator, OmService **instance )
+{
+ if ( instance == NULL )
+ return E_POINTER;
+
+ *instance = NULL;
+
+ if ( nId == 0 || pszName == NULL )
+ return E_INVALIDARG;
+
+ OmService *service = new OmService( nId );
+ if ( service == NULL )
+ return E_OUTOFMEMORY;
+
+ service->SetFlags( flagLocal, flagLocal );
+ service->SetName( pszName );
+ service->SetIcon( pszIcon );
+ service->SetWindowCreator( windowCreator );
+
+ *instance = service;
+
+ return S_OK;
+}
+
+
+size_t OmService::AddRef()
+{
+ return _ref.fetch_add( 1 );
+}
+
+size_t OmService::Release()
+{
+ if ( _ref.load() == 0 )
+ return _ref.load();
+
+ LONG r = _ref.fetch_sub( 1 );
+ if ( r == 0 )
+ delete( this );
+
+ return r;
+}
+
+
+int OmService::QueryInterface( GUID interface_guid, void **object )
+{
+ if ( object == NULL )
+ return E_POINTER;
+
+ if ( IsEqualIID( interface_guid, IFC_OmService ) )
+ *object = static_cast<ifc_omservice *>( this );
+ else
+ {
+ *object = NULL;
+
+ return E_NOINTERFACE;
+ }
+
+ if ( *object == NULL )
+ return E_UNEXPECTED;
+
+ AddRef();
+
+ return S_OK;
+}
+
+
+unsigned int OmService::GetId()
+{
+ return id;
+}
+
+HRESULT OmService::GetName( wchar_t *pszBuffer, int cchBufferMax )
+{
+ return Plugin_CopyResString( pszBuffer, cchBufferMax, name );
+}
+
+HRESULT OmService::GetUrl( wchar_t *pszBuffer, int cchBufferMax )
+{
+ return Plugin_CopyResString( pszBuffer, cchBufferMax, url );
+}
+
+HRESULT OmService::GetIcon( wchar_t *pszBuffer, int cchBufferMax )
+{
+ if ( icon != NULL && IS_INTRESOURCE( icon ) )
+ {
+ WCHAR szPath[ 2 * MAX_PATH ] = { 0 };
+ if ( GetModuleFileName( plugin.hDllInstance, szPath, ARRAYSIZE( szPath ) ) == 0 )
+ return E_FAIL;
+
+ return StringCchPrintf( pszBuffer, cchBufferMax, L"res://%s/#%d/#%d", szPath, RT_RCDATA, icon );
+ }
+
+ return StringCchCopyEx( pszBuffer, cchBufferMax, icon, NULL, NULL, STRSAFE_IGNORE_NULLS );
+}
+
+HRESULT OmService::GetExternal( IDispatch **ppDispatch )
+{
+ if ( ppDispatch == NULL )
+ return E_POINTER;
+
+ *ppDispatch = NULL;
+
+ HWND hWinamp = plugin.hwndWinampParent;
+ if ( hWinamp == NULL )
+ return E_UNEXPECTED;
+
+ //////*ppDispatch = (IDispatch*)SENDWAIPC(hWinamp, IPC_GET_DISPATCH_OBJECT, 0);
+
+ WCHAR szBuffer[ 64 ] = { 0 };
+ if ( SUCCEEDED( StringCchPrintfW( szBuffer, ARRAYSIZE( szBuffer ), L"%u", id ) ) )
+ *ppDispatch = (IDispatch *) SENDWAIPC( hWinamp, IPC_JSAPI2_GET_DISPATCH_OBJECT, (WPARAM) szBuffer );
+
+
+ if (IS_INVALIDISPATCH(*ppDispatch) && FAILED(ExternalCOM::CreateInstance((ExternalCOM**)ppDispatch)))
+ {
+ *ppDispatch = NULL;
+ return E_FAIL;
+ }
+
+
+ return S_OK;
+}
+
+
+HRESULT OmService::SetName( LPCWSTR pszName )
+{
+ Plugin_FreeResString( name );
+ name = Plugin_DuplicateResString( pszName );
+
+ return S_OK;
+}
+
+HRESULT OmService::SetUrl( LPCWSTR pszUrl )
+{
+ Plugin_FreeResString( url );
+ url = Plugin_DuplicateResString( pszUrl );
+
+ return S_OK;
+}
+
+HRESULT OmService::SetIcon( LPCWSTR pszIcon )
+{
+ Plugin_FreeResString( icon );
+ icon = Plugin_DuplicateResString( pszIcon );
+
+ return S_OK;
+}
+
+void OmService::SetFlags( UINT mask, UINT newFlags )
+{
+ flags = ( flags & ~mask ) | ( mask & newFlags );
+}
+
+UINT OmService::GetFlags( void )
+{
+ return flags;
+}
+
+
+HRESULT OmService::SetWindowCreator( SVCWNDCREATEPROC proc )
+{
+ windowCreator = proc;
+
+ return S_OK;
+}
+
+HRESULT OmService::GetWindowCreator( SVCWNDCREATEPROC *proc )
+{
+ if ( proc == NULL )
+ return E_INVALIDARG;
+
+ *proc = windowCreator;
+
+ return S_OK;
+}
+
+
+HRESULT OmService::CreateView( HWND hParent, HWND *hView )
+{
+ if ( hView == NULL )
+ return E_POINTER;
+
+ *hView = NULL;
+ HRESULT hr = S_OK;
+
+ if ( ( flagLocal & flags ) != 0 )
+ {
+ if ( windowCreator != NULL )
+ {
+ *hView = windowCreator( hParent, this );
+ if ( *hView == NULL )
+ hr = E_FAIL;
+ }
+ else
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ if ( OMBROWSERMNGR != NULL )
+ {
+ hr = OMBROWSERMNGR->Initialize( NULL, plugin.hwndWinampParent );
+ if ( SUCCEEDED( hr ) )
+ hr = OMBROWSERMNGR->CreateView( this, hParent, NULL, 0, hView );
+ }
+ else
+ hr = E_UNEXPECTED;
+
+ }
+
+ return hr;
+}
+
+
+#define CBCLASS OmService
+START_DISPATCH;
+CB( ADDREF, AddRef )
+CB( RELEASE, Release )
+CB( QUERYINTERFACE, QueryInterface )
+CB( API_GETID, GetId )
+CB( API_GETNAME, GetName )
+CB( API_GETURL, GetUrl )
+CB( API_GETICON, GetIcon )
+CB( API_GETEXTERNAL, GetExternal )
+END_DISPATCH;
+#undef CBCLASS
+
+ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/service.h b/Src/Plugins/Library/ml_wire/service.h
new file mode 100644
index 00000000..ad4e839e
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/service.h
@@ -0,0 +1,69 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_SERVICE_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_SERVICE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <atomic>
+
+#include "../omBrowser/ifc_omservice.h"
+
+class OmService;
+typedef HWND (CALLBACK *SVCWNDCREATEPROC)(HWND /*hParent*/, OmService* /*service*/);
+
+class OmService : public ifc_omservice
+{
+public:
+ typedef enum
+ {
+ flagRoot = 0x00000001,
+ flagLocal = 0x00000002,
+ } Flags;
+
+protected:
+ OmService( UINT nId );
+ ~OmService();
+
+public:
+ static HRESULT CreateRemote( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, OmService **instance );
+ static HRESULT CreateLocal( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, SVCWNDCREATEPROC windowCreator, OmService **instance );
+
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface( GUID interface_guid, void **object );
+
+ /* ifc_omservice */
+ unsigned int GetId();
+ HRESULT GetName( wchar_t *pszBuffer, int cchBufferMax );
+ HRESULT GetUrl( wchar_t *pszBuffer, int cchBufferMax );
+ HRESULT GetExternal( IDispatch **ppDispatch );
+ HRESULT GetIcon( wchar_t *pszBuffer, int cchBufferMax );
+
+ HRESULT SetName( LPCWSTR pszName );
+ HRESULT SetUrl( LPCWSTR pszUrl );
+ HRESULT SetIcon( LPCWSTR pszIcon );
+
+ void SetFlags( UINT mask, UINT newFlags );
+ UINT GetFlags( void );
+
+ HRESULT SetWindowCreator( SVCWNDCREATEPROC proc );
+ HRESULT GetWindowCreator( SVCWNDCREATEPROC *proc );
+
+ HRESULT CreateView( HWND hParent, HWND *hView );
+
+protected:
+ RECVS_DISPATCH;
+
+ std::atomic<std::size_t> _ref = 1;
+ UINT id = 0;
+ LPWSTR name = NULL;
+ LPWSTR url = NULL;
+ SVCWNDCREATEPROC windowCreator = NULL;
+ LPWSTR icon = NULL;
+ UINT flags = 0;
+};
+
+#endif //NULLSOFT_PODCAST_PLUGIN_SERVICE_HEADER
diff --git a/Src/Plugins/Library/ml_wire/subscriptionView.cpp b/Src/Plugins/Library/ml_wire/subscriptionView.cpp
new file mode 100644
index 00000000..4bff7ebc
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/subscriptionView.cpp
@@ -0,0 +1,2691 @@
+#include "main.h"
+#include "./subscriptionView.h"
+
+#include "api__ml_wire.h"
+#include "./util.h"
+#include "Feeds.h"
+#include "RFCDate.h"
+#include "Cloud.h"
+#include "./channelEditor.h"
+#include "BackgroundDownloader.h"
+#include "./service.h"
+#include "./layout.h"
+#include "../nu/menushortcuts.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../omBrowser/browserView.h"
+#include "./navigation.h"
+
+#include <strsafe.h>
+
+#include "../../../WAT/WAT.h"
+
+#ifndef HDF_SORTUP
+#define HDF_SORTUP 0x0400
+#define HDF_SORTDOWN 0x0200
+#endif // !HDF_SORTUP
+
+using namespace Nullsoft::Utility;
+
+int itemTitleWidth = 200;
+int itemDateWidth = 120;
+int itemMediaWidth = 100;
+int itemSizeWidth = 100;
+
+int currentItemSort = 1; // -1 means no sort active
+bool itemSortAscending = false;
+bool channelSortAscending = true;
+int channelLastSelection = -1;
+
+float htmlDividerPercent = 0.666f;
+float channelDividerPercent = 0.333f;
+
+extern int IPC_LIBRARY_SENDTOMENU;
+extern librarySendToMenuStruct s;
+
+static int episode_info_cy;
+HMENU g_context_menus3 = NULL;
+
+extern Cloud cloud;
+extern wchar_t serviceUrl[1024];
+
+#define SUBSCRIPTIONVIEW_NAME L"SubscriptionView"
+
+typedef enum
+{
+ COLUMN_TITLE = 0,
+ COLUMN_DATEADDED,
+ //COLUMN_MEDIA,
+ COLUMN_MEDIA_TIME,
+ COLUMN_MEDIA_SIZE,
+} ListColumns;
+
+typedef enum
+{
+ LI_CHANNEL = 0,
+ LI_ITEM,
+ LI_VERT,
+ LI_FINDNEW,
+ LI_ADD,
+ LI_EDIT,
+ LI_DELETE,
+ LI_REFRESH,
+ LI_HORZ,
+ LI_EPISODE_INFO,
+ LI_INFO,
+ LI_PLAY,
+ LI_ENQUEUE,
+ LI_CUSTOM,
+ LI_DOWNLOAD,
+ LI_VISIT,
+ LI_STATUS,
+ LI_LAST
+} LayoutItems;
+
+typedef struct __PODCAST
+{
+ LONG vertDivider;
+ LONG horzDivider;
+ LONG bottomRowSpace;
+ LONG middleRowSpace;
+ HRGN updateRegion;
+ POINTS updateOffset;
+ size_t channelActive;
+ BOOL channelAscending;
+ BOOL itemAscending;
+ LPWSTR infoUrl;
+ LPWSTR description;
+} PODCAST;
+
+#define GetPodcast(__hwnd) ((PODCAST*)GetPropW((__hwnd), MAKEINTATOM(VIEWPROP)))
+
+#define LAYOUTREASON_RESIZE 0
+#define LAYOUTREASON_DIV_LEFT 1
+#define LAYOUTREASON_DIV_RIGHT 2
+#define LAYOUTREASON_DIV_TOP 3
+#define LAYOUTREASON_DIV_BOTTOM 4
+
+#define UPDATE_TIMER 12
+#define UPDATE_DELAY 0
+
+static void CALLBACK PodcastChannel_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed);
+static void CALLBACK PodcastItem_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed);
+static size_t PodcastChannel_GetActive(HWND hwnd);
+
+ptrdiff_t inline CopyCharW(wchar_t *dest, const wchar_t *src)
+{
+ wchar_t *end = CharNextW(src);
+ ptrdiff_t count = end-src;
+ for (ptrdiff_t i=0;i<count;i++)
+ {
+ *dest++=*src++;
+ }
+
+ return count;
+}
+
+// TODO for the moment, this is just to cleanup bad
+// urls which use spaces instead of %20 in the feed
+wchar_t *urlencode( wchar_t *p )
+{
+ if ( p )
+ {
+ wchar_t buf[ MAX_PATH * 4 ], *i = buf;
+ StringCchCopyW( buf, MAX_PATH * 4, p );
+ while ( p && *p )
+ {
+ if ( !StrCmpNW( i, L" ", 1 ) )
+ {
+ *i++ = L'%';
+ *i++ = L'2';
+ *i = L'0';
+ p += 1;
+ }
+ else
+ {
+ CopyCharW( i, p );
+ p = CharNextW( p );
+ }
+
+ i = CharNextW( i );
+ }
+
+ *i = 0;
+
+ return _wcsdup( buf );
+ }
+
+ return NULL;
+}
+
+static void SubscriptionView_EnableMenuCommands(HMENU hMenu, const INT *commandList, INT commandCount, BOOL fEnable)
+{
+ UINT uEnable = MF_BYCOMMAND | MF_ENABLED;
+ if (FALSE == fEnable) uEnable |= (MF_GRAYED | MF_DISABLED);
+
+ for (INT i = 0; i < commandCount; i++)
+ {
+ EnableMenuItem(hMenu, commandList[i], uEnable);
+ }
+}
+
+static void PodcastItem_EnableButtons(HWND hwnd, BOOL fEnable)
+{
+ static const INT szButtons[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_DOWNLOAD, };
+ for(INT i = 0; i < ARRAYSIZE(szButtons); i++)
+ {
+ HWND hControl = GetDlgItem(hwnd, szButtons[i]);
+ if (NULL != hControl)
+ {
+ EnableWindow(hControl, fEnable);
+ }
+ }
+}
+
+static void PodcastChannel_EnableButtons(HWND hwnd, BOOL fEnable)
+{
+ static const INT szButtons[] = { IDC_EDIT, IDC_REFRESH, IDC_DELETE, };
+ HWND hControl;
+
+ for(INT i = 0; i < ARRAYSIZE(szButtons); i++)
+ {
+ hControl = GetDlgItem(hwnd, szButtons[i]);
+ if (NULL != hControl)
+ {
+ EnableWindow(hControl, fEnable);
+ }
+ }
+}
+
+static HRESULT SubscriptionView_FormatFont(LPWSTR pszBuffer, INT cchBufferMax, HFONT hFont)
+{
+ const WCHAR szTemplate[] = L"font: %s %d %dpx \"%s\";";
+
+ if (NULL == pszBuffer)
+ return E_POINTER;
+
+ pszBuffer[0] = L'\0';
+
+ if (NULL == hFont) return E_POINTER;
+
+ LOGFONT lf = {0};
+ if (0 == GetObject(hFont, sizeof(LOGFONT), &lf))
+ return E_FAIL;
+
+ if (lf.lfHeight < 0)
+ lf.lfHeight = -lf.lfHeight;
+
+ return StringCchPrintfEx(pszBuffer, cchBufferMax, NULL, NULL, STRSAFE_IGNORE_NULLS,
+ szTemplate, ((0 == lf.lfItalic) ? L"normal" : L"italic"),
+ lf.lfWeight, lf.lfHeight, L"Arial"/*lf.lfFaceName*/);
+}
+
+static BOOL SubscriptionView_SetDescription(HWND hwnd, LPCWSTR pszInfo)
+{
+ HWND hBrowser = GetDlgItem(hwnd, IDC_DESCRIPTION);
+ if (NULL == hBrowser) return FALSE;
+
+ const WCHAR szTemplate[] = L"<html>"
+ L"<base target=\"_blank\">"
+ L"<style type=\"text/css\"> body { %s overflow-y: auto; overflow-x: auto;}</style>"
+ L"<body>%s</body>"
+ L"</html>";
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ WCHAR szFont[128] = {0};
+ HFONT hFont = (HFONT)SendMessage(hItems, WM_GETFONT, 0, 0L);
+ if (FAILED(SubscriptionView_FormatFont(szFont, ARRAYSIZE(szFont), hFont)))
+ szFont[0] = L'\0';
+
+ if (NULL == pszInfo) pszInfo = L"";
+
+ INT cchLen = lstrlen(pszInfo) + 1;
+ cchLen += lstrlen(szFont);
+ cchLen += ARRAYSIZE(szTemplate);
+
+ BSTR documentData = SysAllocStringLen(NULL, cchLen);
+ if (NULL == documentData) return FALSE;
+
+ BOOL result;
+
+ if ( SUCCEEDED( StringCchPrintfEx( documentData, cchLen, NULL, NULL, STRSAFE_IGNORE_NULLS, szTemplate, szFont, pszInfo ) ) )
+ result = BrowserView_WriteDocument( hBrowser, documentData, TRUE );
+ else
+ result = FALSE;
+
+ if (FALSE == result)
+ SysFreeString(documentData);
+
+ return result;
+}
+
+static void SubscriptionView_UpdateInfo( HWND hwnd )
+{
+ PODCAST *podcast = GetPodcast( hwnd );
+ if ( NULL == podcast )
+ return;
+
+ AutoLock channelLock( channels LOCKNAME( "UpdateInfo" ) );
+
+ LPCWSTR infoText = NULL;
+ BOOL itemEnabled = FALSE;
+
+ size_t iChannel = PodcastChannel_GetActive( hwnd );
+ if ( BAD_CHANNEL != iChannel )
+ {
+ Channel *channel = &channels[ iChannel ];
+
+ HWND hItems = GetDlgItem( hwnd, IDC_ITEMLIST );
+ INT selectedCount = ( NULL != hItems ) ? (INT)SNDMSG( hItems, LVM_GETSELECTEDCOUNT, 0, 0L ) : 0;
+ size_t iItem = ( 1 == selectedCount ) ? (size_t)SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_SELECTED ) : BAD_ITEM;
+ if ( iItem < channel->items.size() )
+ {
+ if ( FALSE == podcast->itemAscending )
+ iItem = channel->items.size() - iItem - 1;
+
+ infoText = channel->items[ iItem ].description;
+ }
+
+ if ( NULL == infoText || L'\0' == *infoText )
+ infoText = channel->description;
+
+ if ( selectedCount > 0 )
+ {
+ INT iSelected = -1;
+ while ( -1 != ( iSelected = SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED ) ) )
+ {
+ size_t t = iSelected;
+ if ( t < channel->items.size() )
+ {
+ if ( FALSE == podcast->itemAscending )
+ t = channel->items.size() - t - 1;
+
+ LPCWSTR url = channel->items[ t ].url;
+ if ( NULL != url && L'\0' != *url )
+ {
+ itemEnabled = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if ( podcast->description != infoText && CSTR_EQUAL != CompareString( CSTR_INVARIANT, 0, podcast->description, -1, infoText, -1 ) )
+ {
+ Plugin_FreeString( podcast->description );
+ podcast->description = Plugin_CopyString( infoText );
+ SubscriptionView_SetDescription( hwnd, podcast->description );
+ }
+
+ PodcastChannel_EnableButtons( hwnd, ( BAD_CHANNEL != iChannel ) );
+ PodcastItem_EnableButtons( hwnd, itemEnabled );
+}
+
+static BOOL SubscriptionView_SortItems(size_t iChannel, int sortColumn)
+{
+ if (iChannel >= channels.size())
+ return FALSE;
+
+ AutoLock lock (channels LOCKNAME("SortItems"));
+ Channel &channel = channels[iChannel];
+
+ switch (sortColumn)
+ {
+ case COLUMN_TITLE: channel.SortByTitle(); return TRUE;
+ case COLUMN_MEDIA_TIME: channel.SortByMediaTime(); return TRUE;
+ case COLUMN_MEDIA_SIZE: channel.SortByMediaSize(); return TRUE;
+ //case COLUMN_MEDIA: channel.SortByMedia(); return TRUE;
+ case COLUMN_DATEADDED: channel.SortByDate(); return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL SubscriptionView_SortChannels(int sortColumn)
+{
+ AutoLock lock (channels LOCKNAME("SortItems"));
+
+ switch (sortColumn)
+ {
+ case COLUMN_TITLE: channels.SortByTitle(); return TRUE;
+ }
+
+ return FALSE;
+}
+
+static INT SubscriptionView_GetListSortColumn(HWND hwnd, INT listId, BOOL *fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL != hItems)
+ {
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL != hHeader)
+ {
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item) &&
+ 0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ if (NULL != fAscending)
+ {
+ *fAscending = (0 != (HDF_SORTUP & item.fmt));
+ }
+ return i;
+ }
+ }
+
+ }
+ }
+ return -1;
+}
+
+static void SubscriptionView_SetListSortColumn(HWND hwnd, INT listId, INT index, BOOL fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL == hItems) return;
+
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL == hHeader) return;
+
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+ // reset first (ml req)
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (index != i && FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item))
+ {
+ if (0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
+ SNDMSG(hHeader, HDM_SETITEM, i, (LPARAM)&item);
+ }
+ }
+ }
+
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, index, (LPARAM)&item))
+ {
+ INT fmt = item.fmt & ~(HDF_SORTUP | HDF_SORTDOWN);
+ fmt |= (FALSE == fAscending) ? HDF_SORTDOWN : HDF_SORTUP;
+ if (fmt != item.fmt)
+ {
+ item.fmt = fmt;
+ SNDMSG(hHeader, HDM_SETITEM, index, (LPARAM)&item);
+ }
+ }
+}
+
+static BOOL SubscriptionView_UpdateInfoUrl(HWND hwnd)
+{
+ AutoLock lock (channels LOCKNAME("UpdateInfoUrl"));
+
+ BOOL result = FALSE;
+ BOOL buttonEnable = FALSE;
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL != podcast)
+ {
+ Plugin_FreeString(podcast->infoUrl);
+ podcast->infoUrl = NULL;
+
+ LPCWSTR pszUrl = NULL;
+
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL != iChannel)
+ {
+ Channel *channel = &channels[iChannel];
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ INT selectedCount = (NULL != hItems ) ? (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L) : 0;
+ size_t iItem = (1 == selectedCount) ? (size_t)SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_SELECTED) : BAD_ITEM;
+ if (iItem < channel->items.size())
+ {
+ if (FALSE == podcast->itemAscending)
+ iItem = channel->items.size() - iItem - 1;
+ pszUrl = channel->items[iItem].link;
+ }
+
+ if (NULL == pszUrl || L'\0' == *pszUrl)
+ pszUrl = channel->link;
+ }
+
+ if (NULL != pszUrl && L'\0' != *pszUrl)
+ {
+ podcast->infoUrl = Plugin_CopyString(pszUrl);
+ if (NULL != podcast->infoUrl)
+ {
+ buttonEnable = TRUE;
+ result = TRUE;
+ }
+ }
+ }
+
+ HWND hButton = GetDlgItem(hwnd, IDC_VISIT);
+ if (NULL != hButton) EnableWindow(hButton, buttonEnable);
+
+ return result;
+}
+
+static INT SubscriptionView_Play(HWND hwnd, size_t iChannel, size_t *indexList, size_t indexCount, BOOL fEnqueue, BOOL fForce)
+{
+ AutoLock channelLock (channels LOCKNAME("Play"));
+
+ if (BAD_CHANNEL == iChannel || iChannel >= channels.size())
+ return 0;
+
+ Channel *channel = &channels[iChannel];
+
+ INT cchBuffer = 0;
+
+ for(size_t i = 0; i < indexCount; i++)
+ {
+ size_t iItem = indexList[i];
+ if (iItem < channel->items.size())
+ {
+ WCHAR szPath[MAX_PATH * 2] = {0};
+ RSS::Item *downloadItem = &channel->items[iItem];
+ if ((downloadItem->url && downloadItem->url[0]) &&
+ SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
+ PathFileExists(szPath))
+ {
+ cchBuffer += (lstrlen(szPath) + 1);
+ }
+ else
+ {
+ wchar_t* url = urlencode(channel->items[iItem].url);
+ LPCWSTR p = url;
+ if (NULL != p)
+ {
+ cchBuffer += (lstrlen(p) + 1);
+ free(url);
+ }
+ }
+ }
+ }
+
+ if (0 == cchBuffer)
+ return 0;
+
+ cchBuffer++;
+ LPWSTR pszBuffer = (LPWSTR)calloc(cchBuffer, sizeof(WCHAR));
+ if (NULL == pszBuffer) return 0;
+
+ LPWSTR c = pszBuffer;
+ size_t r = cchBuffer;
+
+ INT playCount = 0;
+ for(size_t i = 0; i < indexCount; i++)
+ {
+ size_t iItem = indexList[i];
+ if (iItem < channel->items.size())
+ {
+ WCHAR szPath[MAX_PATH * 2] = {0};
+ RSS::Item *downloadItem = &channel->items[iItem];
+ if ((downloadItem->url && downloadItem->url[0]) &&
+ SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
+ PathFileExists(szPath))
+ {
+ LPCWSTR p = szPath;
+ if (NULL != p && L'\0' != *p)
+ {
+ if (FAILED(StringCchCopyExW(c, r, p, &c, &r, STRSAFE_NULL_ON_FAILURE)) || 0 == r)
+ break;
+
+ c++;
+ r--;
+
+ channel->items[iItem].listened = true;
+ playCount++;
+ }
+ }
+ else
+ {
+ wchar_t* url = urlencode(channel->items[iItem].url);
+ LPCWSTR p = url;
+ if (NULL != p && L'\0' != *p)
+ {
+ if (FAILED(StringCchCopyExW(c, r, p, &c, &r, STRSAFE_NULL_ON_FAILURE)) || 0 == r)
+ {
+ free(url);
+ break;
+ }
+ free(url);
+ c++;
+ r--;
+
+ channel->items[iItem].listened = true;
+ playCount++;
+ }
+ }
+ }
+ }
+
+ if (c != pszBuffer)
+ {
+ *c = L'\0';
+ // make sure this is initialised as default handler requires this being zeroed
+ mlSendToWinampStruct send = {ML_TYPE_STREAMNAMESW,pszBuffer,0};
+ // otherwise we've a specific action and need to tell ML to do as we want
+ if (TRUE == fForce)
+ send.enqueue = ((FALSE == fEnqueue) ? 0 : 1) | 2;
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SENDTOWINAMP, (WPARAM)&send);
+ }
+
+ free(pszBuffer);
+ return playCount;
+}
+
+static INT SubscriptionView_PlayChannel(HWND hwnd, size_t iChannel, BOOL fEnqueue, BOOL fForce)
+{
+ AutoLock channelLock (channels LOCKNAME("PlayChannel"));
+
+ if (BAD_CHANNEL == iChannel || iChannel >= channels.size())
+ return 0;
+
+ size_t count = channels.size();
+ size_t *list = NULL;
+ if (0 != count)
+ {
+ list = (size_t*)calloc(count, sizeof(size_t));
+ if (NULL == list) return 0;
+ for (size_t i = 0; i < count; i++) list[i] = i;
+ }
+
+ INT result = SubscriptionView_Play(hwnd, iChannel, list, count, fEnqueue, fForce);
+
+ if (NULL != list)
+ free(list);
+
+ return result;
+}
+
+static void PodcastItem_InitializeList(HWND hwnd)
+{
+ HWND hControl = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hControl)
+ return;
+
+ UINT styleEx = (UINT)GetWindowLongPtr(hControl, GWL_EXSTYLE);
+ SetWindowLongPtr(hControl, GWL_EXSTYLE, styleEx & ~WS_EX_NOPARENTNOTIFY);
+
+ styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | /*LVS_EX_HEADERDRAGDROP |*/ LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
+ SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+ SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
+
+ LVCOLUMN lvc = {0};
+ WCHAR szBuffer[128] = {0};
+
+ lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
+ lvc.pszText = szBuffer;
+
+ lvc.fmt = LVCFMT_LEFT;
+ lvc.cx = itemTitleWidth;
+ WASABI_API_LNGSTRINGW_BUF( IDS_ITEM, szBuffer, ARRAYSIZE( szBuffer ) );
+ SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc);
+
+ lvc.fmt = LVCFMT_RIGHT;
+ lvc.cx = itemDateWidth;
+ WASABI_API_LNGSTRINGW_BUF( IDS_DATE_ADDED, szBuffer, ARRAYSIZE( szBuffer ) );
+ SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)1, (LPARAM)&lvc);
+
+ lvc.fmt = LVCFMT_RIGHT;
+ lvc.cx = itemMediaWidth;
+ WASABI_API_LNGSTRINGW_BUF( IDS_MEDIA_TIME, szBuffer, ARRAYSIZE( szBuffer ) );
+ SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)2, (LPARAM)&lvc);
+
+ lvc.fmt = LVCFMT_RIGHT;
+ lvc.cx = itemSizeWidth;
+ WASABI_API_LNGSTRINGW_BUF( IDS_MEDIA_SIZE, szBuffer, ARRAYSIZE( szBuffer ) );
+ SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)3, (LPARAM)&lvc);
+
+ HIMAGELIST imageList = ImageList_Create( 15, 15, ILC_COLOR24, 3, 0 );
+ if ( imageList != NULL )
+ {
+ HIMAGELIST prevList = (HIMAGELIST)SNDMSG( hControl, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)imageList );
+ if ( prevList != NULL )
+ ImageList_Destroy( prevList );
+ }
+
+ MLSKINWINDOW skinWindow;
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.hwndToSkin = hControl;
+
+ MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
+}
+
+static void PodcastItem_SelectionChanged(HWND hwnd, BOOL fImmediate)
+{
+ KillTimer(hwnd, UPDATE_TIMER);
+
+ if (FALSE == fImmediate)
+ {
+ SetTimer(hwnd, UPDATE_TIMER, UPDATE_DELAY, PodcastItem_UpdateTimer);
+ return;
+ }
+
+ SubscriptionView_UpdateInfoUrl(hwnd);
+ SubscriptionView_UpdateInfo(hwnd);
+}
+
+static HMENU PodcastItem_GetMenu(HWND hwnd, HMENU baseMenu, INT iItem)
+{
+ HMENU menu = GetSubMenu(baseMenu, 1);
+ if (NULL == menu)
+ return NULL;
+
+ PodcastItem_SelectionChanged(hwnd, TRUE);
+
+ // update the explore media menu item from the download button state (hence the two IDC_DOWNLOAD)
+ const INT szExtras[] = { IDC_PLAY, IDC_ENQUEUE, IDC_DOWNLOAD, IDC_DOWNLOAD, IDC_VISIT, };
+
+ for (INT i = 0; i < ARRAYSIZE(szExtras); i++)
+ {
+ HWND hButton = GetDlgItem(hwnd, szExtras[i]);
+
+ UINT uEnable = MF_BYCOMMAND | MF_ENABLED;
+ if (NULL == hButton || (-1 == iItem) || 0 != (WS_DISABLED & GetWindowLongPtr(hButton, GWL_STYLE)))
+ uEnable |= (MF_GRAYED | MF_DISABLED);
+
+ EnableMenuItem(menu, (i == 3 ? IDC_EXPLORERITEMFOLDER : szExtras[i]), uEnable);
+ }
+
+ EnableMenuItem(menu, 2, MF_BYPOSITION | ((-1 == iItem) ? (MF_GRAYED | MF_DISABLED) : MF_ENABLED));
+
+ { // send-to menu shit...
+ ZeroMemory(&s, sizeof(s));
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
+ {
+ s.mode = 1;
+ s.hwnd = hwnd;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[1] = 1;
+ s.build_hMenu = GetSubMenu(menu, 2);
+ }
+ }
+
+ UpdateMenuItems(hwnd, menu);
+
+ // check if the menu itsm is shown as having been download and
+ // if it hasn't then is nicer to hide 'explore media folder'
+ {
+ AutoLock channelLock (channels LOCKNAME("ItemMenu"));
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL != podcast)
+ {
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL != iChannel)
+ {
+ Channel *channel = &channels[iChannel];
+
+ if (iItem < (INT)channel->items.size())
+ {
+ if (FALSE == podcast->itemAscending)
+ iItem = (INT)channel->items.size() - iItem - 1;
+
+ RSS::Item *downloadItem = &channel->items[iItem];
+ if(downloadItem->downloaded == false)
+ {
+ DeleteMenu(menu, IDC_EXPLORERITEMFOLDER, MF_BYCOMMAND);
+ }
+ }
+ }
+ }
+ }
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ INT iCount = (NULL != hItems) ? (INT)SNDMSG(hItems, LVM_GETITEMCOUNT, 0, 0L) : 0;
+ INT command = IDC_REFRESH;
+ SubscriptionView_EnableMenuCommands(menu, &command, 1, (0 != iCount));
+
+ return menu;
+}
+
+static BOOL PodcastItem_Sort(HWND hwnd, INT iColumn, BOOL fAscending)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return FALSE;
+
+ BOOL result = FALSE;
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL != iChannel)
+ {
+ result = SubscriptionView_SortItems(iChannel, iColumn);
+ }
+
+ podcast->itemAscending = fAscending;
+ SubscriptionView_SetListSortColumn(hwnd, IDC_ITEMLIST, iColumn, fAscending);
+
+ if (FALSE != result)
+ {
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL != hItems)
+ InvalidateRect(hItems, NULL, TRUE);
+ }
+
+ return TRUE;
+}
+
+static size_t PodcastChannel_GetActive(HWND hwnd)
+{
+ AutoLock lock (channels LOCKNAME("GetActive"));
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL != podcast)
+ {
+ size_t iChannel = podcast->channelActive;
+ if (iChannel < channels.size())
+ {
+ return (size_t)iChannel;
+ }
+ }
+ return BAD_CHANNEL;
+}
+static void PodcastInfo_InitializeList(HWND hwnd)
+{
+ HWND hControl = GetDlgItem(hwnd, IDC_EPISODE_INFO);
+ if ( hControl == NULL )
+ return;
+
+ MLSKINWINDOW skinWindow;
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.hwndToSkin = hControl;
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+
+ MLSkinnedScrollWnd_ShowHorzBar(hControl, FALSE);
+ MLSkinnedScrollWnd_ShowVertBar(hControl, FALSE);
+
+ UINT styleEx;
+
+ styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT;
+ SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+ SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
+
+ LVCOLUMN lvc = {0};
+ WCHAR szBuffer[128] = {0};
+
+ lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
+ lvc.fmt = LVCFMT_LEFT;
+ lvc.pszText = szBuffer;
+ lvc.cx = 9999;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_EPISODE_INFO, szBuffer, ARRAYSIZE(szBuffer));
+ SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc);
+
+ HWND hHeader = ListView_GetHeader(hControl);
+
+ RECT rect;
+ GetClientRect(hHeader, &rect);
+ SetWindowPos(hControl, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
+}
+
+static void PodcastChannel_InitializeList(HWND hwnd)
+{
+ HWND hControl = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL == hControl)
+ return;
+
+ MLSKINWINDOW skinWindow = {0};
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.hwndToSkin = hControl;
+
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+
+ UINT skinStyle = MLSkinnedWnd_GetStyle(hControl);
+ skinStyle |= SWLVS_SELALWAYS;
+ MLSkinnedWnd_SetStyle(hControl, skinStyle);
+ MLSkinnedScrollWnd_ShowHorzBar(hControl, FALSE);
+
+ UINT styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT;
+ SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+ SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
+
+ LVCOLUMN lvc = {0};
+ WCHAR szBuffer[128] = {0};
+
+ lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
+ lvc.fmt = LVCFMT_LEFT;
+ lvc.pszText = szBuffer;
+ lvc.cx = 200;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_CHANNEL, szBuffer, ARRAYSIZE(szBuffer));
+ SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc);
+}
+
+static void PodcastChannel_SelectionChanged(HWND hwnd, BOOL fImmediate)
+{
+ KillTimer(hwnd, UPDATE_TIMER);
+
+ if (FALSE == fImmediate)
+ {
+ SetTimer(hwnd, UPDATE_TIMER, UPDATE_DELAY, PodcastChannel_UpdateTimer);
+ return;
+ }
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast)
+ return;
+
+ HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ INT iSelected = (NULL != hChannel) ? (INT)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, LVNI_SELECTED) : -1;
+
+ AutoLock channelLock (channels LOCKNAME("Channel Changed"));
+
+ if (-1 != iSelected && ((size_t)iSelected) > channels.size())
+ iSelected = -1;
+
+ if (-1 != iSelected && FALSE == podcast->channelAscending)
+ iSelected = (INT)channels.size() - iSelected - 1;
+
+ podcast->channelActive = iSelected;
+
+ if (-1 != iSelected)
+ {
+ INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, NULL);
+ SubscriptionView_SortItems(iSelected, iSort);
+ }
+
+ size_t itemsCount = (-1 != iSelected) ? channels[iSelected].items.size() : 0;
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL != hItems)
+ {
+ SNDMSG(hItems, WM_SETREDRAW, FALSE, 0L);
+
+ LVITEM lvi;
+ lvi.state = 0;
+ lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
+ SNDMSG(hItems, LVM_SETSELECTIONMARK, 0, (LPARAM)-1);
+ SNDMSG(hItems, LVM_SETITEMSTATE, -1, (LPARAM)&lvi);
+ SNDMSG(hItems, LVM_SETITEMCOUNT, (WPARAM)itemsCount, 0L);
+
+ SNDMSG(hItems, WM_SETREDRAW, TRUE, 0L);
+ }
+
+ SubscriptionView_UpdateInfoUrl(hwnd);
+ SubscriptionView_UpdateInfo(hwnd);
+ channelLastSelection = iSelected;
+}
+
+static HMENU PodcastChannel_GetMenu(HWND hwnd, HMENU baseMenu, INT iItem)
+{
+ HMENU menu = GetSubMenu(baseMenu, 0);
+ if (NULL == menu) return NULL;
+
+ if (iItem != -1) PodcastChannel_SelectionChanged(hwnd, TRUE);
+
+ if (iItem != -1)
+ {
+ DeleteMenu(menu, IDC_ADD, MF_BYCOMMAND);
+ const INT szExtras[] = { IDC_REFRESH, IDC_EDIT, IDC_DELETE, };
+ SubscriptionView_EnableMenuCommands(menu, szExtras, ARRAYSIZE(szExtras), (-1 != iItem));
+ }
+ else
+ {
+ DeleteMenu(menu, IDC_REFRESH, MF_BYCOMMAND);
+ DeleteMenu(menu, IDC_EDIT, MF_BYCOMMAND);
+ DeleteMenu(menu, IDC_DELETE, MF_BYCOMMAND);
+ DeleteMenu(menu, IDC_VISIT, MF_BYCOMMAND);
+ DeleteMenu(menu, 0, MF_BYPOSITION);
+ DeleteMenu(menu, 0, MF_BYPOSITION);
+ }
+
+ return menu;
+}
+
+static BOOL PodcastChannel_SyncHeaderSize(HWND hwnd, BOOL fRedraw)
+{
+ HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL == hChannel) return FALSE;
+
+ RECT channelRect;
+
+ HDITEM item;
+ item.mask = HDI_WIDTH;
+
+ HWND hHeader = (HWND)SNDMSG(hChannel, LVM_GETHEADER, 0, 0L);
+ if (NULL == hHeader ||
+ FALSE == GetClientRect(hChannel, &channelRect) ||
+ FALSE == SNDMSG(hHeader, HDM_GETITEM, COLUMN_TITLE, (LPARAM)&item))
+ {
+ return FALSE;
+ }
+
+ LONG columnWidth = channelRect.right - channelRect.left;
+
+
+ if (item.cxy == columnWidth) return TRUE;
+
+ item.cxy = columnWidth;
+
+ UINT windowStyle = GetWindowLongPtr(hHeader, GWL_STYLE);
+ if (FALSE == fRedraw && 0 != (WS_VISIBLE & windowStyle))
+ SetWindowLongPtr(hHeader, GWL_STYLE, windowStyle & ~WS_VISIBLE);
+
+ SNDMSG(hHeader, HDM_SETITEM, COLUMN_TITLE, (LPARAM)&item);
+
+ if (FALSE == fRedraw && 0 != (WS_VISIBLE & windowStyle))
+ SetWindowLongPtr(hHeader, GWL_STYLE, windowStyle);
+
+ return TRUE;
+}
+
+static BOOL PodcastChannel_Sort(HWND hwnd, INT iColumn, BOOL fAscending)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return FALSE;
+
+ BOOL result = SubscriptionView_SortChannels(iColumn);
+
+ podcast->channelAscending = fAscending;
+ SubscriptionView_SetListSortColumn(hwnd, IDC_CHANNELLIST, iColumn, fAscending);
+
+ if (FALSE != result)
+ {
+ HWND hItems = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL != hItems)
+ InvalidateRect(hItems, NULL, TRUE);
+ }
+
+ return result;
+}
+
+static BOOL PodcastChannel_SelectNext(HWND hwnd, BOOL fForward)
+{
+ HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL == hChannel) return FALSE;
+
+ INT iFocus = (INT)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, (LPARAM)LVNI_FOCUSED);
+
+ if (FALSE != fForward)
+ {
+ iFocus++;
+ INT iCount = (INT)SNDMSG(hChannel, LVM_GETITEMCOUNT, 0, 0L);
+ if (iFocus >= iCount) return TRUE;
+ }
+ else
+ {
+ if (iFocus <= 0) return TRUE;
+ iFocus--;
+ }
+
+ LVITEM lvi = {0};
+ lvi.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
+ SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
+
+ lvi.state = lvi.stateMask;
+ if (FALSE != SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)iFocus, (LPARAM)&lvi))
+ {
+ SNDMSG(hChannel, LVM_ENSUREVISIBLE, (WPARAM)iFocus, (LPARAM)FALSE);
+ MLSkinnedScrollWnd_UpdateBars(hChannel, TRUE);
+ }
+
+ return TRUE;
+}
+static void CALLBACK PodcastChannel_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ KillTimer(hwnd, eventId);
+ PodcastChannel_SelectionChanged(hwnd, TRUE);
+}
+
+static void CALLBACK PodcastItem_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ KillTimer(hwnd, eventId);
+ PodcastItem_SelectionChanged(hwnd, TRUE);
+}
+
+wchar_t* PodcastCommand_OnSendToSelection(HWND hwnd)
+{
+ AutoLock channelLock (channels LOCKNAME("SendTo"));
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return 0;
+
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL == iChannel) return 0;
+ Channel *channel = &channels[iChannel];
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hItems) return 0;
+
+ INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
+ if (0 == selectedCount) return 0;
+
+ INT iSelected = -1;
+ WCHAR szPath[MAX_PATH * 2] = {0}, *path = NULL;
+ int buf_pos = 0, buf_size = 0;
+
+ while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED)))
+ {
+ size_t iItem = iSelected;
+ if (iItem < channel->items.size())
+ {
+ if (FALSE == podcast->itemAscending)
+ iItem = channel->items.size() - iItem - 1;
+
+ RSS::Item *downloadItem = &channel->items[iItem];
+ if (!((downloadItem->url && downloadItem->url[0]) &&
+ SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
+ PathFileExists(szPath)))
+ {
+ lstrcpyn(szPath, downloadItem->url, ARRAYSIZE(szPath));
+ }
+
+ listbuild(&path, buf_size, buf_pos, szPath);
+ }
+ }
+
+ if (path) path[buf_pos] = 0;
+ return path;
+}
+
+static void SubscriptionView_ListContextMenu(HWND hwnd, INT controlId, POINTS pts)
+{
+ HWND hControl = GetDlgItem(hwnd, controlId);
+ if (NULL == hControl) return;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+
+ RECT controlRect, headerRect;
+ if (FALSE == GetClientRect(hControl, &controlRect))
+ SetRectEmpty(&controlRect);
+ else
+ MapWindowPoints(hControl, HWND_DESKTOP, (POINT*)&controlRect, 2);
+
+ HWND hHeader = (HWND)SNDMSG(hControl, LVM_GETHEADER, 0, 0L);
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (-1 == pt.x && -1 == pt.y)
+ {
+ RECT rect;
+
+ rect.left = LVIR_BOUNDS;
+ INT iMark = SNDMSG(hControl, LVM_GETNEXTITEM, -1, LVNI_SELECTED | LVNI_FOCUSED);
+ if (-1 == iMark) iMark = SNDMSG(hControl, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
+ if (-1 != iMark && FALSE != SNDMSG(hControl, LVM_GETITEMRECT, (WPARAM)iMark, (LPARAM)&rect))
+ {
+ pt.x = rect.left + 4;
+ if(pt.x > rect.right) pt.x = rect.right;
+ pt.y = rect.bottom - (rect.bottom - rect.top)/3;
+
+ MapWindowPoints(hControl, HWND_DESKTOP, &pt, 1);
+ }
+ }
+
+ INT iItem = -1;
+
+ if ((-1 != pt.x || -1 != pt.y) && FALSE != PtInRect(&controlRect, pt))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return;
+ }
+ else
+ {
+ LVHITTESTINFO hitTest;
+ hitTest.pt = pt;
+ MapWindowPoints(HWND_DESKTOP, hControl, &hitTest.pt, 1);
+
+ iItem = (INT)SNDMSG(hControl, LVM_HITTEST, 0, (LPARAM)&hitTest);
+ if (0 == (LVHT_ONITEM & hitTest.flags)) iItem = -1;
+ }
+ }
+ else
+ {
+ pt.x = controlRect.left + 2;
+ pt.y = controlRect.top + 2;
+ if (headerRect.top == controlRect.top && headerRect.bottom > controlRect.top)
+ pt.y = headerRect.bottom + 2;
+ }
+
+ HMENU baseMenu = WASABI_API_LOADMENU(IDR_MENU1);
+ if (NULL == baseMenu) return;
+
+ HMENU menu = NULL;
+ switch(controlId)
+ {
+ case IDC_ITEMLIST: menu = PodcastItem_GetMenu(hwnd, baseMenu, iItem); break;
+ case IDC_CHANNELLIST: menu = PodcastChannel_GetMenu(hwnd, baseMenu, iItem); break;
+ }
+
+ if (NULL != menu)
+ {
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN | 0x1000/*TPM_VERPOSANIMATION*/, pt.x, pt.y, hwnd, NULL);
+ if (!SendMessage(hwnd, WM_COMMAND, r, 0))
+ {
+ s.menu_id = r;
+ if (s.mode == 2 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
+ {
+ s.mode = 3;
+
+ wchar_t* path = PodcastCommand_OnSendToSelection(hwnd);
+ if (path && *path)
+ {
+ s.data = path;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+ free(path);
+ }
+ }
+ }
+
+ if (s.mode)
+ {
+ s.mode=4;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ }
+
+ DestroyMenu(baseMenu);
+}
+
+static void SubscriptionView_UpdateLayout(HWND hwnd, BOOL fRedraw, UINT uReason, HRGN validRegion, POINTS layoutOffset)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ RECT clientRect;
+ if (FALSE == GetClientRect(hwnd, &clientRect)) return;
+
+ LONG clientWidth = clientRect.right - clientRect.left - WASABI_API_APP->getScaleX(2);
+ LONG clientHeight = clientRect.bottom - clientRect.top;
+
+ const INT szItems[LI_LAST] = { IDC_CHANNELLIST, IDC_ITEMLIST, IDC_VDIV,
+ IDC_FINDNEW, IDC_ADD, IDC_EDIT, IDC_DELETE, IDC_REFRESH,
+ IDC_HDIV,
+ IDC_EPISODE_INFO, IDC_DESCRIPTION,
+ IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_DOWNLOAD, IDC_VISIT, IDC_STATUS};
+
+ LAYOUTITEM *layout = (LAYOUTITEM*)calloc(ARRAYSIZE(szItems), sizeof(LAYOUTITEM));
+ if (NULL == layout) return;
+
+ Layout_Initialize(hwnd, szItems, ARRAYSIZE(szItems), layout);
+
+ if (episode_info_cy == 0 || (layout[LI_EPISODE_INFO].cy && episode_info_cy != layout[LI_EPISODE_INFO].cy))
+ {
+ HWND hControl = GetDlgItem(hwnd, IDC_EPISODE_INFO);
+ if (IsWindow(hControl))
+ {
+ HWND hHeader = ListView_GetHeader(hControl);
+ if (IsWindow(hHeader))
+ {
+ RECT rect;
+ GetClientRect(hHeader, &rect);
+ SetWindowPos(hControl, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
+ episode_info_cy = rect.bottom-rect.top;
+ }
+ else
+ episode_info_cy = layout[LI_EPISODE_INFO].cy;
+ }
+ else
+ episode_info_cy = layout[LI_EPISODE_INFO].cy;
+ }
+
+ LONG t = (clientWidth - layout[LI_VERT].cx) * podcast->vertDivider / 10000;
+ LONG t2 = clientRect.right - layout[LI_VERT].cx;
+
+ if (LAYOUTREASON_DIV_LEFT == uReason && t < clientRect.left + WASABI_API_APP->getScaleX(36))
+ t = clientRect.left;
+
+ if (LAYOUTREASON_DIV_RIGHT == uReason && t > (t2 - WASABI_API_APP->getScaleX(36)))
+ t = t2;
+
+ if (t < clientRect.left + WASABI_API_APP->getScaleX(36)) t = clientRect.left;
+ else if (t > t2 - WASABI_API_APP->getScaleX(36)) t = t2;
+
+ layout[LI_CHANNEL].x = WASABI_API_APP->getScaleX(1);
+ layout[LI_CHANNEL].y = WASABI_API_APP->getScaleX(1);
+ layout[LI_CHANNEL].cx = t;
+ layout[LI_VERT].x = LI_GET_R(layout[LI_CHANNEL]);
+ layout[LI_VERT].y = WASABI_API_APP->getScaleX(1);
+ layout[LI_ITEM].x = LI_GET_R(layout[LI_VERT]);
+ layout[LI_ITEM].y = WASABI_API_APP->getScaleX(1);
+ LI_SET_R(layout[LI_ITEM], clientWidth);
+
+ layout[LI_HORZ].cx = clientWidth - layout[LI_HORZ].x*WASABI_API_APP->getScaleX(2);
+ layout[LI_EPISODE_INFO].x = WASABI_API_APP->getScaleX(1);
+ layout[LI_EPISODE_INFO].cx = clientWidth - layout[LI_EPISODE_INFO].x;
+ layout[LI_INFO].cx = clientWidth - layout[LI_INFO].x*WASABI_API_APP->getScaleX(2) - 1;
+ LI_SET_R(layout[LI_STATUS], clientWidth);
+ if (layout[LI_STATUS].cx < WASABI_API_APP->getScaleX(20)) layout[LI_STATUS].cx = 0;
+
+ t = clientHeight - layout[LI_PLAY].cy;
+ int cum_width = 0, inc = 0, btnY = 0;
+ const INT szBottomRow[] = { LI_PLAY, LI_ENQUEUE, LI_CUSTOM, LI_DOWNLOAD, LI_VISIT, LI_STATUS, };
+ for (INT i = 0; i < ARRAYSIZE(szBottomRow); i++)
+ {
+ LAYOUTITEM *p = &layout[szBottomRow[i]];
+ if (IsWindow(p->hwnd))
+ {
+ if (!inc) p->x = 1;
+ else p->x = 1 + cum_width + WASABI_API_APP->getScaleX(4)*inc;
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(p->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(p->hwnd, buffer);
+
+ btnY = p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize));
+ p->y = clientHeight - p->cy;
+
+ if (szBottomRow[i] != LI_STATUS) // exclude the last one so it'll span the remainder of the space
+ {
+ if (LI_CUSTOM != szBottomRow[i] || customAllowed)
+ {
+ if (groupBtn && szBottomRow[i] == LI_PLAY && enqueuedef == 1)
+ {
+ p->flags |= SWP_HIDEWINDOW;
+ p->cy = 0;
+ continue;
+ }
+
+ if (groupBtn && szBottomRow[i] == LI_ENQUEUE && enqueuedef != 1)
+ {
+ p->flags |= SWP_HIDEWINDOW;
+ p->cy = 0;
+ continue;
+ }
+
+ if (groupBtn && (szBottomRow[i] == LI_PLAY || szBottomRow[i] == LI_ENQUEUE) && customAllowed)
+ {
+ p->flags |= SWP_HIDEWINDOW;
+ p->cy = 0;
+ continue;
+ }
+
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ p->cx = width;
+ p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize));
+ cum_width += width;
+ inc++;
+ }
+ else
+ {
+ p->flags |= SWP_HIDEWINDOW;
+ p->cy = 0;
+ continue;
+ }
+ }
+ else
+ {
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(p->hwnd, buffer);
+ btnY = p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize));
+ p->y = clientHeight - p->cy;
+ p->cx = clientWidth - cum_width - (WASABI_API_APP->getScaleX(6) * 2);
+ }
+ }
+ }
+
+ t = (clientHeight - layout[LI_HORZ].cy) * podcast->horzDivider / 10000;
+ t2 = layout[LI_PLAY].y - layout[LI_HORZ].cy;
+
+ if (LAYOUTREASON_DIV_TOP == uReason && t < clientRect.top + WASABI_API_APP->getScaleY(36 + btnY))
+ t = clientRect.top;
+
+ if (LAYOUTREASON_DIV_BOTTOM == uReason && t > (t2 - WASABI_API_APP->getScaleY(36 + btnY)))
+ t = t2;
+
+ if (t < clientRect.top + WASABI_API_APP->getScaleY(36 + btnY)) t = clientRect.top;
+ else if (t > t2 - WASABI_API_APP->getScaleY(36 + btnY)) t = t2;
+
+ cum_width = 0;
+ const INT szMiddleRow[] = { LI_FINDNEW, LI_ADD, LI_EDIT, LI_DELETE, LI_REFRESH, };
+ for (INT i = 0; i < ARRAYSIZE(szMiddleRow); i++)
+ {
+ LAYOUTITEM *p = &layout[szMiddleRow[i]];
+ p->x = 1 + (i ? cum_width + WASABI_API_APP->getScaleX(4)*i : 0);
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(p->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(p->hwnd, buffer);
+ // used to hide the 'directory' button if not using one
+ LONG width = (!i && !serviceUrl[0] ? WASABI_API_APP->getScaleX(-4) : LOWORD(idealSize) + WASABI_API_APP->getScaleX(6));
+ p->cx = width;
+ cum_width += width;
+
+ p->cy = (t < WASABI_API_APP->getScaleY(48)) ? 0 : WASABI_API_APP->getScaleY(HIWORD(idealSize));
+ p->y = t - p->cy;
+ }
+
+ layout[LI_HORZ].y = LI_GET_B(layout[LI_FINDNEW]);
+ layout[LI_EPISODE_INFO].y = LI_GET_B(layout[LI_HORZ]);
+ if (clientRect.bottom - layout[LI_EPISODE_INFO].y < WASABI_API_APP->getScaleY(episode_info_cy))
+ layout[LI_EPISODE_INFO].cy = 0;
+ else
+ layout[LI_EPISODE_INFO].cy = episode_info_cy;
+
+ layout[LI_INFO].y = LI_GET_B(layout[LI_EPISODE_INFO]);
+ LI_SET_B(layout[LI_INFO], layout[LI_PLAY].y - WASABI_API_APP->getScaleY(4));
+
+ t = layout[LI_FINDNEW].y;
+ if (layout[LI_FINDNEW].cy > 0) t -= podcast->middleRowSpace;
+
+ layout[LI_CHANNEL].cy = t;
+ layout[LI_VERT].cy = t;
+ layout[LI_ITEM].cy = t;
+ layout[LI_INFO].flags |= SWP_NOREDRAW;
+
+ Layout_SetVisibility(&clientRect, layout, ARRAYSIZE(szItems));
+ Layout_Perform(hwnd, layout, ARRAYSIZE(szItems), fRedraw);
+ PodcastChannel_SyncHeaderSize(hwnd, fRedraw);
+
+ if (NULL != validRegion)
+ {
+ RECT validRect;
+ CopyRect(&validRect, &clientRect);
+ validRect.right += (layout[LI_INFO].rect.right - LI_GET_R(layout[LI_INFO]));
+ validRect.bottom += (layout[LI_PLAY].rect.bottom - LI_GET_B(layout[LI_PLAY]));
+ Layout_GetValidRgn(validRegion, layoutOffset, &validRect, layout, ARRAYSIZE(szItems));
+ }
+
+ free(layout);
+}
+
+static BOOL SubscriptionView_UpdateImageList(HIMAGELIST imageList, COLORREF rgbBk, COLORREF rgbFg)
+{
+ if(NULL == imageList)
+ return FALSE;
+
+ INT imageCount = ImageList_GetImageCount(imageList);
+
+ MLIMAGESOURCE source;
+ ZeroMemory(&source, sizeof(source));
+ source.cbSize = sizeof(source);
+ source.hInst = plugin.hDllInstance;
+ source.type = SRC_TYPE_PNG;
+
+ MLIMAGEFILTERAPPLYEX filter;
+ ZeroMemory(&filter, sizeof(filter));
+ filter.cbSize = sizeof(filter);
+ filter.filterUID = MLIF_FILTER3_UID;
+ filter.rgbBk = rgbBk;
+ filter.rgbFg = rgbFg;
+
+ const INT szImages[] = { IDR_TEXT_ICON, IDR_DOWNLOAD_ICON, IDR_MEDIA_ICON, IDR_MEDIA_ICON, };
+
+ for (INT i = 0; i < ARRAYSIZE(szImages); i++)
+ {
+ source.lpszName = MAKEINTRESOURCE(szImages[i]);
+ HBITMAP bitmap = MLImageLoader_LoadDib(plugin.hwndLibraryParent, &source);
+ if (NULL != bitmap)
+ {
+ DIBSECTION dib;
+ if (sizeof(dib) == GetObject(bitmap, sizeof(dib), &dib))
+ {
+ filter.pData = (BYTE*)dib.dsBm.bmBits;
+ filter.bpp = dib.dsBm.bmBitsPixel;
+ filter.cx = dib.dsBm.bmWidth;
+ filter.cy = dib.dsBm.bmHeight;
+
+ if (filter.cy < 0)
+ filter.cy = -filter.cy;
+
+ if (MLImageFilter_ApplyEx(plugin.hwndLibraryParent, &filter))
+ {
+ INT result = (i < imageCount) ?
+ ImageList_Replace(imageList, i, bitmap, NULL) :
+ ImageList_Add(imageList, bitmap, NULL);
+ }
+ }
+
+ DeleteObject(bitmap);
+ }
+ }
+
+ return TRUE;
+}
+
+static void SubscriptionView_UpdateSkin(HWND hwnd)
+{
+ UINT windowStyle = GetWindowLongPtr(hwnd, GWL_STYLE);
+ if (0 != (WS_VISIBLE & windowStyle))
+ SetWindowLongPtr(hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE);
+
+ HWND hControl;
+ COLORREF rgbBk = dialogSkinner.Color(WADLG_ITEMBG);
+ COLORREF rgbText = dialogSkinner.Color(WADLG_ITEMFG);
+ HFONT hFont = dialogSkinner.GetFont();
+
+ episode_info_cy = 0;
+
+ static const INT szLists[] = {IDC_CHANNELLIST, IDC_ITEMLIST, IDC_EPISODE_INFO};
+ for (INT i = 0; i < ARRAYSIZE(szLists); i++)
+ {
+ if (NULL != (hControl = GetDlgItem(hwnd, szLists[i])))
+ {
+ ListView_SetBkColor(hControl, rgbBk);
+ ListView_SetTextBkColor(hControl, rgbBk);
+ ListView_SetTextColor(hControl, rgbText);
+ if (NULL != hFont)
+ {
+ SendMessage(hControl, WM_SETFONT, (WPARAM)hFont, 0L);
+ }
+
+ HIMAGELIST imageList = (HIMAGELIST)SendMessage(hControl, LVM_GETIMAGELIST, LVSIL_SMALL, 0L);
+ if (NULL != imageList)
+ {
+ SubscriptionView_UpdateImageList(imageList, rgbBk, rgbText);
+ }
+ }
+ }
+
+ if (0 != (WS_VISIBLE & windowStyle))
+ {
+ windowStyle = GetWindowLongPtr(hwnd, GWL_STYLE);
+ if (0 == (WS_VISIBLE & windowStyle))
+ SetWindowLongPtr(hwnd, GWL_STYLE, windowStyle | WS_VISIBLE);
+
+ RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
+ SubscriptionView_UpdateInfo(hwnd);
+ }
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ SubscriptionView_SetDescription(hwnd, podcast->description);
+
+ HRGN validRegion = (NULL != podcast->updateRegion) ? CreateRectRgn(0, 0, 0, 0) : NULL;
+ SubscriptionView_UpdateLayout(hwnd, TRUE, LAYOUTREASON_RESIZE, validRegion, podcast->updateOffset);
+ if (NULL != validRegion)
+ {
+ CombineRgn(podcast->updateRegion, podcast->updateRegion, validRegion, RGN_DIFF);
+ DeleteObject(validRegion);
+ }
+}
+
+static void SubscriptionView_SkinControls(HWND hwnd, const INT *itemList, INT itemCount, UINT skinType, UINT skinStyle)
+{
+ FLICKERFIX ff = {0, FFM_ERASEINPAINT};
+ MLSKINWINDOW skinWindow = {0};
+ skinWindow.style = skinStyle;
+ skinWindow.skinType = skinType;
+
+ for(INT i = 0; i < itemCount; i++)
+ {
+ ff.hwnd = skinWindow.hwndToSkin = GetDlgItem(hwnd, itemList[i]);
+ if (IsWindow(skinWindow.hwndToSkin))
+ {
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+ }
+}
+
+static void SubscriptionView_InitMetrics(HWND hwnd)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ HWND hControl;
+ RECT rect;
+ LONG t;
+
+ podcast->vertDivider = (LONG)(channelDividerPercent * 10000);
+ podcast->horzDivider = (LONG)(htmlDividerPercent * 10000);
+
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_ADD)) && FALSE != GetWindowRect(hControl, &rect))
+ {
+ t = rect.top + WASABI_API_APP->getScaleY(2);
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_CHANNELLIST)) && FALSE != GetWindowRect(hControl, &rect))
+ podcast->middleRowSpace = t - rect.bottom;
+ }
+
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_PLAY)) && FALSE != GetWindowRect(hControl, &rect))
+ {
+ t = rect.top + WASABI_API_APP->getScaleY(2);
+ if (NULL != (hControl = GetDlgItem(hwnd, IDC_DESCRIPTION)) && FALSE != GetWindowRect(hControl, &rect))
+ podcast->bottomRowSpace = t - rect.bottom;
+ }
+}
+
+static void PodcastChannel_OnKeyDown(HWND hwnd, NMLVKEYDOWN *pkd)
+{
+ bool ctrl = (0 != (0x8000 & GetAsyncKeyState(VK_CONTROL))),
+ shift = (0 != (0x8000 & GetAsyncKeyState(VK_SHIFT)));
+ switch(pkd->wVKey)
+ {
+ case VK_DELETE:
+ if(!shift && !ctrl)
+ {
+ SENDCMD(hwnd, IDC_DELETE, 0, 0);
+ }
+ break;
+
+ case VK_F5:
+ if(!ctrl)
+ {
+ SENDCMD(hwnd, (!shift ? IDC_REFRESH : IDC_REFRESHALL), 0, 0);
+ }
+ break;
+
+ case VK_F7:
+ if (!shift && !ctrl)
+ {
+ SENDCMD(hwnd, IDC_VISIT, 0, 0);
+ }
+ break;
+
+ case VK_F2:
+ if(!shift && !ctrl)
+ {
+ SENDCMD(hwnd, IDC_EDIT, 0, 0);
+ }
+ break;
+
+ case VK_INSERT:
+ if(!shift && !ctrl)
+ {
+ SENDCMD(hwnd, IDC_ADD, 0, 0);
+ }
+ break;
+
+ case VK_LEFT:
+ if(!shift && !ctrl)
+ {
+ PodcastChannel_SelectNext(hwnd, FALSE);
+ }
+ break;
+
+ case VK_RIGHT:
+ if(!shift && !ctrl)
+ {
+ PodcastChannel_SelectNext(hwnd, TRUE);
+ }
+ break;
+ }
+}
+
+static void PodcastChannel_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
+{
+ BOOL fAscending;
+ INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, &fAscending);
+ fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : TRUE;
+
+ PodcastChannel_Sort(hwnd, plv->iSubItem, fAscending);
+}
+
+static void PodcastChannel_OnDoubleClick(HWND hwnd, NMITEMACTIVATE *pia)
+{
+ if(pia->iItem != -1)
+ {
+ LVITEM lvi;
+ lvi.state = LVIS_SELECTED;
+ lvi.stateMask = LVIS_SELECTED;
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+
+ if (NULL != hItems &&
+ FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi))
+ {
+ SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
+ lvi.state = 0;
+ SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
+ }
+ }
+}
+
+static void PodcastChannel_OnReturn(HWND hwnd, NMHDR *pnmh)
+{
+ LVITEM lvi;
+ lvi.state = LVIS_SELECTED;
+ lvi.stateMask = LVIS_SELECTED;
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+
+ if (NULL != hItems &&
+ FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi))
+ {
+ SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
+ lvi.state = 0;
+ SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
+ }
+}
+
+static void PodcastChannel_OnGetDispInfo(HWND hwnd, NMLVDISPINFO *pdi)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ if (0 != (LVIF_TEXT & pdi->item.mask))
+ {
+ switch (pdi->item.iSubItem)
+ {
+ case COLUMN_TITLE:
+ {
+ pdi->item.pszText[0] = 0; // turn into an empty string as a safety precaution
+ AutoLock channelLock(channels LOCKNAME("FillChannelTitle"));
+ size_t iChannel = (size_t)pdi->item.iItem;
+ if (iChannel < channels.size())
+ {
+ if (FALSE == podcast->channelAscending)
+ iChannel = channels.size() - iChannel - 1;
+
+ const wchar_t *title = channels[iChannel].title;
+ if (title)
+ StringCchCopy(pdi->item.pszText, pdi->item.cchTextMax, title);
+ }
+ }
+ break;
+ }
+ }
+}
+
+static void PodcastChannel_OnItemChanged(HWND hwnd, NMLISTVIEW *plv)
+{
+ if ((plv->iItem != -1) && (LVIS_SELECTED & plv->uNewState) != (LVIS_SELECTED & plv->uOldState))
+ PodcastChannel_SelectionChanged(hwnd, FALSE);
+}
+
+static void PodcastChannel_OnSetFocus(HWND hwnd, NMHDR *pnmh)
+{
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hItems) return;
+
+ LVITEM lvi;
+ lvi.state = 0;
+ lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
+ SNDMSG(hItems, LVM_SETITEMSTATE, -1, (LPARAM)&lvi);
+ SNDMSG(hItems, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)-1);
+}
+
+static LRESULT PodcastChannel_OnNotify(HWND hwnd, NMHDR *pnmh)
+{
+ switch (pnmh->code)
+ {
+ case LVN_KEYDOWN: PodcastChannel_OnKeyDown(hwnd, (NMLVKEYDOWN *)pnmh); break;
+ case LVN_COLUMNCLICK: PodcastChannel_OnColumnClick(hwnd, (NMLISTVIEW*)pnmh); break;
+ case NM_DBLCLK: PodcastChannel_OnDoubleClick(hwnd, (NMITEMACTIVATE*)pnmh); break;
+ case LVN_GETDISPINFO: PodcastChannel_OnGetDispInfo(hwnd, (NMLVDISPINFO*)pnmh); break;
+ case LVN_ITEMCHANGED: PodcastChannel_OnItemChanged(hwnd, (NMLISTVIEW*)pnmh); break;
+ case NM_SETFOCUS: PodcastChannel_OnSetFocus(hwnd, pnmh); break;
+ case NM_RETURN: PodcastChannel_OnReturn(hwnd, pnmh); break;
+ }
+
+ return 0;
+}
+
+bool ParseDuration(const wchar_t *duration, int *out_hours, int *out_minutes, int *out_seconds)
+{
+ if (duration && duration[0])
+ {
+ const wchar_t *colon_position = wcschr(duration, L':');
+ if (colon_position == 0)
+ {
+ int v = _wtoi(duration);
+ *out_hours = v / 3600;
+ *out_minutes = (v/60)%60;
+ *out_seconds = v % 60;
+ return true;
+ }
+ else
+ {
+ int first_time = _wtoi(duration);
+ duration = colon_position+1;
+ colon_position = wcschr(duration, L':');
+ if (colon_position == 0) // only have MM:SS
+ {
+ *out_hours=0;
+ *out_minutes = first_time;
+ *out_seconds = _wtoi(duration);
+ }
+ else
+ {
+ *out_hours = first_time;
+ *out_minutes = _wtoi(duration);
+ *out_seconds = _wtoi(colon_position+1);
+ }
+ return true;
+ }
+ }
+ else
+ {
+return false;
+ }
+}
+
+static void PodcastItem_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
+{
+ BOOL fAscending;
+ INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, &fAscending);
+ fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : TRUE;
+
+ PodcastItem_Sort(hwnd, plv->iSubItem, fAscending);
+}
+
+static void PodcastItem_OnGetDispInfo(HWND hwnd, NMLVDISPINFO *pdi)
+{
+ AutoLock channelLock (channels LOCKNAME("Item LVN_GETDISPINFO"));
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ size_t iChannel = podcast->channelActive;
+ if (iChannel >= channels.size()) return;
+
+ size_t iItem = pdi->item.iItem;
+ if (iItem >= channels[iChannel].items.size()) return;
+ if (FALSE == podcast->itemAscending) iItem = channels[iChannel].items.size() - iItem - 1;
+
+ RSS::Item *item = &channels[iChannel].items[iItem];
+
+ if (0 != (LVIF_IMAGE & pdi->item.mask))
+ {
+ switch (pdi->item.iSubItem)
+ {
+ case COLUMN_TITLE:
+ if (!item->url || !item->url[0])
+ pdi->item.iImage = 0;
+ else if (item->downloaded)
+ pdi->item.iImage = 1;
+ else if (item->listened)
+ pdi->item.iImage = 2;
+ else
+ pdi->item.iImage = 3;
+ break;
+ }
+ }
+
+ if (0 != (LVIF_TEXT &pdi->item.mask))
+ {
+ pdi->item.pszText[0] = L'\0';
+ switch (pdi->item.iSubItem)
+ {
+ case COLUMN_TITLE:
+ lstrcpyn(pdi->item.pszText, item->itemName, pdi->item.cchTextMax);
+ break;
+ case COLUMN_MEDIA_TIME:
+ {
+ int hours, minutes, seconds;
+ if (ParseDuration(item->duration, &hours, &minutes, &seconds))
+ {
+ if (hours)
+ {
+ StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"%d:%02d:%02d", hours, minutes, seconds);
+ }
+ else if (minutes)
+ {
+ StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"%d:%02d", minutes, seconds);
+ }
+ else
+ {
+ StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"0:%02d", seconds);
+ }
+ }
+ }
+
+ break;
+ case COLUMN_MEDIA_SIZE:
+ if (item->size)
+ {
+ WASABI_API_LNG->FormattedSizeString(pdi->item.pszText, pdi->item.cchTextMax, item->size);
+ }
+ break;
+ //case COLUMN_MEDIA:
+ // WASABI_API_LNGSTRINGW_BUF((item->url && item->url[0]) ? IDS_ARTICLE_WITH_MEDIA : IDS_TEXT_ARTICLE,
+ // pdi->item.pszText, pdi->item.cchTextMax);
+ // break;
+ case COLUMN_DATEADDED:
+ MakeDateString(item->publishDate, pdi->item.pszText, pdi->item.cchTextMax);
+ break;
+ }
+ }
+}
+
+static void PodcastItem_OnItemChanged(HWND hwnd, NMLISTVIEW *plv)
+{
+ if ((LVIS_SELECTED & plv->uNewState) != (LVIS_SELECTED & plv->uOldState))
+ PodcastItem_SelectionChanged(hwnd, FALSE);
+}
+
+static void PodcastItem_OnSetFocus(HWND hwnd, NMHDR *pnmh)
+{
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hItems) return;
+
+ INT iMark = (INT)SNDMSG(hItems, LVM_GETNEXTITEM, -1, LVNI_FOCUSED | LVNI_SELECTED);
+ if (-1 == iMark)
+ {
+ POINT pt;
+ RECT rect;
+ if (GetCursorPos(&pt) && GetClientRect(hwnd, &rect))
+ {
+ MapWindowPoints(hwnd, HWND_DESKTOP, (POINT*)&rect, 2);
+ if (PtInRect(&rect, pt))
+ {
+ const INT szKey[] = { VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, /*VK_XBUTTON1*/0x05, /*VK_XBUTTON2*/0x06, };
+ for (INT i = 0; i < ARRAYSIZE(szKey); i++)
+ {
+ if (0 != (0x80000000 & GetAsyncKeyState(szKey[i])))
+ return;
+ }
+ }
+ }
+
+ LVITEM lvi;
+ lvi.state = LVIS_SELECTED | LVIS_FOCUSED;
+ lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
+ if (FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, 0, (LPARAM)&lvi))
+ {
+ SNDMSG(hItems, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)0);
+ SNDMSG(hItems, LVM_ENSUREVISIBLE, 0, FALSE);
+ }
+
+ }
+}
+
+static void PodcastItem_OnDoubleClick(HWND hwnd, NMITEMACTIVATE *pia)
+{
+ SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
+}
+
+static void PodcastItem_OnKeyDown(HWND hwnd, NMLVKEYDOWN *pkd)
+{
+ bool ctrl = (0 != (0x8000 & GetAsyncKeyState(VK_CONTROL))),
+ shift = (0 != (0x8000 & GetAsyncKeyState(VK_SHIFT)));
+ switch(pkd->wVKey)
+ {
+ case VK_F5:
+ if (!shift && !ctrl)
+ {
+ SENDCMD(hwnd, IDC_REFRESH, 0, 0);
+ }
+ break;
+
+ case VK_F7:
+ if (!shift && !ctrl)
+ {
+ SENDCMD(hwnd, IDC_VISIT, 0, 0);
+ }
+ break;
+
+ case 'A':
+ if (!shift && ctrl)
+ {
+ SENDCMD(hwnd, IDC_SELECTALL, 0, 0);
+ }
+ break;
+
+ case 'F':
+ if (!shift && ctrl)
+ {
+ SENDCMD(hwnd, IDC_EXPLORERITEMFOLDER, 0, 0);
+ }
+ break;
+
+ case 'D':
+ if (!shift && ctrl)
+ {
+ SENDCMD(hwnd, IDC_DOWNLOAD, 0, 0);
+ }
+ break;
+ }
+}
+
+static void PodcastItem_OnReturn(HWND hwnd, NMHDR *pnmh)
+{
+ SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
+}
+
+static LRESULT PodcastItem_OnNotify(HWND hwnd, NMHDR *pnmh)
+{
+ switch (pnmh->code)
+ {
+ case NM_SETFOCUS: PodcastItem_OnSetFocus(hwnd, pnmh); break;
+ case LVN_COLUMNCLICK: PodcastItem_OnColumnClick(hwnd, (NMLISTVIEW*)pnmh); break;
+ case NM_DBLCLK: PodcastItem_OnDoubleClick(hwnd, (NMITEMACTIVATE*)pnmh); break;
+ case LVN_GETDISPINFO: PodcastItem_OnGetDispInfo(hwnd, (NMLVDISPINFO*)pnmh); break;
+ case LVN_ITEMCHANGED: PodcastItem_OnItemChanged(hwnd, (NMLISTVIEW*)pnmh); break;
+ case LVN_KEYDOWN: PodcastItem_OnKeyDown(hwnd, (NMLVKEYDOWN *)pnmh); break;
+ case NM_RETURN: PodcastItem_OnReturn(hwnd, pnmh); break;
+ }
+
+ return 0;
+}
+
+static void CALLBACK SubscriptionView_OnDividerMoved(HWND hDivider, INT position, LPARAM param)
+{
+ HWND hwnd = GetParent(hDivider);
+ if(NULL == hwnd) return;
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ RECT clientRect, dividerRect;
+ if (FALSE == GetClientRect(hwnd, &clientRect) ||
+ FALSE == GetWindowRect(hDivider, &dividerRect))
+ {
+ return;
+ }
+
+ LONG t;
+
+ UINT reason = 0;
+
+ switch(param)
+ {
+ case IDC_HDIV:
+ t = (clientRect.bottom - clientRect.top) - (dividerRect.bottom - dividerRect.top);
+ t = 10000 * position / t;
+ if (podcast->horzDivider != t)
+ {
+ reason = (podcast->horzDivider > t) ? LAYOUTREASON_DIV_TOP : LAYOUTREASON_DIV_BOTTOM;
+ podcast->horzDivider = t;
+ }
+ break;
+ case IDC_VDIV:
+ t = (clientRect.right - clientRect.left) - (dividerRect.right - dividerRect.left);
+ t = 10000 * position / t;
+ if (podcast->vertDivider != t)
+ {
+ reason = (podcast->vertDivider > t) ? LAYOUTREASON_DIV_LEFT : LAYOUTREASON_DIV_RIGHT;
+ podcast->vertDivider = t;
+ }
+ break;
+ }
+
+ if (0 != reason)
+ {
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+
+ POINTS pts = {0,0};
+
+ HRGN updateRegion = CreateRectRgnIndirect(&rc);
+ HRGN validRegion = CreateRectRgn(0, 0, 0, 0);
+
+ SubscriptionView_UpdateLayout(hwnd, FALSE, reason, validRegion, pts);
+ CombineRgn(updateRegion, updateRegion, validRegion, RGN_DIFF);
+ RedrawWindow(hwnd, NULL ,updateRegion, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+
+ DeleteObject(updateRegion);
+ DeleteObject(validRegion);
+ }
+}
+
+static INT_PTR SubscriptionView_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
+{
+ SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)SUBSCRIPTIONVIEW_NAME);
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_DOWNLOAD_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwnd, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ PODCAST *podcast = (PODCAST*)calloc(1, sizeof(PODCAST));
+ if (NULL == podcast || FALSE == SetProp(hwnd, MAKEINTATOM(VIEWPROP), podcast))
+ {
+ if (NULL != podcast) free(podcast);
+ return 0;
+ }
+
+ current_window = hwnd;
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ g_context_menus3 = WASABI_API_LOADMENU(IDR_MENU1);
+ groupBtn = ML_GROUPBTN_VAL();
+ enqueuedef = (ML_ENQDEF_VAL() == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwnd, (INT_PTR)MAKELONG(IDC_CUSTOM, IDC_ENQUEUE), (INT_PTR)L"ml_wire"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwnd, IDC_CUSTOM, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ SubscriptionView_InitMetrics(hwnd);
+
+ podcast->channelActive = -1;
+
+ HWND hLibrary = plugin.hwndLibraryParent;
+ MLSkinWindow2(hLibrary, hwnd, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ PodcastChannel_InitializeList(hwnd);
+ PodcastItem_InitializeList(hwnd);
+ PodcastInfo_InitializeList(hwnd);
+
+ const INT szControls[] = { IDC_FINDNEW, IDC_ADD, IDC_EDIT, IDC_DELETE, IDC_REFRESH, IDC_DOWNLOAD, IDC_VISIT, IDC_STATUS, };
+ SubscriptionView_SkinControls(hwnd, szControls, ARRAYSIZE(szControls), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ const INT szControlz[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, };
+ SubscriptionView_SkinControls(hwnd, szControlz, ARRAYSIZE(szControlz), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0));
+
+ HWND hControl = GetDlgItem(hwnd, IDC_HDIV);
+ MLSkinWindow2(hLibrary, hControl, SKINNEDWND_TYPE_DIVIDER, SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT | SWDIV_HORZ /*| SWDIV_NOHILITE*/);
+ MLSkinnedDivider_SetCallback(hControl, SubscriptionView_OnDividerMoved, IDC_HDIV);
+
+ hControl = GetDlgItem(hwnd, IDC_VDIV);
+ MLSkinWindow2(hLibrary, hControl, SKINNEDWND_TYPE_DIVIDER, SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT | SWDIV_VERT /*| SWDIV_NOHILITE*/);
+ MLSkinnedDivider_SetCallback(hControl, SubscriptionView_OnDividerMoved, IDC_VDIV);
+
+ PodcastChannel_Sort(hwnd, 0, channelSortAscending);
+ PodcastItem_Sort(hwnd, currentItemSort, itemSortAscending);
+
+ OmService *service = (OmService*)lParam;
+ HWND hBrowser = NULL;
+ if (NULL != OMBROWSERMNGR &&
+ SUCCEEDED(OMBROWSERMNGR->Initialize(NULL, plugin.hwndWinampParent)) &&
+ SUCCEEDED(OMBROWSERMNGR->CreateView(service, hwnd, NAVIGATE_BLANK, NBCS_NOTOOLBAR | NBCS_NOSTATUSBAR, &hBrowser)))
+ {
+ HWND hTarget = GetDlgItem(hwnd, IDC_DESCRIPTION);
+ if (NULL != hTarget)
+ {
+ RECT rect;
+ if (GetWindowRect(hTarget, &rect))
+ {
+ MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rect, 2);
+ SetWindowPos(hBrowser, hTarget, rect.left, rect.top, rect.right -rect.left, rect.bottom - rect.top,
+ SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+ }
+ DestroyWindow(hTarget);
+ }
+
+ SetWindowLongPtr(hBrowser, GWLP_ID, IDC_DESCRIPTION);
+ ShowWindow(hBrowser, SW_SHOWNA);
+ }
+
+ Downloads_UpdateButtonText(hwnd, enqueuedef);
+ SendMessage(hwnd, WM_DISPLAYCHANGE, 0, 0L);
+
+ wchar_t status[256] = {0};
+ cloud.GetStatus(status, 256);
+ SubscriptionView_SetStatus(hwnd, status);
+ SubscriptionView_RefreshChannels(hwnd, TRUE);
+
+ // evil to do but as the data is known and kept by us then this should be ok
+ if (channelLastSelection != -1)
+ {
+ static LV_ITEM _macro_lvi;
+ _macro_lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
+ _macro_lvi.state = LVIS_SELECTED | LVIS_FOCUSED;
+ PostMessage(GetDlgItem(hwnd, IDC_CHANNELLIST), LVM_SETITEMSTATE,
+ (WPARAM)channelLastSelection, (LPARAM)(LV_ITEM *)&_macro_lvi);
+ }
+
+ return 0;
+}
+
+static void SubscriptionView_OnDestroy(HWND hwnd)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ RemoveProp(hwnd, MAKEINTATOM(VIEWPROP));
+
+ if (NULL != podcast)
+ {
+ htmlDividerPercent = podcast->horzDivider / 10000.0f;
+ channelDividerPercent = podcast->vertDivider / 10000.0f;
+
+ Plugin_FreeString(podcast->infoUrl);
+ Plugin_FreeString(podcast->description);
+ free(podcast);
+ }
+
+ BOOL fAscending;
+
+ currentItemSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, &fAscending);
+ itemSortAscending = (-1 != currentItemSort) ? (FALSE != fAscending) : true;
+
+ INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, &fAscending);
+ channelSortAscending = (-1 != iSort) ? (FALSE != fAscending) : true;
+
+ INT iSelected = (INT)SNDMSG(GetDlgItem(hwnd, IDC_CHANNELLIST), LVM_GETNEXTITEM, -1, LVNI_SELECTED);
+
+ HWND hControl = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL != hControl)
+ {
+ itemTitleWidth = ListView_GetColumnWidth(hControl, COLUMN_TITLE);
+ itemDateWidth = ListView_GetColumnWidth(hControl, COLUMN_DATEADDED);
+ //itemMediaWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA);
+ itemMediaWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA_TIME);
+ itemSizeWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA_SIZE);
+
+ HIMAGELIST imageList = (HIMAGELIST)SendMessage(hControl, LVM_GETIMAGELIST, LVSIL_SMALL, 0L);
+ if (NULL != imageList)
+ ImageList_Destroy(imageList);
+ }
+
+ // ensure view settings are saved...
+ SaveAll(true);
+}
+
+static void SubscriptionView_OnWindowPosChanged(HWND hwnd, WINDOWPOS *pwp)
+{
+ if (SWP_NOSIZE == ((SWP_NOSIZE | SWP_FRAMECHANGED) & pwp->flags)) return;
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ HRGN validRegion = (NULL != podcast->updateRegion) ? CreateRectRgn(0, 0, 0, 0) : NULL;
+
+ SubscriptionView_UpdateLayout(hwnd, (0 == (SWP_NOREDRAW & pwp->flags)),
+ LAYOUTREASON_RESIZE, validRegion, podcast->updateOffset);
+
+ if (NULL != validRegion)
+ {
+ CombineRgn(podcast->updateRegion, podcast->updateRegion, validRegion, RGN_DIFF);
+ DeleteObject(validRegion);
+ }
+}
+
+static void SubscriptionView_OnContextMenu(HWND hwnd, HWND hTarget, POINTS pts)
+{
+ INT controlId = (NULL != hTarget) ? (INT)GetWindowLongPtr(hTarget, GWLP_ID) : 0;
+
+ switch(controlId)
+ {
+ case IDC_ITEMLIST:
+ case IDC_CHANNELLIST:
+ SubscriptionView_ListContextMenu(hwnd, controlId, pts);
+ break;
+ }
+}
+
+static void SubscriptionView_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS updateOffset)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ podcast->updateRegion = updateRegion;
+ podcast->updateOffset = updateOffset;
+}
+
+static void PodcastCommand_OnDeleteChannel(HWND hwnd)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ AutoLock lock (channels LOCKNAME("DeleteChannel"));
+
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL == iChannel) return;
+
+ WCHAR szText[1024] = {0}, szBuffer[1024] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SURE_YOU_WANT_TO_REMOVE_THIS, szBuffer, ARRAYSIZE(szBuffer));
+ StringCchPrintf(szText, ARRAYSIZE(szText), szBuffer, channels[iChannel].title, channels[iChannel].url);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRM, szBuffer, ARRAYSIZE(szBuffer));
+
+ if (IDYES == MessageBox(hwnd, szText, szBuffer, MB_YESNO | MB_ICONWARNING))
+ {
+ channels.RemoveChannel((int)iChannel);
+ SubscriptionView_RefreshChannels(hwnd, FALSE);
+ SaveAll();
+ }
+}
+
+static void PodcastCommand_OnVisitSite(HWND hwnd)
+{
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast || NULL == podcast->infoUrl)
+ return;
+
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, podcast->infoUrl);
+}
+
+static void PodcastCommand_OnRefreshChannel(HWND hwnd)
+{
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL != iChannel)
+ {
+ channels[iChannel].needsRefresh = true;
+ cloud.Pulse();
+ }
+}
+
+static void PodcastCommand_OnEditChannel(HWND hwnd)
+{
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL == iChannel) return;
+
+ ChannelEditor_Show(hwnd, iChannel, CEF_CENTEROWNER);
+
+ HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL != hChannel)
+ PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hChannel, TRUE);
+}
+
+static void PodcastCommand_OnAddChannel(HWND hwnd)
+{
+ ChannelEditor_Show(hwnd, 0, CEF_CENTEROWNER | CEF_CREATENEW);
+
+ HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL != hChannel)
+ PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hChannel, TRUE);
+}
+
+static void PodcastCommand_OnFindNewChannel(HWND hwnd)
+{
+ HNAVITEM hItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL);
+ MLNavItem_Select(plugin.hwndLibraryParent, hItem);
+}
+
+static void PodcastCommand_OnRefreshAll(HWND hwnd)
+{
+ cloud.RefreshAll();
+ cloud.Pulse();
+}
+
+static void PodcastCommand_OnPlaySelection(HWND hwnd, BOOL fEnqueue, BOOL fForce)
+{
+ AutoLock channelLock (channels LOCKNAME("PlaySelection"));
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL == iChannel) return;
+ Channel *channel = &channels[iChannel];
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hItems) return;
+
+ INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
+ if (0 == selectedCount) return;
+
+ size_t indexCount = 0;
+ size_t *indexList = (size_t*)calloc(selectedCount, sizeof(size_t));
+ if (NULL == indexList) return;
+
+ INT iSelected = -1;
+
+ while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED)))
+ {
+ size_t iItem = iSelected;
+ if (iItem < channel->items.size())
+ {
+ if (FALSE == podcast->itemAscending)
+ iItem = channel->items.size() - iItem - 1;
+
+ indexList[indexCount++] = iItem;
+ }
+ }
+
+ if (0 != indexCount)
+ {
+ SubscriptionView_Play(hwnd, iChannel, indexList, indexCount, fEnqueue, fForce);
+ }
+
+ free(indexList);
+}
+
+static void PodcastCommand_OnDownloadSelection( HWND hwnd )
+{
+ AutoLock channelLock( channels LOCKNAME( "DownloadSelection" ) );
+
+ PODCAST *podcast = GetPodcast( hwnd );
+ if ( NULL == podcast ) return;
+
+ size_t iChannel = PodcastChannel_GetActive( hwnd );
+ if ( BAD_CHANNEL == iChannel ) return;
+ Channel *channel = &channels[ iChannel ];
+
+ HWND hItems = GetDlgItem( hwnd, IDC_ITEMLIST );
+ if ( NULL == hItems ) return;
+
+ INT selectedCount = (INT)SNDMSG( hItems, LVM_GETSELECTEDCOUNT, 0, 0L );
+ if ( 0 == selectedCount ) return;
+
+ INT iSelected = -1;
+
+ WCHAR szPath[ MAX_PATH * 2 ] = { 0 };
+
+ size_t scheduled = 0;
+
+ while ( -1 != ( iSelected = SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED ) ) )
+ {
+ size_t iItem = iSelected;
+ if ( iItem < channel->items.size() )
+ {
+ if ( FALSE == podcast->itemAscending )
+ iItem = channel->items.size() - iItem - 1;
+
+ RSS::Item *downloadItem = &channel->items[ iItem ];
+ if ( ( downloadItem->url && downloadItem->url[ 0 ] ) && SUCCEEDED( downloadItem->GetDownloadFileName( channel->title, szPath, ARRAYSIZE( szPath ), TRUE ) ) )
+ {
+ if ( !wa::files::file_exists( szPath ) )
+ {
+ wchar_t *url = urlencode( downloadItem->url );
+ downloader.Download( url, szPath, channel->title, downloadItem->itemName, downloadItem->publishDate );
+ downloadItem->downloaded = true;
+ if ( 0 == scheduled )
+ {
+ SendMessage( hwnd, SVM_SETSTATUS, 0, (LPARAM)IDS_ADD_TO_DOWNLOADS );
+ }
+ free( url );
+ scheduled++;
+ }
+ }
+ }
+ }
+
+ if ( 0 == scheduled )
+ SendMessage( hwnd, SVM_SETSTATUS, 0, (LPARAM)IDS_NO_MEDIA_TO_DOWNLOAD );
+}
+
+static void PodcastCommand_OnExploreItemFolder(HWND hwnd)
+{
+ AutoLock channelLock (channels LOCKNAME("Explore"));
+
+ PODCAST *podcast = GetPodcast(hwnd);
+ if (NULL == podcast) return;
+
+ size_t iChannel = PodcastChannel_GetActive(hwnd);
+ if (BAD_CHANNEL == iChannel) return;
+ Channel *channel = &channels[iChannel];
+
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hItems) return;
+
+ INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
+ if (0 == selectedCount) return;
+
+ INT iSelected = -1;
+
+ WCHAR szPath[MAX_PATH * 2] = {0};
+ WASABI_API_EXPLORERFINDFILE->Reset();
+
+ while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED)))
+ {
+ size_t iItem = iSelected;
+ if (iItem < channel->items.size())
+ {
+ if (FALSE == podcast->itemAscending)
+ iItem = channel->items.size() - iItem - 1;
+
+ RSS::Item *downloadItem = &channel->items[iItem];
+ if ((downloadItem->url && downloadItem->url[0]) &&
+ SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
+ PathFileExists(szPath))
+ {
+ WASABI_API_EXPLORERFINDFILE->AddFile(szPath);
+ }
+ }
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+}
+
+static void PodcastCommand_OnSelectAll(HWND hwnd)
+{
+ HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL == hItems) return;
+
+ INT iCount = (INT)SNDMSG(hItems, LVM_GETITEMCOUNT, 0, 0L);
+ if (0 != iCount)
+ {
+ INT iSelected = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
+ if (iSelected != iCount)
+ {
+ LVITEM lvi;
+ lvi.state = LVIS_SELECTED;
+ lvi.stateMask = LVIS_SELECTED;
+ SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
+ }
+ }
+}
+
+static void PodcastCommand_PlayEnqueue(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(g_context_menus3, 1);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ Downloads_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+static void SubscriptionView_OnCommand(HWND hwnd, INT controlId, INT eventId, HWND hControl)
+{
+ switch (controlId)
+ {
+ case IDC_CUSTOM:
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_PLAYACTION:
+ case IDC_ENQUEUEACTION:
+
+ if (eventId == MLBN_DROPDOWN)
+ {
+ PodcastCommand_PlayEnqueue(hwnd, hControl, controlId);
+ }
+ else
+ {
+ int action;
+ if (controlId == IDC_PLAY || controlId == IDC_PLAYACTION)
+ action = (eventId == 1) ? enqueuedef == 1 : 0;
+ else if (controlId == IDC_ENQUEUE || controlId == IDC_ENQUEUEACTION)
+ action = (eventId == 1) ? (enqueuedef != 1) : 1;
+ else
+ // so custom can work with the menu item part
+ break;
+
+ PodcastCommand_OnPlaySelection(hwnd, action, (controlId == IDC_PLAY || controlId == IDC_ENQUEUE));
+ }
+ break;
+
+ case IDC_VISIT: PodcastCommand_OnVisitSite(hwnd); break;
+ case IDC_DOWNLOAD: PodcastCommand_OnDownloadSelection(hwnd); break;
+ case IDC_EXPLORERITEMFOLDER: PodcastCommand_OnExploreItemFolder(hwnd); break;
+ case IDC_SELECTALL: PodcastCommand_OnSelectAll(hwnd); break;
+ case IDC_REFRESH: PodcastCommand_OnRefreshChannel(hwnd); break;
+ case IDC_EDIT: PodcastCommand_OnEditChannel(hwnd); break;
+ case IDC_ADD: PodcastCommand_OnAddChannel(hwnd); break;
+ case IDC_FINDNEW: PodcastCommand_OnFindNewChannel(hwnd); break;
+ case IDC_DELETE: PodcastCommand_OnDeleteChannel(hwnd); break;
+ case IDC_REFRESHALL: PodcastCommand_OnRefreshAll(hwnd); break;
+ }
+}
+
+static LRESULT SubscriptionView_OnNotify(HWND hwnd, INT controlId, NMHDR *pnmh)
+{
+ LRESULT result = 0;
+ switch (controlId)
+ {
+ case IDC_CHANNELLIST: result = PodcastChannel_OnNotify(hwnd, pnmh); break;
+ case IDC_ITEMLIST: result = PodcastItem_OnNotify(hwnd, pnmh); break;
+ }
+
+ return result;
+}
+
+static void SubscriptionView_OnChar(HWND hwnd, UINT vKey, UINT flags)
+{
+ switch(vKey)
+ {
+ case VK_DELETE:
+ if (GetFocus() == GetDlgItem(hwnd, IDC_CHANNELLIST))
+ SENDCMD(hwnd, IDC_DELETE, 0, 0L);
+ break;
+ }
+}
+
+static BOOL SubscriptionView_OnRefreshChannels(HWND hwnd, BOOL fSort)
+{
+ AutoLock lock (channels LOCKNAME("All_ChannelsUpdated"));
+
+ HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
+ if (NULL == hChannel) return FALSE;
+
+ size_t channelSize = channels.size();
+ size_t iSelected = (size_t)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
+
+ if (FALSE != fSort)
+ {
+ INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, NULL);
+ SubscriptionView_SortChannels(iSort);
+ }
+
+ SNDMSG(hChannel, LVM_SETITEMCOUNT, channelSize, 0L);
+ PodcastChannel_SyncHeaderSize(hwnd, TRUE);
+
+ BOOL selectChannel = FALSE;
+ if(BAD_CHANNEL != iSelected && iSelected >= channelSize)
+ {
+ iSelected = (channelSize - 1);
+ selectChannel = TRUE;
+ }
+
+ if (BAD_CHANNEL == iSelected && channelSize > 0)
+ {
+ iSelected = 0;
+ selectChannel = TRUE;
+ }
+
+ if (FALSE != selectChannel)
+ {
+ LVITEM lvi;
+ lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
+ lvi.state = lvi.stateMask;
+
+ if (FALSE == SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)iSelected, (LPARAM)&lvi))
+ iSelected = -1;
+ else
+ {
+ SNDMSG(hChannel, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)iSelected);
+ SNDMSG(hChannel, LVM_ENSUREVISIBLE, (WPARAM)iSelected, (LPARAM)FALSE);
+ }
+ }
+
+ if (FALSE == selectChannel || 0 == channelSize)
+ {
+ PodcastChannel_SelectionChanged(hwnd, FALSE);
+ }
+
+ return TRUE;
+}
+
+static void SubscriptionView_OnParentNotify(HWND hwnd, UINT uMsg, HWND childId, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case /*WM_XBUTTONDOWN*/0x020B:
+ {
+ POINT pt;
+ GetCursorPos(&pt);
+
+ RECT rect;
+ HWND hItem = GetDlgItem(hwnd, IDC_ITEMLIST);
+ if (NULL != hItem && hItem != GetFocus() && FALSE != GetClientRect(hItem, &rect))
+ {
+ MapWindowPoints(hItem, HWND_DESKTOP, (POINT*)&rect, 2);
+ if (PtInRect(&rect, pt))
+ {
+ SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hItem, MAKELPARAM(TRUE, 0));
+ }
+ }
+ }
+ break;
+ }
+}
+
+static LRESULT SubscriptionView_OnSetStatus(HWND hwnd, LPCWSTR pszStatus)
+{
+ HWND hStatus = GetDlgItem(hwnd, IDC_STATUS);
+ if (NULL == hStatus) return FALSE;
+
+ WCHAR szBuffer[512] = {0};
+ if (IS_INTRESOURCE(pszStatus))
+ {
+ pszStatus = WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszStatus, szBuffer, ARRAYSIZE(szBuffer));
+ }
+
+ return SetWindowText(hStatus, pszStatus);
+}
+
+static INT_PTR CALLBACK SubscriptionView_DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG: return SubscriptionView_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: SubscriptionView_OnDestroy(hwnd); return TRUE;
+ case WM_WINDOWPOSCHANGED: SubscriptionView_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return TRUE;
+ case WM_DISPLAYCHANGE: SubscriptionView_UpdateSkin(hwnd); return TRUE;
+ case WM_CONTEXTMENU: SubscriptionView_OnContextMenu(hwnd, (HWND)wParam, MAKEPOINTS(lParam)); return TRUE;
+ case WM_NOTIFYFORMAT: MSGRESULT(hwnd, NFR_UNICODE);
+ case WM_COMMAND: SubscriptionView_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ case WM_NOTIFY: MSGRESULT(hwnd, SubscriptionView_OnNotify(hwnd, (INT)wParam, (NMHDR*)lParam));
+ case WM_CHAR: SubscriptionView_OnChar(hwnd, (UINT)wParam, (UINT)lParam); return TRUE;
+ case WM_PARENTNOTIFY: SubscriptionView_OnParentNotify(hwnd, LOWORD(wParam), (HWND)HIWORD(wParam), lParam); return TRUE;
+
+ case WM_USER + 0x200: MSGRESULT(hwnd, TRUE);
+ case WM_USER + 0x201: SubscriptionView_OnSetUpdateRegion(hwnd, (HRGN)lParam, MAKEPOINTS(wParam)); return TRUE;
+
+ case SVM_REFRESHCHANNELS: MSGRESULT(hwnd, SubscriptionView_OnRefreshChannels(hwnd, (BOOL)wParam));
+ case SVM_SETSTATUS: MSGRESULT(hwnd, SubscriptionView_OnSetStatus(hwnd, (LPCWSTR)lParam));
+
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
+ s.mode = 2;
+ }
+ return 0;
+
+ case WM_APP + 104:
+ {
+ Downloads_UpdateButtonText(hwnd, enqueuedef);
+ SendMessage(hwnd, WM_DISPLAYCHANGE, 0, 0);
+ return 0;
+ }
+
+ case WM_PAINT:
+ if (IsWindowVisible(GetDlgItem(hwnd, IDC_DESCRIPTION)))
+ {
+ int tab[] = { IDC_DESCRIPTION|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwnd, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+ }
+ return 0;
+}
+
+HWND CALLBACK SubscriptionView_Create(HWND hParent, OmService *service)
+{
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_PODCAST, hParent, SubscriptionView_DlgProc, (LPARAM)service);
+}
+
+HWND CALLBACK SubscriptionView_FindWindow()
+{
+ HWND hView = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == hView) return NULL;
+
+ WCHAR szBuffer[128] = {0};
+ if (0 == GetClassName(hView, szBuffer, ARRAYSIZE(szBuffer)) ||
+ CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, 0, szBuffer, -1, L"#32770", -1) ||
+ 0 == GetWindowText(hView, szBuffer, ARRAYSIZE(szBuffer)) ||
+ CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, 0, szBuffer, -1, SUBSCRIPTIONVIEW_NAME, -1))
+ {
+ hView = NULL;
+ }
+
+ return hView;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/subscriptionView.h b/Src/Plugins/Library/ml_wire/subscriptionView.h
new file mode 100644
index 00000000..9bc5555d
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/subscriptionView.h
@@ -0,0 +1,29 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_SUBSCRIPTION_VIEW_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_SUBSCRIPTION_VIEW_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#include <windows.h>
+#include "../nu/listview.h"
+#include "../nu/AutoLock.h"
+
+class OmService;
+
+HWND CALLBACK SubscriptionView_Create(HWND hParent, OmService *service);
+HWND CALLBACK SubscriptionView_FindWindow(void);
+
+#define SVM_FIRST (WM_APP + 13)
+
+#define SVM_REFRESHCHANNELS (SVM_FIRST + 0)
+#define SubscriptionView_RefreshChannels(/*HWND*/ __hwnd, /*BOOL*/ __sort)\
+ ((BOOL)PostMessage((__hwnd), SVM_REFRESHCHANNELS, (WPARAM)(__sort), 0L))
+
+#define SVM_SETSTATUS (SVM_FIRST + 1)
+#define SubscriptionView_SetStatus(/*HWND*/ __hwnd, /*LPCWSTR*/ __status)\
+ ((BOOL)SNDMSG((__hwnd), SVM_SETSTATUS, 0, (LPARAM)(__status)))
+
+#endif //NULLSOFT_PODCAST_PLUGIN_SUBSCRIPTION_VIEW_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/util.cpp b/Src/Plugins/Library/ml_wire/util.cpp
new file mode 100644
index 00000000..78ad4277
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/util.cpp
@@ -0,0 +1,271 @@
+#include "main.h"
+#include "./util.h"
+#include "api__ml_wire.h"
+
+#include <strsafe.h>
+
+LPWSTR Plugin_MallocString( size_t cchLen )
+{
+ return (LPWSTR)calloc( cchLen, sizeof( WCHAR ) );
+}
+
+void Plugin_FreeString( LPWSTR pszString )
+{
+ if ( NULL != pszString )
+ {
+ free( pszString );
+ }
+}
+
+LPWSTR Plugin_ReAllocString( LPWSTR pszString, size_t cchLen )
+{
+ return (LPWSTR)realloc( pszString, sizeof( WCHAR ) * cchLen );
+}
+
+LPWSTR Plugin_CopyString( LPCWSTR pszSource )
+{
+ if ( NULL == pszSource )
+ return NULL;
+
+ //INT cchSource = lstrlenW( pszSource ) + 1;
+
+ //LPWSTR copy = Plugin_MallocString( cchSource );
+ //if ( NULL != copy )
+ //{
+ // CopyMemory( copy, pszSource, sizeof( WCHAR ) * cchSource );
+ //}
+ //
+ return _wcsdup( pszSource );
+}
+
+LPSTR Plugin_MallocAnsiString( size_t cchLen )
+{
+ return (LPSTR)calloc( cchLen, sizeof( CHAR ) );
+}
+
+LPSTR Plugin_CopyAnsiString( LPCSTR pszSource )
+{
+ if ( NULL == pszSource )
+ return NULL;
+
+ //INT cchSource = lstrlenA(pszSource) + 1;
+ //
+ //LPSTR copy = Plugin_MallocAnsiString(cchSource);
+ //if (NULL != copy)
+ //{
+ // CopyMemory(copy, pszSource, sizeof(CHAR) * cchSource);
+ //}
+
+ return _strdup( pszSource );
+}
+
+void Plugin_FreeAnsiString( LPSTR pszString )
+{
+ Plugin_FreeString( (LPWSTR)pszString );
+}
+
+LPSTR Plugin_WideCharToMultiByte( UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar )
+{
+ INT cchBuffer = WideCharToMultiByte( codePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, lpDefaultChar, lpUsedDefaultChar );
+ if ( 0 == cchBuffer )
+ return NULL;
+
+ LPSTR buffer = Plugin_MallocAnsiString( cchBuffer );
+ if ( NULL == buffer )
+ return NULL;
+
+ if ( 0 == WideCharToMultiByte( codePage, dwFlags, lpWideCharStr, cchWideChar, buffer, cchBuffer, lpDefaultChar, lpUsedDefaultChar ) )
+ {
+ Plugin_FreeAnsiString( buffer );
+ return NULL;
+ }
+
+ return buffer;
+}
+
+LPWSTR Plugin_MultiByteToWideChar( UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte )
+{
+ if ( NULL == lpMultiByteStr )
+ return NULL;
+
+ INT cchBuffer = MultiByteToWideChar( codePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0 );
+
+ if ( NULL == cchBuffer )
+ return NULL;
+
+ if ( cbMultiByte > 0 )
+ cchBuffer++;
+
+ LPWSTR buffer = Plugin_MallocString( cchBuffer );
+
+ if ( NULL == buffer )
+ return NULL;
+
+ if ( 0 == MultiByteToWideChar( codePage, dwFlags, lpMultiByteStr, cbMultiByte, buffer, cchBuffer ) )
+ {
+ Plugin_FreeString( buffer );
+ return NULL;
+ }
+
+ if ( cbMultiByte > 0 )
+ {
+ buffer[ cchBuffer - 1 ] = L'\0';
+ }
+
+ return buffer;
+}
+
+
+LPWSTR Plugin_DuplicateResString( LPCWSTR pszResource )
+{
+ return ( IS_INTRESOURCE( pszResource ) ) ? (LPWSTR)pszResource : Plugin_CopyString( pszResource );
+}
+
+void Plugin_FreeResString( LPWSTR pszResource )
+{
+ if ( !IS_INTRESOURCE( pszResource ) )
+ Plugin_FreeString( pszResource );
+}
+
+HRESULT Plugin_CopyResString( LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString )
+{
+ if ( NULL == pszBuffer )
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ if ( NULL == pszString )
+ {
+ pszBuffer[ 0 ] = L'\0';
+ }
+ else if ( IS_INTRESOURCE( pszString ) )
+ {
+ if ( NULL == WASABI_API_LNG )
+ hr = E_FAIL;
+ else
+ WASABI_API_LNGSTRINGW_BUF( (INT)(INT_PTR)pszString, pszBuffer, cchBufferMax );
+ }
+ else
+ {
+ hr = StringCchCopy( pszBuffer, cchBufferMax, pszString );
+ }
+
+ return hr;
+}
+
+void Plugin_SafeRelease( IUnknown *pUnk )
+{
+ if ( NULL != pUnk )
+ pUnk->Release();
+}
+
+HRESULT Plugin_FileExtensionFromUrl( LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension )
+{
+ LPCWSTR cursor = pszUrl;
+ while ( L'\0' != *cursor && L'?' != *cursor )
+ cursor = CharNext( cursor );
+
+ LPCWSTR end = cursor;
+
+ while ( cursor != pszUrl && L'/' != *cursor && L'\\' != *cursor && L'.' != *cursor )
+ cursor = CharPrev( pszUrl, cursor );
+
+ if ( L'.' == *cursor && cursor != pszUrl )
+ {
+ if ( CharNext( cursor ) < end )
+ {
+ INT cchExtension = (INT)(INT_PTR)( end - cursor );
+ return StringCchCopyN( pszBuffer, cchBufferMax, cursor, cchExtension );
+ }
+ }
+
+ return StringCchCopy( pszBuffer, cchBufferMax, defaultExtension );
+}
+
+void Plugin_ReplaceBadPathChars( LPWSTR pszPath )
+{
+ if ( NULL == pszPath )
+ return;
+
+ while ( L'\0' != *pszPath )
+ {
+ switch ( *pszPath )
+ {
+ case L'?':
+ case L'/':
+ case L'\\':
+ case L':':
+ case L'*':
+ case L'\"':
+ case L'<':
+ case L'>':
+ case L'|':
+ *pszPath = L'_';
+ break;
+ default:
+ if ( *pszPath < 32 )
+ *pszPath = L'_';
+ break;
+ }
+
+ pszPath = CharNextW( pszPath );
+ }
+}
+
+INT Plugin_CleanDirectory( LPWSTR pszPath )
+{
+ if ( NULL == pszPath )
+ return 0;
+
+ INT cchPath = lstrlen( pszPath );
+ LPWSTR cursor = pszPath + cchPath;
+ while ( cursor-- != pszPath && ( L' ' == *cursor || L'.' == *cursor ) )
+ *cursor = L'\0';
+
+ return ( cchPath - (INT)(INT_PTR)( cursor - pszPath ) - 1 );
+}
+
+HRESULT Plugin_EnsurePathExist( LPCWSTR pszDirectory )
+{
+ DWORD ec = ERROR_SUCCESS;
+
+ UINT errorMode = SetErrorMode( SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS );
+
+ if ( 0 == CreateDirectory( pszDirectory, NULL ) )
+ {
+ ec = GetLastError();
+ if ( ERROR_PATH_NOT_FOUND == ec )
+ {
+ LPCWSTR pszBlock = pszDirectory;
+ WCHAR szBuffer[ MAX_PATH ] = { 0 };
+
+ LPCTSTR pszCursor = PathFindNextComponent( pszBlock );
+ ec = ( pszCursor == pszBlock || S_OK != StringCchCopyN( szBuffer, ARRAYSIZE( szBuffer ), pszBlock, ( pszCursor - pszBlock ) ) ) ? ERROR_INVALID_NAME : ERROR_SUCCESS;
+
+ pszBlock = pszCursor;
+
+ while ( ERROR_SUCCESS == ec && NULL != ( pszCursor = PathFindNextComponent( pszBlock ) ) )
+ {
+ if ( pszCursor == pszBlock || S_OK != StringCchCatN( szBuffer, ARRAYSIZE( szBuffer ), pszBlock, ( pszCursor - pszBlock ) ) )
+ ec = ERROR_INVALID_NAME;
+
+ if ( ERROR_SUCCESS == ec && !CreateDirectory( szBuffer, NULL ) )
+ {
+ ec = GetLastError();
+ if ( ERROR_ALREADY_EXISTS == ec )
+ ec = ERROR_SUCCESS;
+ }
+
+ pszBlock = pszCursor;
+ }
+ }
+
+ if ( ERROR_ALREADY_EXISTS == ec )
+ ec = ERROR_SUCCESS;
+ }
+
+ SetErrorMode( errorMode );
+ SetLastError( ec );
+
+ return HRESULT_FROM_WIN32( ec );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_wire/version.rc2 b/Src/Plugins/Library/ml_wire/version.rc2
new file mode 100644
index 00000000..3fe05bd4
--- /dev/null
+++ b/Src/Plugins/Library/ml_wire/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,80,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,80,0,0"
+ VALUE "InternalName", "Nullsoft Podcasts"
+ VALUE "LegalCopyright", "Copyright © 2005-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_wire.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END